ceil(N / max_batch_size).
Three Layers
The batching system has three layers, each building on the one below:Low-Level: execute_batch()
Combines a list of BatchOperation objects into one aliased GraphQL document and sends it as a single HTTP request.
Chunking
When the number of operations exceedsmax_batch_size (default 250), they are automatically split into sequential chunks. Each chunk is a separate HTTP request, but results are aggregated into a single BatchResult.
BatchResult
The return value provides convenient access to results:
Mid-Level: store.save_batch()
Saves a list of dirty StashObject instances through the entity store, with full cache integration.
- Non-dirty objects are skipped — clean entities in the list are silently excluded
- Creates before updates — new objects are ordered first in the alias list so GraphQL’s sequential execution assigns server IDs before updates that might reference them
- Cascade saves — unsaved related objects (UUID IDs) are automatically saved before the batch, with a deprecation warning encouraging explicit saves
- Side mutations fire — field-based side mutation handlers and queued operations execute sequentially per-entity after the main batch succeeds
- Cache key remapping — new objects get their UUID replaced with the server-assigned ID in the identity map
Mixed Creates and Updates
High-Level: store.save_all()
Scans the identity map cache for all dirty or new entities and batch-saves them. This is an ORM-style “flush” operation.
save_all() partitions entities into new objects first, then updates, before delegating to save_batch().
Error Handling
StashBatchError is raised when any operation in a batch fails. The exception carries the full BatchResult so you can inspect partial successes:
store.save_batch(), partial failure handling is automatic:
- Succeeded objects get their side mutations fired and
mark_clean()called - Failed objects remain dirty and can be retried
- The
StashBatchErroris re-raised after processing partial successes
Performance: return_fields on Bulk Methods
All bulk update methods (bulk_image_update, bulk_scene_update, etc.) support an optional return_fields parameter. By default, these methods request the full entity fragment in the response, which causes the server to resolve every relationship for every entity in the batch.
For fire-and-forget updates where you don’t need the response data, pass return_fields="id" to use a minimal inline mutation:
__side_mutations__) automatically use return_fields="id __typename" for all bulk calls, avoiding the full fragment resolution overhead.
Impact: For a bulk studio assignment of 32,000 images at 500/batch, this eliminates ~160,000 unnecessary relationship SELECT queries on the server.