Daten für strikte Konsistenz strukturieren
Mit Sammlungen den Überblick behalten
Sie können Inhalte basierend auf Ihren Einstellungen speichern und kategorisieren.
Datastore bietet hohe Verfügbarkeit, Skalierbarkeit und Langlebigkeit, da die Daten auf viele Rechner verteilt werden und eine synchrone Replikation für einen großen geografischen Bereich verwendet wird. Bei diesem Design müssen jedoch Einschränkungen in Kauf genommen werden. Der Schreibdurchsatz für eine einzelne Entitätengruppe ist auf etwa einen Commit-Vorgang pro Sekunde begrenzt. Weitere Einschränkungen bestehen bei Abfragen oder Transaktionen, die mehrere Entitätengruppen umfassen. Auf dieser Seite erfahren Sie mehr zu den einzelnen Einschränkungen. Außerdem werden Best Practices zur Strukturierung der Daten im Hinblick auf strikte Konsistenz bei gleichzeitiger Einhaltung der Anforderungen an den Schreibdurchsatz der Anwendung vorgestellt.
Strikt konsistente Lesevorgänge liefern immer aktuelle Daten. Werden sie innerhalb derselben Transaktion ausgeführt, sieht es so aus, als stammten sie von einem einzigen, konsistenten Snapshot. Abfragen müssen allerdings einen Ancestor-Filter angeben, um strikte Konsistenz zu bieten oder Bestandteil einer Transaktion zu sein. Transaktionen können höchstens 25 Entitätengruppen umfassen. Für Lesezugriffe mit Eventual Consistency gelten diese Einschränkungen nicht. Sie eignen sich für viele Anwendungsfälle. Mit Lesevorgängen mit Eventual Consistency können Sie die Daten auf eine größere Anzahl von Entitätengruppen verteilen. Durch parallele Commits für die verschiedenen Entitätengruppen erhöhen Sie den Schreibdurchsatz. Sie müssen jedoch die Eigenschaften von Lesevorgängen mit Eventual Consistency verstehen, um zu ermitteln, ob sie für Ihre Anwendung geeignet sind:
- Die Ergebnisse dieser Lesevorgänge spiegeln eventuell nicht die letzten Transaktionen wider. Dies kann geschehen, weil diese Lesezugriffe nicht gewährleisten, dass die Replikate, mit denen sie ausgeführt werden, auf dem neuesten Stand sind. Stattdessen verwenden sie die Daten, die im Replikat zum Zeitpunkt der Abfrageausführung verfügbar sind. Die Replikationslatenz liegt fast immer bei wenigen Sekunden.
- Es kann den Anschein haben, dass eine für mehrere Entitäten mit Commit durchgeführte Transaktion nur auf einen Teil der Entitäten angewendet wurde. Eine Transaktion wurde niemals scheinbar teilweise innerhalb einer einzelnen Entität angewendet.
- Die Abfrageergebnisse können Entitäten enthalten, die gemäß den Filterkriterien nicht hätten einbezogen werden sollen, und Entitäten ausschließen, die hätten einbezogen werden sollen. Dies liegt daran, dass Indexe unter Umständen in einer anderen Version als die Entität gelesen werden.
Zum besseren Verständnis, wie Daten im Hinblick auf strikte Konsistenz strukturiert werden sollen, vergleichen Sie zwei verschiedene Lösungen für eine einfache Gästebuchanwendung. In der ersten Lösung wird für jede erstellte Entität eine neue Stammentität erstellt:
Anschließend werden die zehn letzten Begrüßungen nach dem Entitätstyp Greeting
abgefragt.
Da Sie jedoch eine Nicht-Ancestor-Abfrage verwenden, wurde der neue Gruß zum Zeitpunkt der Ausführung der Abfrage möglicherweise noch nicht von dem Replikat erfasst, das für die Abfrage in diesem Schema verwendet wurde. Dennoch sind nahezu alle Schreibvorgänge für Nicht-Ancestor-Abfragen innerhalb von ein paar Sekunden nach einem Commit-Vorgang verfügbar. Bei vielen Anwendungen reicht im Allgemeinen eine Lösung, die die Ergebnisse einer Nicht-Ancestor-Abfrage im Kontext der eigenen Änderungen des aktuellen Nutzers bereitstellt, damit derartige Replikationslatenzen völlig akzeptabel sind.
Wenn es für Ihre Anwendung auf Strong Consistency ankommt, können Entitäten mit einem Ancestor-Pfad geschrieben werden, der dieselbe Stammentität für alle Entitäten ermittelt, die in einer einzigen Ancestor-Abfrage mit Strong Consistency gelesen werden müssen:
Danach können Sie eine Ancestor-Abfrage mit Strong Consistency innerhalb der Entitätengruppe ausführen, die von der gemeinsamen Stammentität identifiziert wird:
Diese Lösung erzielt strikte Konsistenz, indem in eine einzige Entitätengruppe pro Gästebuch geschrieben wird. Änderungen am Gästebuch werden jedoch auch auf maximal einen Schreibvorgang pro Sekunde beschränkt (den unterstützten Grenzwert für Entitätengruppen). Wenn für Ihre Anwendung eine höhere Schreibnutzung wahrscheinlich ist, müssen Sie möglicherweise andere Mittel verwenden: Sie können z. B. die neuesten Posts in einem memcache mit einem Ablauf speichern und eine Mischung aus den neuesten Posts aus dem Memcache und Datastore anzeigen oder Sie speichern sie in einem Cookie, setzen einen Status in der URL oder etwas anderes. Das Ziel besteht darin, eine Caching-Lösung zu finden, die dem aktuellen Nutzer die Daten in dem Zeitraum bereitstellt, in dem er Beiträge an Ihre Anwendung postet. Beachten Sie, dass Sie immer die zuletzt geschriebenen Daten sehen, wenn Sie einen get-Vorgang, eine Ancestor-Abfrage oder einen Vorgang innerhalb einer Transaktion ausführen.
Sofern nicht anders angegeben, sind die Inhalte dieser Seite unter der Creative Commons Attribution 4.0 License und Codebeispiele unter der Apache 2.0 License lizenziert. Weitere Informationen finden Sie in den Websiterichtlinien von Google Developers. Java ist eine eingetragene Marke von Oracle und/oder seinen Partnern.
Zuletzt aktualisiert: 2025-09-04 (UTC).
[[["Leicht verständlich","easyToUnderstand","thumb-up"],["Mein Problem wurde gelöst","solvedMyProblem","thumb-up"],["Sonstiges","otherUp","thumb-up"]],[["Schwer verständlich","hardToUnderstand","thumb-down"],["Informationen oder Beispielcode falsch","incorrectInformationOrSampleCode","thumb-down"],["Benötigte Informationen/Beispiele nicht gefunden","missingTheInformationSamplesINeed","thumb-down"],["Problem mit der Übersetzung","translationIssue","thumb-down"],["Sonstiges","otherDown","thumb-down"]],["Zuletzt aktualisiert: 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."]]