Google engine: concurrent user registration

I know this is a classic problem, but I still don't know how to do it. On Google App Engine, I have a member registration form that uses jQuery validation to check if a username exists. Of course there is a concurrency problem: multiple users try to register, enter the same username, Validation finds an available username and lets them click Add at about the same time. Checking will not detect this. In my application, the username, email address, and personal ID must be unique. How to prevent the following code from concurrency issue:

member = Member()
member.username = self.request.get('username')
member.Pid = self.request.get('Pid')
member.email = self.request.get('email')
...

      

+3


source to share


3 answers


Since there is a unique constraint on the username, you must use it as a key in the data store and use transactions.

def txn():
    key = ndb.Key(Member, username)
    member = key.get()
    if member is not None:
        raise CustomAlreadyExistsException(member)  # This will abort txn
    member = Member(
        id=username,
        Pid=self.request.get('Pid'),
        email=self.request.get('email'),
        ...)
    member.put()
ndb.transaction(txn)

      

This ensures that only one person can register a username.

The jQuery helper will check if it produces a ndb.Key(Member, userid).get()

result or not. GET is not transactional.



To improve the user side of usability in "reserve" the username after checking for availability, you can use memcached as suggested by Daniel, but I would call YAGNI, skip the complexity and rather let some people get a validation error after submitting the form. Note that memcached is best effort and no guarantees about anything.

If you want guaranteed uniqueness across multiple fields, you need to add class classes for them and check the cross-group (XG) transaction.

class Pid(ndb.Model):
    member = ndb.KeyProperty()

class Email(ndb.Model):
    member = ndb.KeyProperty()

class Member(ndb.Model):
    pid = ndb.KeyProperty()
    email = ndb.KeyProperty()

    @property
    def pid_value(self):
        return self.pid.id()

    @property
    def email_value(self):
        return self.email.id()

def txn():
    member_key = ndb.Key(Member, username)
    pid_key = ndb.Key(PersonalId, self.request.get('Pid'))
    email_key = ndb.Key(Email, self.request.get('email'))

    member, pid, email = ndb.get_multi([member_key, pid_key, email_key])

    if member is not None or pid is not None or email is not None:
        raise CustomAlreadyExistsException(member, pid, email)  # This will abort txn

    # Create instances referencing each other
    email = Email(key=email_key, member=member_key)
    pid = Pid(key=pid_key, member=member_key)
    member = Member(
        key=member_key,
        pid=pid_key,
        email=email_key,
        ...)
    ndb.put_multi([member, pid, email])

ndb.transaction(txn, xg=True)

      

+3


source


This is a great use for memcache. Your Ajax validation function should put an entry in memcache to record that the username was requested. It should also check both memcache and data store to ensure the username is free. Likewise, the registration code must check memcache to ensure that the current user is the one who requested the username.



This solves the concurrency problem nicely, and the best thing is that memcache entries expire on their own, either in time or when the cache gets too full.

+3


source


I agreed with tesdal. If you still want to implement the memcache tag as suggested by Daniel you should do something like "memcache.add (usernameA, dummy value, short period)". This way you know that usernameA is reserved for a short period and will not conflict with "memcache.add (usernameB, ..."

0


source







All Articles