It seems to me that a transaction in a method does not include the variables that are the arguments of the method. Because in my application I get the entity incremented without changes of the other models.
#ndb.transactional(xg=true)
def method(entity):
# `entity` is a datastore entity
# Transaction works here
Model.foo()
# ...here too
Model.bar()
# But not here! Always incremented.
entity.x += 1
entity.put()
In the example above the property x of the entity will be incremented even if the transaction fails.
Is it correct?
Yes. Since the entity get() was outside the transaction, there's no value to to roll back to (the transaction wouldn't know the previous value of entity. Also, if this transaction was actually intended to increment entity.x by one, it would fail to provide transactional consistency.
Imagine entity.x = 1, this transaction could potentially start, and a second request operating on the same request could run at the same time. Since both gets are outside of the transaction, they'll both read 1, then they'll both increment x to 2, and it'll save as 2, even though it was incremented twice and should be 3. Since the get() was outside the transaction, it would not be protected.
Related
In an Ndb datastore, a given entity has an attribute num, which should be a unique id, but contrary to Ndb's native ids, it must not be a huge number, but should be assigned equal to the max of current num values plus one.
In other words, you could assign it this way:
last_entity = MyEntity.query().order(-MyEntity.num).get()
max_num = last_entity.num
new_num = max_num + 1
And you would want to do this within a #transactional scope, up until you put() the newly created entity.
It would seem a good place to assign the num attribute (if not already set) might be in the _pre_put_hook(). But I don't think I could start a transaction within the _pre_put_hook() function that would comprise the final put() of the entity.
So how would this best be handled ?
Another approach would be to define a assign_num_and_put() function that would open a transaction (say doing a get on a parent entity), then find the max_num, assign max_num + 1 to new entity, and do the put(), all within the transaction.
Is there a way to update only specific property on NDB entity?
Consider this example.
Entity A has following property:
property B
property C
Let's assume both of these properties have values of 1 at the moment.
Two different request are trying to update same entity and they are happening at the same time.
So when Request#1 and #2 are retrieving this entity, value of B and C were 1.
Now Request #1 tries to update property B so it sets the value B to 2 and put into Datastore. Now B = 2 and C = 1 in the datastore.
But, Request #2 has B=1 and C=1 in the memory and when it change C to 2 and put into DB, it put's B=1 and C=2 which overwrites B value written by Request #1.
How do you get around this? Is there way to only write specific property into datastore?
I believe you may want to look into transactions.
As per the documentation:
If the transaction "collides" with another, it fails; NDB automatically retries such failed transactions a few times. Thus, the function may be called multiple times if the transaction is retried.
Link: https://developers.google.com/appengine/docs/python/ndb/transactions
I had tried to make my entire POST method transactional, but was unable to because it called other methods and was therefore nested. So what I've done is created a transactional method just to complete the db.put() of my entity.
def post(self):
myobj = db.get(key)
myobj.property = x + 1
second_method()
my_txn(my_obj)
#db.transactional
def my_txn(obj):
db.put(obj)
Is this a valid way to create a transaction?
No I don't think it has any use regarding the concept of TA.
Something like this:
def post(self):
second_method()
my_txn(key)
#db.transactional
def my_txn(key):
myobj = db.get(key)
myobj.property = x + 1
db.put(obj)
You should use transactions in order to ensure that the read(get) and write(put) are consistent.
In this way you know that when you are getting the entity and until you write the entity if anything has changed in the meanwhile will abort this TA and have a retry (default 3).
In your way of using it the get is outside the transaction thus making the transaction useless
There is only a role per user in the application at the same time. To update a role, we previously remove all the current roles:
Integer roleId = params.roleSelector.toInteger()
def roleInstance = Role.findById(roleId)
UserRol.removeAll userInstance
UserRol.create userInstance, roleInstance
It is working, but I think it is more correct to perform removeAll and create as an unitary operation in order to a correct roll back if any error happens.
Is it possible?
UPDATE 1.
I found here that we can add #Transactional to make a method transactional. So if we write:
#Transactional
private def unitaryOperationUpdate {
Integer roleId = params.roleSelector.toInteger()
def roleInstance = Role.findById(roleId)
UserRol.removeAll userInstance
UserRol.create userInstance, roleInstance
}
If some error happened between removeAll and create, it would roll back correctly?
By the way, I'd like to know how to check myself if it is working: I asked that question in a separate thread: How to check if a #transactional method perform rollback correctly in Grails?
Services are the right place to do that, because they're already transactional. It means that if something goes wrong the transaction will be rolledback.
A side note is that only unchecked exceptions will rollback your transaction.
I have a situation where I need to update votes for a candidate.
Citizens can vote for this candidate, with more than one vote per candidate. i.e. one person can vote 5 votes, while another person votes 2. In this case this candidate should get 7 votes.
Now, I use Django. And here how the pseudo code looks like
votes = candidate.votes
vote += citizen.vote
The problem here, as you can see is a race condition where the candidate’s votes can get overwritten by another citizen’s vote who did a select earlier and set now.
How can avoid this with an ORM like Django?
If this is purely an arithmetic expression then Django has a nice API called F expressions
Updating attributes based on existing fields
Sometimes you'll need to perform a simple arithmetic task on a field, such as incrementing or decrementing the current value. The obvious way to achieve this is to do something like:
>>> product = Product.objects.get(name='Venezuelan Beaver Cheese')
>>> product.number_sold += 1
>>> product.save()
If the old number_sold value retrieved from the database was 10, then the value of 11 will be written back to the database.
This can be optimized slightly by expressing the update relative to the original field value, rather than as an explicit assignment of a new value. Django provides F() expressions as a way of performing this kind of relative update. Using F() expressions, the previous example would be expressed as:
>>> from django.db.models import F
>>> product = Product.objects.get(name='Venezuelan Beaver Cheese')
>>> product.number_sold = F('number_sold') + 1
>>> product.save()
This approach doesn't use the initial value from the database. Instead, it makes the database do the update based on whatever value is current at the time that the save() is executed.
Once the object has been saved, you must reload the object in order to access the actual value that was applied to the updated field:
>>> product = Products.objects.get(pk=product.pk)
>>> print product.number_sold
42
Perhaps the select_for_update QuerySet method is helpful for you.
An excerpt from the docs:
All matched entries will be locked until the end of the transaction block, meaning that other transactions will be prevented from changing or acquiring locks on them.
Usually, if another transaction has already acquired a lock on one of the selected rows, the query will block until the lock is released. If this is not the behavior you want, call select_for_update(nowait=True). This will make the call non-blocking. If a conflicting lock is already acquired by another transaction, DatabaseError will be raised when the queryset is evaluated.
Mind that this is only available in the Django development release (i.e. > 1.3).