I am wondering how to deal with concurrency given a general ledger. Consider schema like this:
id | account_id | credit | debit | balance |
1 | 123 | 0 | 100 | 200 |
2 | 456 | 100 | 0 | 100 |
To add a new entry to the ledger, I would do (pseudo code):
last_entry = get last account entry
is_credit = figure out if it is debit or credit entry
is_liability = figure out type of account
new_entry = Entry(
foo='bar'
# etc
)
if is_liability and is_credit
new_entry.balance = last_entry.balance + amount
if is_liability and !is_credit
new_entry.balance = last_entry.balance - amount
if !is_liability and is_credit
new_entry.balance = last_entry.balance - amount
if !is_liability and !is_credit
new_entry.balance = last_entry.balance + amount
new_entry.save()
The problem I see with such approach is:
Let's say a request comes and I have to enter new entry in the ledger. The new entry is going to increase the account balance.
What if in the middle of running the above code (let's say after getting the last entry) there is another request which will increase the balance again.
So the balance will be increased once, the other request will save a new entry with the same balance as it will just use something like:
new_balance = last_entry.balance + amount
But the last_entry has been outdated by another request so the balance is higher now.
Any ideas how to make sure a situation like this does not happen (I know this would not be very likely).
UPDATE:
Following some answers, I have come up with this solution using SELECT FOR UPDATE:
with transaction.atomic():
new_entries = prepare_entries()
for new_entry in new_entries:
new_entry.save()
Is this a good way to get around potential concurrency issues?