Best practice to query large number of ndb entities from datastore
Large processing like this should not be done in a user request, which has a 60s time limit. Instead, it should be done in a context that supports long-running requests. The task queue supports requests up to 10 minutes, and (I believe) normal memory restraints (F1 instances, the default, have 128MB of memory). For even higher limits (no request timeout, 1GB+ of memory), use backends.
Here's something to try: set up a URL that, when accessed, fires off a task queue task. It returns a web page that polls every ~5s to another URL that responds with true/false if the task queue task has been completed yet. The task queue processes the data, which can take some 10s of seconds, and saves the result to the datastore either as the computed data or a rendered web page. Once the initial page detects that it has completed, the user is redirected to the page, which fetches the now computed results from the datastore.
The new experimental Data Processing feature (an AppEngine API for MapReduce) looks very suitable for solving this problem. It does automatic sharding to execute multiple parallel worker processes.
I have a similar problem and after working with Google support for few weeks I can confirm there is no magic solution at least as of December 2017.
tl;dr: One can expect throughput from 220 entities/second for standard SDK running on B1 instance up to 900 entities/second for a patched SDK running on a B8 instance.
The limitation is CPU related and changing the instanced type directly impacts performance. This is confirmed by similar results obtained on B4 and B4_1G instances
The best throughput I got for an Expando entity with about 30 fields is:
Standard GAE SDK
- B1 instance: ~220 entities/second
- B2 instance: ~250 entities/second
- B4 instance: ~560 entities/second
- B4_1G instance: ~560 entities/second
- B8 instance: ~650 entities/second
Patched GAE SDK
- B1 instance: ~420 entities/second
- B8 instance: ~900 entities/second
For standard GAE SDK I tried various approaches including multi-threading but the best proved to be fetch_async
with wait_any
. Current NDB library already does a great job of using async and futures under the hood so any attempt to push that using threads only make it worse.
I found two interesting approaches to optimize this:
- Matt Faus - Speeding up GAE Datastore Reads with Protobuf Projection
- Evan Jones - Tracing a Python performance bug on App Engine
Matt Faus explains the problem very well:
GAE SDK provides an API for reading and writing objects derived from your classes to the datastore. This saves you the boring work of validating raw data returned from the datastore and repackaging it into an easy-to-use object. In particular, GAE uses protocol buffers to transmit raw data from the store to the frontend machine that needs it. The SDK is then responsible for decoding this format and returning a clean object to your code. This utility is great, but sometimes it does a bit more work than you would like. [...] Using our profiling tool, I discovered that fully 50% of the time spent fetching these entities was during the protobuf-to-python-object decoding phase. This means that the CPU on the frontend server was a bottleneck in these datastore reads!
Both approaches try to reduce the time spent doing protobuf to Python decoding by reducing the number of fields decoded.
I tried both approaches but I only succeed with Matt's. SDK internals changed since Evan published his solution. I had to change a bit the code published by Matt here, but is was pretty easy - if there is interest I can publish the final code.
For a regular Expando entity with about 30 fields I used Matt's solution to decode only couple fields and obtained a significant improvement.
In conclusion one need to plan accordingly and don't expect to be able to process much more than few hundreds entities in a "real-time" GAE request.