Implementing an home grown locking feature, what are expiration time semantics

I have an issue where we need to either create or update a document depending on whether a document with property x does or does not already exist in the db.

here is psuedo code for the issue:

if (documentWithPropXExistsOnDbQuery()):
     update doc with prop X
else:
     create doc with prop x

here is a race condition where 2 or more callers can get to the else branch and create a document. Since the primary key is generated by uuid the DB lets it go, but in the real world we have a duplicate. (please don’t tell me to switch out the pk logic, because it’s not an option here)

my thought is to roll my own lock where I basically do something like this:

lock = getLock(codeFileName+functionName)
if lock:
       try: 
            do work
      finally:
            releaseLock(codeFileName + functionName)

now getLock would simply write a tiny doc to the db with persistToAllNodes
and releaseLock would delete that document

the thinking is that no two callers could succeed in creating that lock.

Now, this is scary to us, because who knows if something wont go wrong and something, something, we fail to delete the document and “release this lock.”

So the obvious solution is to write the document with an couchbase expiration, but I can’t get a solid enough grasp of its governing semantics.

So here are my questions:

  1. Is there an easier way to do what I’m looking to do?

  2. What are the semantics of expirations, namely:

Is the timing guaranteed or is it along the lines of a hint?
I’ve read that getting the doc after the expiration will result in a not found even though the doc hasn’t been deleted but what about inserting?

  1. If I try to insert a doc (with an expiration) which is duplicate to a document that’s been inserted with an expiration, will it result in an ‘you can’t create a duplicate document’ error like I’m hoping, or will just upsert and reconfigure the expiration?

  2. if a doc is expired but not yet actually deleted, will inserting the same doc again just result in a new doc with a new expiration like we are hoping, or are there potential surprises we need to look out for.

is there any clarification I could add that might help me get a response?

Hey @naftali, welcome to the forums :slight_smile:

I’m not sure you need the lock. Why not let the two callers race in the else branch, and rely on one of them failing with a document-already-exists error?

Update: actually I misread your post, I see you create the document with a UUID, so this won’t work. Please disregard this post, I’ll have another stab at this…

Could you simply use a KV upsert operation here? It seems that regardless of whether the document currently exists or not you want to end up with the same document. And if you have two callers at the same time, you still want to end up with the same document? If so, then upsert would seem to meet your needs.

If there’s a quirk to the problem that means upsert can’t work then let me know, perhaps we can do something clever with KV sub-document instead.

thank you for the reply.

i don’t know how a KV upsert will solve this. what key would I use. each caller checks if the doc with property x is there, and if not creates a doc by generating a key. the key will be unique to each caller, there’s no way it’s known outside the context of the particular call.

I get that changing the data model is the correct solution here, however it’s not feasible right now, given the real world factors governing this little piece of our big puzzle (there’s class hierarchies and technical debt and refactor time allocation and QA resources and there’s the likelyhood that this will be a problem in prod and the impact it will have if it will).

but this locking would be generally useful, and I’m wondering if and why you don’t think it would work.

Ok so this particular challenge aside, can you please point me to the documentation or spell out the document expiration semantics?

namely,

Is the timing guaranteed or is it along the lines of a hint?
I’ve read that getting the doc after the expiration will result in a not found even though the doc hasn’t been deleted but what about inserting?

  1. If I try to insert a doc (with an expiration) which is duplicate to a document that’s been inserted with an expiration, will it result in an ‘you can’t create a duplicate document’ error like I’m hoping, or will just upsert and reconfigure the expiration?
  2. if a doc is expired but not yet actually deleted, will inserting the same doc again just result in a new doc with a new expiration like we are hoping, or are there potential surprises we need to look out for.

If the feature is only supposed to be used for the naive case of designating a doc for garbage collection and nothing more, that’s fine, but I’d like to know that that’s all that’s officially supported.

after some reading, there is zero reason to implement a home grown locking mechanism using expirations…

couchbase features getAndLock(key, timeout) where you can lock an arbitrary document (whether the target of your update, or a sentry doc that you’ve set up to act as lock for a particular code path). the timeout is max time of the lock (the longest the timeout can be is 30 seconds because that’s when couchbase releases the lock if your code does not).

the lock is release either via an explicit unlock(key) call (see the doc, going from memory…) or else when you’ve updating the doc with the CAS or else couchbase releases it after 30 seconds if neither of the foregoing 2 things happened first.

1 Like