El API de servicio asíncrono del almacén de datos permite realizar varias invocaciones a la vez, sin que se bloqueen entre sí, al almacén de datos y obtener los resultados de dichas invocaciones más adelante, cuando se procese la solicitud. En este documento se describen los siguientes aspectos del API de servicio asíncrono del almacén de datos:
Cómo utilizar el servicio asíncrono del almacén de datos
Con la API Async Datastore, puedes hacer llamadas a Datastore mediante métodos de la interfaz AsyncDatastoreService. Para obtener este objeto, llama al método de clase getAsyncDatastoreService() de la clase DatastoreServiceFactory.
import com.google.appengine.api.datastore.AsyncDatastoreService; import com.google.appengine.api.datastore.DatastoreServiceFactory; // ... AsyncDatastoreService datastore = DatastoreServiceFactory.getAsyncDatastoreService();
AsyncDatastoreService
admite las mismas operaciones que DatastoreService, excepto que la mayoría de los métodos devuelven inmediatamente un Future cuyo resultado puedes bloquear en algún momento posterior. Por ejemplo, DatastoreService.get() devuelve un Entity, pero AsyncDatastoreService.get() devuelve un Future<Entity>
.
// ... Key key = KeyFactory.createKey("Employee", "Max"); // Async call returns immediately Future<Entity> entityFuture = datastore.get(key); // Do other stuff while the get operation runs in the background... // Blocks if the get operation has not finished, otherwise returns instantly Entity entity = entityFuture.get();
Nota: Las excepciones no se generan hasta que llamas al método get(). La invocación de este método te permite comprobar si la operación asíncrona se ha realizado correctamente.
Si tienes un AsyncDatastoreService
, pero necesitas ejecutar una operación de forma síncrona, invoca el método AsyncDatastoreService
adecuado y, a continuación, bloquea inmediatamente el resultado:
// ... Entity entity = new Employee("Employee", "Alfred"); // ... populate entity properties // Make a sync call via the async interface Key key = datastore.put(key).get();
Cómo utilizar transacciones asíncronas
Las invocaciones del API de servicio asíncrono del almacén de datos pueden participar en las transacciones, al igual que las invocaciones sincronizadas. Aquí tienes una función que ajusta el salario de un Employee
y escribe una entidad SalaryAdjustment
adicional en el mismo grupo de entidades que el Employee
, todo ello en una sola transacción.
void giveRaise(AsyncDatastoreService datastore, Key employeeKey, long raiseAmount) throws Exception { Future<Transaction> txn = datastore.beginTransaction(); // Async call to lookup the Employee entity Future<Entity> employeeEntityFuture = datastore.get(employeeKey); // Create and put a SalaryAdjustment entity in parallel with the lookup Entity adjustmentEntity = new Entity("SalaryAdjustment", employeeKey); adjustmentEntity.setProperty("adjustment", raiseAmount); adjustmentEntity.setProperty("adjustmentDate", new Date()); datastore.put(adjustmentEntity); // Fetch the result of our lookup to make the salary adjustment Entity employeeEntity = employeeEntityFuture.get(); long salary = (Long) employeeEntity.getProperty("salary"); employeeEntity.setProperty("salary", salary + raiseAmount); // Re-put the Employee entity with the adjusted salary. datastore.put(employeeEntity); txn.get().commit(); // could also call txn.get().commitAsync() here }
En este ejemplo se muestra una diferencia importante entre las invocaciones asíncronas sin transacciones y aquellas con transacciones. Cuando no usas una transacción, la única forma de asegurarte de que una llamada asíncrona individual se ha completado es obtener el valor devuelto de Future
que se devolvió cuando se hizo la llamada. Cuando usas una transacción, al llamar a Transaction.commit() se bloquea el resultado de todas las llamadas asíncronas realizadas desde que se inició la transacción antes de confirmarla.
Por lo tanto, en el ejemplo anterior, aunque nuestra llamada asíncrona para insertar la entidad SalaryAdjustment
aún esté pendiente cuando llamemos a commit()
, la confirmación no se producirá hasta que se complete la inserción. Del mismo modo, si decides llamar a commitAsync()
en lugar de a commit()
, al invocar get()
en el Future
devuelto por commitAsync()
, se bloqueará hasta que se hayan completado todas las llamadas asíncronas pendientes.
Nota: Las transacciones están asociadas a un hilo específico, no a una instancia específica de DatastoreService
o AsyncDatastoreService
. Esto significa que, si inicias una transacción con un DatastoreService
y realizas una llamada asíncrona con un AsyncDatastoreService
, la llamada asíncrona participa en la transacción. O, dicho de forma más concisa, DatastoreService.getCurrentTransaction()
y AsyncDatastoreService.getCurrentTransaction()
siempre devuelven el mismo Transaction
.
Cómo utilizar el método Future
En el Javadoc de Future se explica la mayor parte de lo que necesitas saber para trabajar correctamente con un Future
devuelto por la API Async Datastore, pero hay algunas cosas específicas de App Engine que debes tener en cuenta:
- Cuando llamas a Future.get(long timeout, TimeUnit unit), el tiempo de espera es independiente de cualquier plazo de RPC establecido al crear el
AsyncDatastoreService
. Para obtener más información, consulta Coherencia de datos en las consultas de Cloud Datastore. - Cuando llamas a Future.cancel(boolean mayInterruptIfRunning) y esa llamada devuelve
true
, no significa necesariamente que el estado de tu almacén de datos no haya cambiado. Es decir, cancelar unaFuture
no es lo mismo que revertir una transacción.
Consultas asíncronas
Actualmente no disponemos de un API de servicio asíncrono específica para las consultas. Sin embargo, cuando invocas PreparedQuery.asIterable(), PreparedQuery.asIterator() o PreparedQuery.asList(FetchOptions fetchOptions), tanto DatastoreService como AsyncDatastoreService devuelven inmediatamente y preobtiene resultados de forma asíncrona. Esto permite que tu aplicación realice trabajos en paralelo mientras se extraen los resultados de la consulta.
// ... Query q1 = new Query("Salesperson"); q1.setFilter(new FilterPredicate("dateOfHire", FilterOperator.LESS_THAN, oneMonthAgo)); // Returns instantly, query is executing in the background. Iterable<Entity> recentHires = datastore.prepare(q1).asIterable(); Query q2 = new Query("Customer"); q2.setFilter(new FilterPredicate("lastContact", FilterOperator.GREATER_THAN, oneYearAgo)); // Also returns instantly, query is executing in the background. Iterable<Entity> needsFollowup = datastore.prepare(q2).asIterable(); schedulePhoneCall(recentHires, needsFollowUp);
Cuándo realizar invocaciones del almacén de datos usando el servicio asíncrono
Las operaciones expuestas por la interfaz DatastoreService son síncronas. Por ejemplo, cuando llamas a DatastoreService.get()
, tu código se bloquea hasta que se completa la llamada al almacén de datos. Si lo único que tiene que hacer tu aplicación es renderizar el resultado de get()
en HTML, bloquear hasta que se complete la llamada es una acción perfectamente razonable. Sin embargo, si tu aplicación necesita el resultado de get()
más el resultado de Query
para renderizar la respuesta, y si get()
y Query
no tienen ninguna dependencia de datos, esperar a que se complete get()
para iniciar Query
es una pérdida de tiempo. A continuación, incluimos un ejemplo con código que puede mejorarse mediante el API de servicio asíncrono:
DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); Key empKey = KeyFactory.createKey("Employee", "Max"); // Read employee data from the Datastore Entity employee = datastore.get(empKey); // Blocking for no good reason! // Fetch payment history Query query = new Query("PaymentHistory"); PreparedQuery pq = datastore.prepare(query); List<Entity> result = pq.asList(FetchOptions.Builder.withLimit(10)); renderHtml(employee, result);
En lugar de esperar a que se complete get()
, usa una instancia de AsyncDatastoreService para ejecutar la llamada de forma asíncrona:
AsyncDatastoreService datastore = DatastoreServiceFactory.getAsyncDatastoreService(); Key empKey = KeyFactory.createKey("Employee", "Max"); // Read employee data from the Datastore Future<Entity> employeeFuture = datastore.get(empKey); // Returns immediately! // Fetch payment history for the employee Query query = new Query("PaymentHistory", empKey); PreparedQuery pq = datastore.prepare(query); // Run the query while the employee is being fetched List<Entity> result = pq.asList(FetchOptions.Builder.withLimit(10)); // Implicitly performs query asynchronously Entity employee = employeeFuture.get(); // Blocking! renderHtml(employee, result);
Las versiones síncronas y asíncronas de este código usan una cantidad de CPU similar (después de todo, ambas llevan a cabo la misma cantidad de trabajo). Sin embargo, dado que la versión asíncrona permite que se ejecuten las dos operaciones del almacén de datos al mismo tiempo, la versión asíncrona tiene una latencia menor. En general, si necesitas realizar varias operaciones de Datastore que no tengan dependencias de datos, AsyncDatastoreService
puede mejorar significativamente la latencia.