0

Consider an abstract example, just to illustrate - you have a service for loans, which gets requests to borrow X USD, checks if there’s enough USD to lend that, if yes - marks that amount as reserved for a loan and sends a message to another service which actually processes the loan, and responds with success, if no - responds with an error.

Given there’s X USD available in total if 2 users concurrently check for ability to borrow X they’ll both get a response telling that there’s enough USD for them, while actually only one of them can be served.

Assuming we want to be able to scale, and the service needs to respond and we can’t just put requests to some queue for asynchronous ordered processing, what are the design and implementation best practices to ensure consistency in such cases, so the second users gets a response telling that loan is not available? The first thing which comes to my mind is locking the db table when the read operation to check availability happens but it doesn’t sound too nice...

P.S. I’m sorry if the question title is not optimal, was wondering how to word it properly... If you know how it can be improved - please tell.

bumped to the homepage by Community 2 days ago

This question has answers that may be good or bad; the system has marked it active so that they can be reviewed.

2

What is important is that the success of the second request is independent of the success/failure of the first request, and vice-versa the success of the first request is independent of the success/failure of the second request.

Reservations

You could achieve this by reserving resources demanded from the account. This could be achieved at the database with an update commit, it should also add a record identifying whom has reserved it. Failure to reserve implies an insufficiency on the account that cannot currently satisfy the request.

Resources into the account don't participate because if their request fails, for any technical or business reason, then those resources where never available, and so should never be relied on for another request.

Once a reservation was successfully committed, then the request knows that it can process the rest of the request.

  • If that results in a rejection of the request, it need only remove the reservation.
  • If that results in accepting the request, the reservation transmutes into the transaction record itself.

Claim first, Verify next

You can achieve this by treating the account as a stream of events in a sort of event log. The request appends a record to the event stream, by definition this serialises the record. Then each thread/process is responsible for taking its current view of that account and reading each appended record from the last event to be added to the view up to the event that it itself requested to be appended.

  • Any record that changes the account in an invalid way, is rejected.
  • Any record that changes the account in a valid way, is accepted.

A Request for X dollars which leaves an account with a positive balance might be deemed valid, hence the request was accepted. Alternately perhaps the balance would have become negative, and that account has no over-draft, hence that action is invalid and the request would then have been rejected.

The view of the account at specific points in the log can be cached for speed, and importantly can be updated independently of other process working on the same account. There is a little overhead in comprehending the logged events but then again it does provide independence and serialisation.

Each account is a service

Alternately instead of having everyone work out independently what happened, treat each account as an actual service. When a request against that account occurs, they are all forwarded to the same single threaded process. This has the benefit of collocating all the knowledge and rules, but the disadvantage of not scaling well, there is literally one process/thread per account.

You could make the service responsible for a portfolio of accounts, but as it must be single-threaded all of these accounts must serialise their transaction when they don't have to, also load-balancing becomes an issue (2 process with very active accounts and 100 process with relatively passive accounts).

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.