-
Notifications
You must be signed in to change notification settings - Fork 27
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support saving of entities to memcache in putMulti() #58
Comments
Hi @astec and thanks for your interest. You are right, caching is hard and it's going to take me a while to get the First of all I think your analysis of step 7 is incorrect which might explain your issue with the
I think this statement is wrong. Within a transaction a lock is only placed here when we are sure the transaction will succeed and it is never removed. Although it expires after However, outside a transaction memcache lock is removed in the The reason we create memcache lock items in the first place is to ensure that any stale cache is guaranteed to be removed before a subsequent If we used your suggestion and the actual I hope this helps. It's fun to think about all the edge cases and error pathways in order to keep cache consistency. Maybe/hopefully you can come up with a better strategy! |
First of all sorry I made some links to my fork instead of your repo and some lead to wrong lines due my recent commit - edited to permalinks. I agree that statement 7 is incorrect, overlooked the if condition. I still believe you can save entities to memcache on Consider this flow:
If put/transaction failed we don't save to memcache. While tx is in progress other process would not override the items as it's locked. If memcache lock was not successful we simply do not put entities to memcache and basically fall back to current behaviour. If everything works as intended (I guess would be 99.99% in my use case) we gain 50% less datastore reads for Am I missing anything? |
I like that idea and from what I can see this would work 99.99% of the time as you suggest. However there is a 0.01% race condition that would lead to cache inconsistency. I imagine that under high instance and memcache load it could become an issue and incredibly difficult to debug. Consider the scenario that will cause cache consistency: I often see long pauses and/or memcache purges between App Engine RPC calls that could make this race condition a reality. |
At first I thought it's a very good point and we are doomed. But after considerations I think there is no race or it can be mitigated. Basically we don't care about evicted or expired lock items in memcache. We just need to know if the lock was successful in first place so we can try to do CompareAndSwap later on transaction success. If the lock evicted/expired while the 1st transaction is in progress and the 2nd transaction overwrites entity with another value then:
|
Unfortunately the datastore transaction contention semantics will not help us at this point because it would have already committed the transaction at this line which you mention in point 3 in the comment above.
I currently can't think of a reason why this wouldn't work! I'm going to have to think about its interaction with |
Good to know we have a hope. I don’t get your point reagrads datastore tx semantic. As i stated before the memcache check & put should happen outside of datastore tx. Instead of “return datastore.RunInTransaction()” you should do “err = datastore.RunInTransaction()“ and if err is nil do the memcache checks & put. If the 2nd transaction committed the 1st should fail, we get err!=nil and instead of saving to memcache do the delete from memcache (to clear locks in case if failed due to non conflict). They can’t both return err==nil if updating same entity in parallel, isn’t it? If they do it’s mean they are not overlapping and we are fine to put to memcache. Of course developer should first call Get() within transaction but its the same requirement as for nds-less code. So I still don’t see race here. P.S. |
By the way probably the tx.locItems should be a map by key rather a slice to handle tx autoretries gracefully. I suspect that if the f() gets executed multiple times you can get duplicates in tx.lockitems and get unpredictable behavior. |
If I got it right currently entities are not stored to memcache on Put().
Suppose we run in transaction and need to update some entities. Then current execution order is:
First of all it's not clear why we create lockItem() at line 129 as it seems it is not used further in current implementation.
Second just removing items from memcache results in unnecessary paid Get() operation on following request while potentially results could be retrieved from the "free" memcache. For example I have a chat application and whenever user writes to the bot and bot sends response I do following:
Currently the first
Get()
is not leveraging memcache with always a miss. This increases response time & costs.So the suggestion is:
nds
should try to lock items in memcache.nds
tries to put items to memcaches using CAS.I know caching is hard to get right and probably I'm missing something.
edit: fixed urls to permalinks
The text was updated successfully, but these errors were encountered: