L'API Async Datastore permet d'effectuer des appels parallèles non bloquants vers le datastore et de récupérer les résultats de ces appels à un stade ultérieur du traitement de la requête. Ce document décrit plusieurs aspects de l'API Async Datastore.
Utilisation de l'API Async Datastore
L'API Async Datastore permet d'appeler le datastore à l'aide des méthodes de l'interface AsyncDatastoreService Vous obtenez cet objet en appelant la méthode de classe getAsyncDatastoreService() de la classe DatastoreServiceFactory.
import com.google.appengine.api.datastore.AsyncDatastoreService; import com.google.appengine.api.datastore.DatastoreServiceFactory; // ... AsyncDatastoreService datastore = DatastoreServiceFactory.getAsyncDatastoreService();
AsyncDatastoreService
est compatible avec les mêmes opérations que DatastoreService, excepté que la plupart des méthodes renvoient immédiatement un objet Future dont vous pouvez bloquer le résultat ultérieurement. Par exemple, DatastoreService.get() renvoie un objet Entity, tandis que AsyncDatastoreService.get() renvoie un objet 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();
Remarque : Les exceptions ne sont pas levées tant que la méthode get() n'est pas appelée. L'appel de cette méthode vous permet de vérifier que l'opération asynchrone a réussi.
Si vous avez une méthode AsyncDatastoreService
et que vous devez exécuter une opération de manière synchrone, appelez la méthode AsyncDatastoreService
appropriée, puis bloquez immédiatement le résultat :
// ... Entity entity = new Employee("Employee", "Alfred"); // ... populate entity properties // Make a sync call via the async interface Key key = datastore.put(key).get();
Utiliser des transactions asynchrones
Les appels vers l'API Async Datastore peuvent être effectués dans des transactions, de la même façon que les appels synchrones. Voici une fonction qui ajuste le salaire d'un Employee
et écrit une entité supplémentaire SalaryAdjustment
dans le même groupe d'entités qu'Employee
, le tout dans une même transaction.
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 }
Cet exemple illustre une différence essentielle entre les appels asynchrones avec et sans transactions. Lorsque vous n'utilisez pas de transaction, le seul moyen de vous assurer qu'un appel asynchrone individuel est terminé consiste à extraire la valeur de Future
qui a été renvoyée lors de l'appel. Lorsque vous utilisez une transaction, l'appel de Transaction.commit() bloque le résultat de tous les appels asynchrones effectués depuis le début de la transaction avant sa validation.
Ainsi, dans notre exemple ci-dessus, même si notre appel asynchrone visant à insérer l'entité SalaryAdjustment
peut toujours être en suspens lorsque nous appelons commit()
, la validation ne se produira pas tant que l'insertion n'est pas terminée. De même, le fait d'appeler commitAsync()
au lieu de commit()
entraîne le blocage de l'appel get()
sur l'objet Future
renvoyé par commitAsync()
jusqu'à ce que tous les appels asynchrones soient terminés.
Remarque : Les transactions sont associées à un thread spécifique et non à une instance spécifique de DatastoreService
ou AsyncDatastoreService
. En d'autres termes, si vous lancez une transaction avec un service DatastoreService
et effectuez un appel asynchrone avec un service AsyncDatastoreService
, cet appel asynchrone participe à la transaction. Ou, pour résumer, DatastoreService.getCurrentTransaction()
et AsyncDatastoreService.getCurrentTransaction()
renvoient toujours la même Transaction
.
Utiliser des objets Future
Future Javadoc explique la plupart des choses que vous devez savoir pour utiliser avec succès un objet Future
renvoyé par l'API Async Datastore. Toutefois, vous devez prendre en compte quelques éléments spécifiques à App Engine :
- Lorsque vous appelez Future.get(long timeout, TimeUnit unit), le délai d'expiration est distinct de tout RPC défini lors de la création du service
AsyncDatastoreService
. Pour plus d'informations, consultez la page Cohérence des données dans les requêtes Cloud Datastore. - Lorsque vous appelez Future.cancel(boolean mayInterruptIfRunning) et que cet appel renvoie
true
, cela ne signifie pas nécessairement que l'état de votre datastore reste inchangé. En d'autres termes, le fait d'annuler un objetFuture
ne revient pas à effectuer le rollback d'une transaction.
Requêtes asynchrones
Pour le moment, nous ne proposons pas d'API explicitement asynchrone pour les requêtes. Toutefois, lorsque vous appelez PreparedQuery.asIterable(), PreparedQuery.asIterator() ou PreparedQuery.asList(FetchOptions fetchOptions), DatastoreService et AsyncDatastoreService renvoient immédiatement et récupèrent de manière asynchrone les résultats. Cela permet à votre application d'exécuter des tâches en parallèle pendant la récupération des résultats de la requête.
// ... 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);
Utilisation des appels vers l'API Async Datastore
Les opérations exposées par l'interface DatastoreService sont synchrones. Par exemple, lorsque vous appelez DatastoreService.get()
, votre code est bloqué jusqu'à la fin de l'appel du datastore. Si la seule opération requise de la part de votre application consiste à restituer le résultat de la méthode get()
au format HTML, le blocage du code jusqu'à l'aboutissement de l'appel se révèle parfaitement judicieux. Cependant, si votre application a besoin du résultat de get()
et du résultat d'une Query
pour restituer la réponse, et si get()
et la Query
ne comportent aucune dépendance de données, le fait d'attendre que get()
termine pour initier la Query
est une perte de temps. Voici un exemple de code susceptible d'être amélioré par l'utilisation de l'API asynchrone :
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);
Au lieu d'attendre la fin de l'opération get()
, utilisez l'instance AsyncDatastoreService pour exécuter l'appel de manière asynchrone comme suit :
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);
Bien que les versions synchrones et asynchrones de ce code exploitent autant le processeur l'une que l'autre (puisqu'elles effectuent le même volume de travail), la version asynchrone se caractérise par un temps de latence inférieur, car elle permet aux deux opérations du datastore de s'exécuter en parallèle. En général, si vous devez exécuter plusieurs opérations de datastore ne dépendant pas de données, AsyncDatastoreService
peut considérablement améliorer la latence.