I need to come up with a strategy for handling client-retries on a data-store entry creation:
- Client sends request to create new entry in database
- Server performs entry creation and prepares success-reply
- Some error happens that makes the client believe that the request wasn't processed (packet loss, ...)
- Client sends same request to create new entry in database again
- Server detects retry and recreates and sends original reply without creating another data-store entry
- Client receives reply
- Everyone is happy and only ONE entry was created in database
I have one restriction: The server is STATELESS! It has no kind of session-information on the client.
My current idea is the following:
- Tag every create-request with a guaranteed globally unique ID (here's how I create them, although they are not too relevant for the question):
- Using the data-store (and memcache), I assign a unique, monotonically increasing ID to every server instance once it loads (let's call it SI)
- When a client requests the starting-page, the instance that served the request generates a unique monotonically increasing page-load-id (PL) and sends SI.PL to the client along with the page content
- For every create-request, the client generates a unique monotonically increasing request-id (RI) and sends SI.PL.RI along with the create-request
- For every create-request, the server first checks whether it knows the create-tag
- If not, it creates the new entry and somehow stores the create-tag along with it
- If it does know the tag, it uses it to find the originally created entry and recreates a corresponding reply
Here are the implementation options that I am thinking about right now and their problems:
- Store the create-tag as an indexed property inside the entry:
- When the server gets a request, it has to use a query to find any existing entry
- Problem: Since queries in AppEngine are only eventually consistent, it might miss an entry
- Use the create-tag as the entry's key:
- Should be ok as it is guaranteed to be unique if the numbers don't wrap (unlikely with longs)
- Minor inconvenience: It increases the length of the entries' keys in any future use (unneeded overhead)
- Major problem: This will generate sequential entry keys in the datastore which should be avoided at all cost as it creates hot-spots in the stored data and thus can impact performance significantly
One solution I am contemplating for option 2 is to use some sort of formula that takes the sequential numbers and re-maps them onto a unique, deterministic, but random-looking sequence instead to eliminate hot-spots. Any ideas on what such a formula could look like?
Or maybe there is a better approach altogether?