In class we have studied the concepts of server scaling and service switching, and how they relate to AppEngine development caveats. Let's explore a bit more about these concepts.
client -------------------------------------------------------- \ / service ---==------===------==--------------------------------- \read/ \read/ cloud --------------------------------------------------------where "=" represents the time when the server is actually processing data as opposed to waiting for the cloud.
client -------------------------------------------------------- \ / service ---===================--------------------------------- |write cloud --------------------------------------------------------where "=" represents the time when the server is actually processing data.
class mailbox { private Date whenGloballyConsistent; private int token; public Date getDate(); public void setDate(Date d); public int getToken(); public void setToken(int t); };
To write something into the mailbox, we might use:
mailbox m; success=false; while (!success) { pm.begin(); if ((m.getDate()==null || m.getDate() < new Date()) && m.getToken()==0) { // if consistent and empty m.setDate(new Date()+t0); // date arithmetic: time when it will be consistent m.setToken(t); // token is t!=0 pm.commit(); success=true; } else { // not consistent or not empty pm.abort(); } }In other words, to write a token, wait until the mailbox is empty and strongly consistent, and then write the token into it.
To read something from the mailbox, we might use:
mailbox m; success=false; while (!success) { pm.begin(); if (m.getDate()!=null && m.getToken()!=0 && m.getDate() < new Date()) { // if consistent and nonempty t = m.getToken(); m.setDate(new Date()+t0); // when this will be consistent m.setToken(0); // empty pm.commit(); success=true; } else { // not consistent or not nonempty pm.abort(); } }In other words, wait until the mailbox is full and strongly consistent, and then read the value and erase the token. The point of this mailbox is that the first transaction will only succeed when the mailbox is empty and consistent, and will switch it from empty to nonempty. Likewise, the second will only succeed if it is full and consistent, and each changes the state of the mailbox from empty to full and vice-versa. This is -- in essence -- a semaphore for communication of consistency between service instances!
The way this is used in the application is that if we have one object that is consistent, we can make any other object consistent with it by sharing a value between the objects and waiting for the shared values to agree. Suppose, for example, that we are concerned that references to a specific object should exhibit strong consistency.
The reason I used a mailbox, and not the object itself, to measure consistency, is that I needed an object limited to simple transactions. In general, I cannot get away with assuming global consistency after time t0 unless I keep my manipulations of the mailbox simple!
If the datastore does not exhibit some form of transactional integrity, then all bets are off. One cannot even assure that the version of mailbox data read by a later service request is even complete, much less consistent. One potential avenue is to include a checksum or hash field in the distributed object that can be checked for validity and assure transactional integrity of the mailbox message, e.g. But this is an imperfect strategy, in the sense that there will be object states that verify against the hash and are not consistent!
This is just one way to try to synthesize strong consistency in a weak consistency environment. All of them require more information to work.