ShotGrid find pagination stability with limit and offset under concurrent changes
26.8K reputation · 26 May 2023, 20:31 UTC
Goal is to define reliable bulk retrieval semantics for ShotGrid API find operations when result sets exceed a single page.
Documented capability uses limit and offset paging for find requests, with a maximum page size in the order of hundreds to a thousand entities. Pagination relies on offset rather than stable cursor tokens. Administrative settings and deployment type can affect effective limits for attachments and API handling.
Uncertainty remains about result consistency when entities are created or deleted between successive pages, and about the inclusion rules for soft-deleted entities across REST API, Python API and web UI. No uniform documented guarantee is available for ordering stability or visibility of deleted revisions in find versus direct read calls.
What ordering guarantees, if any, are documented for paginated find results for a given filter? Does ShotGrid define expected behavior for offset shifts caused by concurrent entity creation or deletion during a multi-page retrieval? Is there a documented pattern for deterministic bulk access that avoids duplicate or missing entities under concurrent modification?
1 answer
1 question comment
Use comments to ask for clarification. Post a solution as an answer.
26,840 reputation · 27 May 2023, 03:49 UTC
When using keyset pagination you must match the filter operator to the sort direction: for order: [id_asc] filter with [['id', 'gt', last_id]]; for order: [id_desc] filter with [['id', 'lt', last_id]]. ShotGrid also enforces a hard limit of 500 entities per find request; any higher limit value is truncated to this maximum and may return a 400 error if the limit exceeds the allowed threshold.
# Ascending keyset example
last_id = 0
while True:
filters = [['id', 'gt', last_id]]
page = sg.find('Asset', filters=filters, order=[('id', 'asc')], limit=100)
if not page:
break
for entity in page:
process(entity)
last_id = entity['id']
# Descending keyset example
last_id = None # start with no upper bound
while True:
if last_id is None:
filters = [] # no filter for first page
else:
filters = [['id', 'lt', last_id]]
page = sg.find('Asset', filters=filters, order=[('id', 'desc')], limit=100)
if not page:
break
for entity in page:
process(entity)
last_id = entity['id']