Google App Engine Datastore, returning before it w

2019-03-03 03:41发布

问题:

So I have values I need to update in my datastore. I am using a transaction do so as seen below. After the update has been committed I send a result back to the client letting them know the update has went through. The client then sends another request for an updated list of items. All code executes correctly as far as I can tell, no errors thrown and eventually I do get the update requested showing as expected.

My issue is even after the commit sometimes it is several seconds sometimes before the update shows in the returned list. If it was just a delay that would be crappy, but it is worse than that. The list returns incorrect / non-updated values during this time period. I understand that there can be delays with this architecture, but I thought the entire point of transactions was so that if something was updated like this nothing could possibly read the old value once the transaction grabbed the desired item? To see the old value for a long, long time afterwards seems incorrect. Much less waiting until after the transaction says it has been committed and having an entire 300ms+ RTT and still getting bad values for several seconds after it was supposedly committed. What am I missing here?

/*
    We don't actually delete a post, we simply replace it with a [removed]
    version of itself.
*/
router.delete('/', function (req, res) {

    //Check our parameters
    if (req.body == null || req.body["Thread"] == null || typeof req.body["Thread"] !== "string") {
        res.json({ success: false, message: "Thread name was not included as a parameter.", data: null});
        return;
    }

    if (req.body == null || req.body["PostNumber"] == null) {
        res.json({ success: false, message: "Post Number was not included as a parameter.", data: null });
        return;
    }

    if ((parseInt(req.body["PostNumber"]) || 0) < 1) {
        res.json({ success: false, message: "Post Number was not a valid numeric value.", data: null });
        return;
    }

    var transaction = datastore.transaction();
    transaction.get(datastore.key(['Post', PostName(req.body["Thread"], 6, parseInt(req.body["PostNumber"]))]), function (err, value) {

        if (err)
        {
            res.json({ success: false, message: "Transaction failed.", data: null });
            return;
        }

        if (value === null)
        {
            res.json({ success: false, message: "Post and thread combination does not exist.", data: null });
            return;
        }

        value.data.CreatorName = "[removed]";
        value.data.CreatorFooter = "";
        value.data.Contents = "[removed]";
        transaction.save(value);
        transaction.commit(function (err) {
            if (err)
            {
                res.json({ success: false, message: "Transaction failed.", data: null });
            }
            else
            {
                res.json({ success: true, message: "Erased post information from table", data: null });
            }
        });
    });
});

回答1:

What you experience is called "eventual consistency", and it is an important part of Cloud Datastore architecture. Without it the Datastore would be much slower for all requests.

Note that all get operations are always consistent - only queries are affected as it takes time to update all indexes. Updates to indexes may take up to a few seconds.

There are several strategies to work with eventual consistency, but they are not really applicable/necessary in your use case, because the updated data is already available to your client app. It was your client app that initiated the save request. In almost all situations it means that you can avoid reloading the data.

For example, if an app displays a list of 17 records and a user added a new record, you can simply add the new record object to the displayed list after the backend responds to the save request. Such response may include data that is missing from the record being saved, e.g. its datastore ID. In some situations it may be easier to return the entire saved object if it has many properties that have been updated on the server side.

In a very rare case when saving an object requires the client to load a totally new set of data, which may include the updated object, the client may still replace the object returned in a query with an updated object - or add it if it is missing. Again, by the time the query results arrive you already have a complete updated object from your "save object" response.