September 12, 2011

Counters done right.

Terry has posted two articles on assertions and "increment"  that I thought were good stepping stones to show you how a Directory Server may solve some tough problems better than you think: how to concurrently keep track of counters in your applications.


Let's take a simple application:
Get a value, decrement it, save it back. Simple. It works. Until ....
Shoot. We lost 1. This type of concurrency issue is well know to developers who have to concurrently keep track of sessions for example. The proposed solution is locking. Here's the issue with locking:

Why the warning sign? Well, locking works. Fine actually. But the client waits. In a lot of cases you do not even have much of a choice. But for counters, you do, and most likely you must. Here's an alternative:
OK, I know, the server-side decrement isn't exactly prime news and has been around for a while. No surprise! It's way easier from the application side and leaves the developer to solely focus on business "value-added" logic. So why is it not used more? I don't know. Maybe because not very many people know to ask the right question, so they never get the right answer?
With MySQL or other relational databases you usually have the ability to so with a pseudo syntax like:
UPDATE plan SET minutes=minutes-1 WHERE subscriber_id=(555)123-4567

Let's make that even better with some LDAP. First, let's take a look at how to decrement the "minutesLeft" counter stored in our user.19 profile:
dn: uid=user.19,ou=people,dc=example,dc=com
changetype: modify
increment: minutesLeft
minutesLeft: -1

So, functionally, our application will attempt to decrement the minutesLeft counter and depending on whether or not it succeeds it will proceed to let the user use his/her plan for a minute. Additionally, we'll make sure that there are indeed minutesLeft on the plan for the decrement to be successful. That's where the assertion comes in the picture. The --assertionFilter can be used on the CLI tool to manually test it. In your client, the UnboundID LDAP SDK provides full programmatic control.


This is how it looks when it is successful (there ARE minutes left!)
C:\UnboundID-DS\bat>ldapmodify -a -c --assertionFilter "(minutesLeft>=1)" --postReadAttributes minutesLeft -f Decrement.ldif
# Processing MODIFY request for uid=user.19,ou=people,dc=example,dc=com
# MODIFY operation successful for DN uid=user.19,ou=people,dc=example,dc=com
# Target entry after the operation:
# dn: uid=user.19,ou=People,dc=example,dc=com
# minutesleft: 2

Note that you can request the value before and/or after (only after in this example) to use the write operation as a read as well.
When our subscriber has depleted his plan completely, the server will return:
C:\UnboundID-DS\bat>ldapmodify -a -c --assertionFilter "(minutesLeft>=1)" --postReadAttributes minutesLeft -f Decrement.ldif


# Processing MODIFY request for uid=user.19,ou=people,dc=example,dc=com
MODIFY operation failed
Result Code:  122 (Assertion Failed)
Diagnostic Message:  Entry uid=user.19,ou=people,dc=example,dc=com cannot be modified because the request contained an LDAP assertion control and the associated filter did not match the contents of the that entry


So there you have it, the most elegant, concurrent-friendly way to keep track of your counters and avoid server round-trips to keep user experience nice thanks to constant low-latency requests.

No comments:

Post a Comment