GAE: datastore transactions
What is a transaction?
performing a set of
multiple operations, possibly over multiple units of data
TansactionSuccess = all of the operations in the transaction are executed completely
Tansaction Failure = means none of its operations are applied to the data
Example from Book fo a transaction
-
App = Message board where post messages and the total number of current messages
-
Transaction = (operation 1 post a new message ) + (operation 2 increase total count)
-
WHY do as a transaction = you wouldn't want operation 1 to succeed and not have operation 2 complete.
-
JAVA:
-
start of transaction = datastore_object.beginTransaction()
-
end of transaction = datastore_object.commit();
DatastoreService ds = DatastoreServiceFactory.getDatastoreService();
Key boardKey;
Entity messageBoard;
try {
Transaction txn = ds.beginTransaction(); //START OF TRANSACTION
try {
boardKey = KeyFactory.createKey("MessageBoard", boardName);
messageBoard = ds.get(boardKey);
} catch (EntityNotFoundException e) {
messageBoard = new Entity("MessageBoard", boardName);
messageBoard.setProperty("count", 0);
boardKey = ds.put(messageBoard);
}
txn.commit(); //END OF TRASANCTION
} catch (DatastoreFailureException e) {
// Report an error.. }
NOTE: If you do not commit the transaction, the transaction is rolled back automatically after
the servlet exits, and changes are not applied. You can roll back the transaction explicitly
by calling the rollback() method of the Transaction object.
Strong Consistency verus Eventual Consistency
-
STRONG Consistency = The promise tha all processes can see the changes once a transaction is
complete is known as strong consistency.
-
EVENTUAL Consistency = does not promise strong consistency --- greater flexibility in how changes are applied, but this makes life difficult
for an app that needs to present the user with a consistent view of the world with
each request.
GAE: Strong Consistency with limited transaction scope
limiting scope = a single transaction can only
read or write to entities that belong to a single entity group.
Every entity belongs to an
entity group, by default a group of its own.
The app assigns an entity to a group when
the entity is created, and the assignment is permanent.
limiting scope makes faster =
App arranges entities into groups, the datastore can treat each group independently
when applying concurrent transactions.
Two transactions that use different
groups can occur simultaneously without harm.
A PROGRAMMERS GOAL = an app can
ensure that entities are arranged to minimize the likelihood that two processes will need
to access the same group, and thereby maximize throughput !!!!
Write your Code to Handle the Failure of Your transaction
-
May want appropriate for the application to try
the transaction again in the event of a concurrency failure.
-
The following example tries up to 3 times before reporting error
import com.google.appengine.api.datastore.DatastoreFailureException;
import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.EntityNotFoundException;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.datastore.PreparedQuery;
import com.google.appengine.api.datastore.Query;
import com.google.appengine.api.datastore.Transaction;
// ...
DatastoreService ds = DatastoreServiceFactory.getDatastoreService();
int retries = 3;
boolean success = false;
while (!success && retries > 0) {
--retries;
try {
Transaction txn = ds.beginTransaction();
Key boardKey;
Entity messageBoard;
try {
boardKey = KeyFactory.createKey("MessageBoard", boardName);
messageBoard = ds.get(boardKey);
} catch (EntityNotFoundException e) {
messageBoard = new Entity("MessageBoard", boardName);
messageBoard.setProperty("count", 0);
boardKey = ds.put(messageBoard);
}
Entity message = new Entity("Message", boardKey);
message.setProperty("message_title", messageTitle);
message.setProperty("message_text", messageText);
message.setProperty("post_date", postDate);
ds.put(message);
long count = (Long) messageBoard.getProperty("count");
++count;
messageBoard.setProperty("count", count);
ds.put(messageBoard);
log.info("Posting msg, updating count to " + count);
txn.commit();
// Break out of retry loop.
success = true;
} catch (DatastoreFailureException e) {
// Allow retry to occur.
}
}
if (!success) {
// Tell the user it didn't work out...
resp.getWriter().println("<p>A new message could not be posted. Try again later.</p>");
}
// ...
Key boardKey = KeyFactory.createKey("MessageBoard", boardName);
try {
Entity messageBoard = ds.get(boardKey);
long count = (Long) messageBoard.getProperty("count");
resp.getWriter().println("<p>Latest messages posted to " + boardName + " (" + count + " total):</p>");
Query q = new Query("Message", boardKey);
PreparedQuery pq = ds.prepare(q);
for (Entity result : pq.asIterable()) {
resp.getWriter().println("<h3>" + result.getProperty("message_title")
+ "</h3><p>"
+ result.getProperty("message_text")
+ "</p>");
}
} catch (EntityNotFoundException e) {
resp.getWriter().println("<p>No message board found.</p>");
}
How
to create entities in entity groups
New Entity associate with another' Entity's Group = you associate it with the key of another
entity from that group.
parent = this other entity is the new entity's parent
root = the last entity is the backwards trace path related entities
group = consists of all entities sharing the same root entity
Entity first_root_entity = new Entity("Kind_Name");
String firt_root_entity_Key = first_root_entity.getKey(); //get key of this entity
ds.put(first_root_entity); // ds is the current DataStore instance, will be the root of a NEW GROUP
/* NOTE: When you create an entity and do not specify a parent, the entity is created in a new
group by itself. The new entity is the root of the new group.
*/
Entity e = new Entity("Kind_Name",
first_root_entity_Key); //second attribute is parent key --makes same group
ds.put(e);
How to create a Parent Key for Entity Grouping
Do nothing---create Entity you want to use as parent as you would nomally (see example above) and grab its key
Entity first_root_entity = new Entity("Kind_Name");
String firt_root_entity_Key = first_root_entity.getKey(); //get key of this entity
ds.put(first_root_entity); // ds is the current DataStore instance, will be the root of a NEW GROUP
/* NOTE: When you create an entity and do not specify a parent, the entity is created in a new
group by itself. The new entity is the root of the new group.
*/
Create a Key that you use as the partent key using KeyFactory class
Key parentKey = KeyFactory.createKey("MessageBoard", "The_Archonville_Times");
Entity message = new Entity("Message", parentKey);
Create a Key that you use as the partent key using KeyFactory.Builder class
Key k = new Builder("MessageBoard", "The_Archonville_times").addChild("Message", 1427).getKey();
What happens when you update an entity group?
How to perform multiple operations on an entity
group using a transaction
GAE Consistency guarantees
Anscestor Queries
Possible to make queries that only search an given entity's anscestors.
WHY? --Example suppose you had a messaging bulletin board and you make the parent of each new class the last message posted:
Entity new_e = new Entity("kind", last_posted_message_key);
ds.put(new_e); //ds is current DataStore
Key last_posted_message_key = new_e.getKey(); //update last posted message key to just posted message
Query q = new Query("MessageBoard");
q.addFilter("posterName", Query.FilterOperator.EQUAL, posterNameParam);
q.setAnscestor(last_posted_message_key);
List<Entity> last_ten_posting_for_particular_user = ds.prepare(q).asList(FetchOptions.Builder.withLimit(5));
|