In Google App Engine, how can I work with eventual

2019-02-18 05:28发布

问题:

I've noticed that, with eventual consistency, the common form-processing workflow that I am used to (submit -> create/update record -> redirect -> reload) doesn't work. Upon redirect, the new record (probably) won't be available for display. How should I handle forms so that updates are displayed upon reload?

I could try to use strong consistency, but as the App Engine documentation notes, updates are limited to one update per second.

So how can I process a form providing immediate user feedback with eventual consistency?

回答1:

Try to restructure your code so that you are getting by key (which always gives you the most recent data) instead of doing a query. I realize this isn't always possible, but I'll give you a recent example of something that worked for me.

I have a user dashboard where a user can create and delete "items". My entities looked like this:

class User(ndb.Model)
    ...

class Item(ndb.Model)
    user = ndb.KeyProperty(User, required=True)

In the past, I would do a query like this when responding to a GET request for the user dashboard.

items = Item.query(user=user.key)

This was a bad experience because a user would delete an item and after the POST/redirect/GET the just deleted item would again appear in the dashboard because of eventual consistency.

To fix this, I changed my User entity to have a list of Items like this:

class User(ndb.Model)
    items = ndb.KeyProperty(repeated=True)
    ...

Now, when I show the dashboard, I do this:

items = ndb.get_multi(user.items)

Since I am now getting by key, the data is always up to date.

This works for me because a user won't have that many items. If a user could have thousands of items, however, this approach wouldn't work because of the entity size limit.



回答2:

You probably still can use strong consistency, as long as you design your entity groups properly. Reading closely on the doc linked, the restriction is actually only one update per second per entity group.

This is because Google's underlying masterless consistency protocol ("Paxos") guarantees strong consistency only when working within an entity group (being defined as an entire group of keys, under a root key and including that root key, where the eventual parent of all non-root keys is that root key - see Transactions and entity groups for more).

Quoth the manual:

As mentioned above, an entity group is a set of entities connected through ancestry to a common root element. The organization of data into entity groups can limit what transactions can be performed:

... [ details about transactions ] ...

  • There is a write throughput limit of about one transaction per second within a single entity group. This limitation exists because Datastore performs masterless, synchronous replication of each entity group over a wide geographic area to provide high reliability and fault tolerance.

So, if you architect your Datastore keys to use, say, the logged-in-user's ID or email address as a parent key, reading the resulting key on the other end can be guaranteed strongly consistent by specifying that key as an ancestor.

It's also worth noting that an entity doesn't technically have to exist at that parent key - you just need a key to start the root of your entity group so that you can apply your consistency guarantee.

If you can architect that way, you can both guarantee consistency and apply writes at one-per-second-per-user, or whatever you scope your keys to.

For more info, see "Structuring for Strong Consistency".