I have a central database for handling user credit with multiple servers reads and writes to it. The application sits on top of these servers serve user requests by doing the following for each request:
1. check if user has enough credit for the task by reading from db. 2. perform the time consuming request 3. deduct a credit from user account, save the new credit count back to db.
the application uses the database's optimistic locking. So following might happen
1. request a comes in, see that user x has enough credit, 2. request b comes in, see that user x has enough credit, 3. a performs work 4. a saves the new credit count back to db 5. b performs work 6. b tries to save the new credit count back to db, application gets an exception and fails to account for this credit deduction.
With pessimistic locking, the application will need to explicitly get a lock on the user account to guarantee exclusive access, but this KILLs performance since the system have many concurrent requests.
so what would be a good new design for this credit system?
Here are two "locking" mechanisms at avoid using InnoDB's locking mechanism for either of two reasons:
BEGIN...COMMITof InnoDB.Plan A. (This assumes the race condition is rare, and the time wasted for Step 2 is acceptable in those rare cases.)
START TRANSACTION;ROLLABCKand abort if not.)COMMIT;START..COMMITis InnoDB transaction stuff. If a race condition caused 'x' to not have credit by step 4, you willROLLBACKand not perform steps 4 and 5.Plan B. (This is more complex, but you might prefer it.)
Locksfor locking. It contains user_id and a timestamp.START TRANSACTION;Locks, abort (ROLLBACKand exit).INSERT INTO Locksthe user_id and current_timestamp inLocks(thereby "locking" 'x').COMMIT;DELETE FROM Locks WHERE user_id = 'x';(autocommit=1suffices here.)A potential problem: If the processing dies in step 6, not getting around to releasing the lock, that user will be locked out forever. The 'solution' is to periodically check
Locksfor any timestamps that are 'very' old. If any are found, assume that the processing died, and DELETE the row(s).