Structurer des données pour renforcer la cohérence
Restez organisé à l'aide des collections
Enregistrez et classez les contenus selon vos préférences.
Pour garantir la haute disponibilité, l'évolutivité et la durabilité, Datastore distribue les données sur de nombreuses machines et met en œuvre une réplication synchrone sur une vaste zone géographique. Cette conception implique toutefois une concession : le débit en écriture d'un seul groupe d'entités donné est limité à environ un commit par seconde, et il existe des restrictions en termes de requêtes ou de transactions couvrant plusieurs groupes d'entités. Cette page décrit plus en détail ces limites et indique les bonnes pratiques à suivre pour structurer les données, afin de garantir une cohérence forte tout en respectant les exigences de débit en écriture de l'application.
Les lectures fortement cohérentes renvoient toujours les données actuelles et, si elles sont effectuées dans une transaction, elles semblent provenir d'un seul instantané cohérent. Toutefois, les requêtes doivent spécifier un filtre d'ancêtre afin d'être fortement cohérentes ou de participer à une transaction, et les transactions peuvent impliquer 25 groupes d'entités au maximum. Les lectures cohérentes à terme ne sont pas soumises à ces limites et conviennent dans de nombreux cas. Avec des lectures cohérentes à terme, vous pouvez répartir les données entre un plus grand nombre de groupes d'entités, ce qui vous permet d'obtenir un meilleur débit en écriture en exécutant des commits en parallèle sur les différents groupes d'entités. Cependant, vous devez connaître les caractéristiques des lectures cohérentes à terme afin de déterminer si elles sont adaptées à votre application :
- Les résultats de ces lectures peuvent ne pas refléter les dernières transactions. Cela peut se produire, car ces lectures ne garantissent pas que l'instance dupliquée sur laquelle elles s'exécutent est à jour. À la place, elles utilisent les données disponibles sur cette instance dupliquée au moment de l'exécution de la requête, quelles qu'elles soient. La latence de réplication est presque toujours inférieure à quelques secondes.
- Une transaction ayant fait l'objet d'un commit et qui couvre plusieurs entités peut sembler avoir été appliquée à certaines entités, et non à d'autres. Sachez cependant qu'une transaction ne semblera jamais avoir été partiellement appliquée au sein d'une seule entité.
- Les résultats de la requête peuvent inclure des entités qui n'auraient pas dû l'être selon les critères de filtrage, et peuvent exclure des entités qui auraient dû être incluses. Cela peut se produire, car les index peuvent être lus avec une version différente de celle employée pour lire l'entité elle-même.
Pour comprendre comment structurer les données de façon à assurer une cohérence forte, nous allons comparer deux approches différentes pour une application simple de livre d'or. La première approche consiste à créer une entité racine dans chaque entité créée :
Une requête est ensuite exécutée sur le genre d'entité Greeting
pour obtenir les 10 messages d'accueil les plus récents.
Toutefois, étant donné que vous utilisez une requête non ascendante, l'instance dupliquée permettant d'exécuter la requête dans ce schéma n'a peut-être pas vu le nouveau message d'accueil au moment de l'exécution de la requête. Néanmoins, presque toutes les écritures sont disponibles pour les requêtes non ascendantes quelques secondes après le commit. Pour de nombreuses applications, une solution fournissant les résultats d'une requête non ascendante dans le contexte des modifications de l'utilisateur actuel est généralement suffisante pour rendre de telles latences de réplication parfaitement acceptables.
Si une cohérence forte est importante pour votre application, une autre approche consiste à écrire des entités avec un chemin d'ancêtre qui identifie la même entité racine dans toutes les entités devant être lues dans une seule requête ascendante fortement cohérente :
Vous pouvez ensuite exécuter une requête ascendante fortement cohérente au sein du groupe d'entités identifié par l'entité racine commune :
Cette approche assure une cohérence forte en effectuant les opérations d'écriture dans un seul groupe d'entités par livre d'or, mais elle limite également les modifications apportées à ce dernier à une écriture par seconde (limite acceptée pour les groupes d'entités). Si le nombre d'écritures est susceptible d'être plus élevé dans votre application, vous devrez peut-être envisager de recourir à d'autres moyens. Par exemple, vous pouvez placer les posts récents dans Memcache avec une date d'expiration, et afficher une combinaison de posts récents issus de Memcache et de Datastore. Vous avez également la possibilité de les mettre en cache dans un cookie, d'indiquer un état dans l'URL, ou tout autre chose encore. L'objectif est de trouver une solution de mise en cache fournissant les données de l'utilisateur actuel pour la période pendant laquelle celui-ci publie sur l'application. N'oubliez pas que si vous effectuez une opération "get", une requête ascendante ou toute opération dans une transaction, vous verrez toujours les dernières données écrites.
Sauf indication contraire, le contenu de cette page est régi par une licence Creative Commons Attribution 4.0, et les échantillons de code sont régis par une licence Apache 2.0. Pour en savoir plus, consultez les Règles du site Google Developers. Java est une marque déposée d'Oracle et/ou de ses sociétés affiliées.
Dernière mise à jour le 2025/09/04 (UTC).
[[["Facile à comprendre","easyToUnderstand","thumb-up"],["J'ai pu résoudre mon problème","solvedMyProblem","thumb-up"],["Autre","otherUp","thumb-up"]],[["Difficile à comprendre","hardToUnderstand","thumb-down"],["Informations ou exemple de code incorrects","incorrectInformationOrSampleCode","thumb-down"],["Il n'y a pas l'information/les exemples dont j'ai besoin","missingTheInformationSamplesINeed","thumb-down"],["Problème de traduction","translationIssue","thumb-down"],["Autre","otherDown","thumb-down"]],["Dernière mise à jour le 2025/09/04 (UTC)."],[[["\u003cp\u003eThis API is compatible with first-generation runtimes and can be used when upgrading to the corresponding second-generation runtimes, with a separate migration guide available for those updating to App Engine Java 11/17.\u003c/p\u003e\n"],["\u003cp\u003eDatastore ensures high availability, scalability, and durability through data distribution and synchronous replication, but write throughput is limited to one commit per second for a single entity group, with restrictions on queries or transactions spanning multiple groups.\u003c/p\u003e\n"],["\u003cp\u003eStrongly-consistent reads provide current data and a consistent snapshot within transactions, requiring an ancestor filter for queries and limiting transactions to 25 entity groups, while eventually-consistent reads offer more flexibility but may not reflect the latest transactions.\u003c/p\u003e\n"],["\u003cp\u003eFor applications needing strong consistency, structuring data with a common ancestor path enables strongly-consistent ancestor queries, although this limits writes to one per second per entity group.\u003c/p\u003e\n"],["\u003cp\u003eFor applications with high write usage, consider using alternative solutions such as memcache or cookies to cache recent posts and display a mix of recent data alongside Datastore results.\u003c/p\u003e\n"]]],[],null,["# Structuring Data for Strong Consistency\n\n| This API is supported for first-generation runtimes and can be used when [upgrading to corresponding second-generation runtimes](/appengine/docs/standard/\n| java-gen2\n|\n| /services/access). If you are updating to the App Engine Java 11/17 runtime, refer to the [migration guide](/appengine/migration-center/standard/migrate-to-second-gen/java-differences) to learn about your migration options for legacy bundled services.\n\nDatastore provides high availability, scalability and durability by\ndistributing data over many machines and using synchronous\nreplication over a wide geographic area. However, there is a tradeoff in this\ndesign, which is that the write throughput for any single\n[*entity group*](/appengine/docs/legacy/standard/java/datastore/entities#Ancestor_paths) is limited to about\none commit per second, and there are limitations on queries or transactions that\nspan multiple entity groups. This page describes these limitations in more\ndetail and discusses best practices for structuring your data to support strong\nconsistency while still meeting your application's write throughput\nrequirements.\n\nStrongly-consistent reads always return current data, and, if performed within a\ntransaction, will appear to come from a single, consistent snapshot. However,\nqueries must specify an ancestor filter in order to be strongly-consistent or\nparticipate in a transaction, and transactions can involve at most 25 entity\ngroups. Eventually-consistent reads do not have those limitations, and are\nadequate in many cases. Using eventually-consistent reads can allow you to\ndistribute your data among a larger number of entity groups, enabling you to\nobtain greater write throughput by executing commits in parallel on the\ndifferent entity groups. But, you need to understand the characteristics of\neventually-consistent reads in order to determine whether they are suitable for\nyour application:\n\n- The results from these reads might not reflect the latest transactions. This can occur because these reads do not ensure that the replica they are running on is up-to-date. Instead, they use whatever data is available on that replica at the time of query execution. Replication latency is almost always less than a few seconds.\n- A committed transaction that spanned multiple entities might appear to have been applied to some of the entities and not others. Note, though, that a transaction will never appear to have been partially applied within a single entity.\n- The query results can include entities that should not have been included according to the filter criteria, and might exclude entities that should have been included. This can occur because indexes might be read at a different version than the entity itself is read at.\n\nTo understand how to structure your data for strong consistency, compare two\ndifferent approaches for a simple guestbook application. The first approach\ncreates a new root entity for each entity that is created: \n\n protected Entity createGreeting(\n DatastoreService datastore, User user, Date date, String content) {\n // No parent key specified, so Greeting is a root entity.\n Entity greeting = new Entity(\"Greeting\");\n greeting.setProperty(\"user\", user);\n greeting.setProperty(\"date\", date);\n greeting.setProperty(\"content\", content);\n\n datastore.put(greeting);\n return greeting;\n }\n\nIt then queries on the entity kind `Greeting` for the ten most recent greetings. \n\n protected List\u003cEntity\u003e listGreetingEntities(DatastoreService datastore) {\n Query query = new Query(\"Greeting\").addSort(\"date\", Query.SortDirection.DESCENDING);\n return datastore.prepare(query).asList(FetchOptions.Builder.withLimit(10));\n }\n\nHowever, because you are using a non-ancestor query, the replica used to perform\nthe query in this scheme might not have seen the new greeting by the time the\nquery is executed. Nonetheless, nearly all writes will be available for\nnon-ancestor queries within a few seconds of commit. For many applications, a\nsolution that provides the results of a non-ancestor query in the context of the\ncurrent user's own changes will usually be sufficient to make such replication\nlatencies completely acceptable.\n\nIf strong consistency is important to your application, an alternate approach is\nto write entities with an ancestor path that identifies the same root entity\nacross all entities that must be read in a single, strongly-consistent ancestor\nquery: \n\n protected Entity createGreeting(\n DatastoreService datastore, User user, Date date, String content) {\n // String guestbookName = \"my guestbook\"; -- Set elsewhere (injected to the constructor).\n Key guestbookKey = KeyFactory.createKey(\"Guestbook\", guestbookName);\n\n // Place greeting in the same entity group as guestbook.\n Entity greeting = new Entity(\"Greeting\", guestbookKey);\n greeting.setProperty(\"user\", user);\n greeting.setProperty(\"date\", date);\n greeting.setProperty(\"content\", content);\n\n datastore.put(greeting);\n return greeting;\n }\n\nYou will then be able to perform a strongly-consistent ancestor query within the\nentity group identified by the common root entity: \n\n protected List\u003cEntity\u003e listGreetingEntities(DatastoreService datastore) {\n Key guestbookKey = KeyFactory.createKey(\"Guestbook\", guestbookName);\n Query query =\n new Query(\"Greeting\", guestbookKey)\n .setAncestor(guestbookKey)\n .addSort(\"date\", Query.SortDirection.DESCENDING);\n return datastore.prepare(query).asList(FetchOptions.Builder.withLimit(10));\n }\n\nThis approach achieves strong consistency by writing to a single entity group\nper guestbook, but it also limits changes to the guestbook to no more than\n1 write per second (the supported limit for entity groups). If your application\nis likely to encounter heavier write usage, you might need to consider using\nother means: for example, you might put recent posts in a\n[memcache](/appengine/docs/legacy/standard/java/memcache) with an expiration\nand display a mix of recent posts from the memcache and\nDatastore, or you might cache them in a cookie, put some state\nin the URL, or something else entirely. The goal is to find a caching solution\nthat provides the data for the current user for the period of time in which the\nuser is posting to your application. Remember, if you do a get, an ancestor\nquery, or any operation within a transaction, you will always see the most\nrecently written data."]]