{"record":{"id":"bfc73a5c90f8df24","repo":"phacility/phabricator","slug":"query-offset-is-too-large-offset-limit-s-max-s","errorCode":null,"errorMessage":"Query offset is too large. offset+limit=%s (max=%s)","messagePattern":"Query offset is too large\\. offset\\+limit=(.+?) \\(max=(.+?)\\)","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"src/applications/search/fulltextstorage/PhabricatorElasticFulltextStorageEngine.php","lineNumber":227,"sourceCode":"\n    $spec = array(\n      '_source' => false,\n      'query' => array(\n        'bool' => $q->toArray(),\n      ),\n    );\n\n\n    if (!$query->getParameter('query')) {\n      $spec['sort'] = array(\n        array('dateCreated' => 'desc'),\n      );\n    }\n\n    $offset = (int)$query->getParameter('offset', 0);\n    $limit =  (int)$query->getParameter('limit', 101);\n    if ($offset + $limit > 10000) {\n      throw new Exception(pht(\n        'Query offset is too large. offset+limit=%s (max=%s)',\n        $offset + $limit,\n        10000));\n    }\n    $spec['from'] = $offset;\n    $spec['size'] = $limit;\n\n    return $spec;\n  }\n\n  public function executeSearch(PhabricatorSavedQuery $query) {\n    $types = $query->getParameter('types');\n    if (!$types) {\n      $types = array_keys(\n        PhabricatorSearchApplicationSearchEngine::getIndexableDocumentTypes());\n    }\n\n    // Don't use '/_search' for the case that there is something","sourceCodeStart":209,"sourceCodeEnd":245,"githubUrl":"https://github.com/phacility/phabricator/blob/5720a38cfe95b00ca4be5016dd0d2f3195f4fa04/src/applications/search/fulltextstorage/PhabricatorElasticFulltextStorageEngine.php#L209-L245","documentation":"PhabricatorElasticFulltextStorageEngine::buildSpec() enforces Elasticsearch's max_result_window (default 10000) before issuing a search: offset + limit must not exceed 10000, because ES deep-paginated from/size queries get extremely slow and ES itself rejects them. Phabricator surfaces this proactively with its own Exception instead of letting ES fail later.","triggerScenarios":"Executing a saved search with a large offset (e.g. offset 9900, limit 101 - the default limit is 101), or building a custom PhabricatorSavedQuery with limit/offset parameters summing over 10000; also user-driven paging deep into result sets when Elasticsearch is the configured fulltext engine (cluster.search-service-config).","commonSituations":"Users jumping to a very deep page of search results in the UI; scripts enumerating all matches with offset += limit loops; over-broad queries (missing filters) returning huge result counts that invite deep paging; ES clusters where admins lowered max_result_window below 10000 while Phabricator still assumes 10000.","solutions":["Narrow the query (projects, statuses, date ranges) so the result set fits within the first 10000 - deep paging is an anti-pattern for search UIs.","Paginate with after_key/cursor-style ordering (sort by dateCreated/id and use the last hit as a boundary) instead of large offsets; the Conduit search 'after' cursor does exactly this.","If you truly must deep-page and control the ES cluster, raise index.max_result_window in ES settings - accept the performance cost; do not change Phabricator's cap.","Audit scripts that loop with offset += 100 and cap total processed documents at 10000 per query window."],"exampleFix":"// before: deep offset pagination against ES\nfor ($off = 0; $off < 100000; $off += 100) {\n  $saved->setParameter('offset', $off);\n  $hits = $engine->executeSearch($saved);\n}\n\n// after: cursor by creation date, no offset\n$last = 0;\ndo {\n  $saved->setParameter('offset', 0);\n  $saved->setParameter('createdStart', $last);\n  $hits = $engine->executeSearch($saved);\n  $last = end($hits)['dateCreated'];\n} while (count($hits) == 100);","handlingStrategy":"validation","validationCode":"$offset = (int)$saved->getParameter('offset', 0);\n$limit  = (int)$saved->getParameter('limit', 100);\nif ($offset + $limit > 10000) {\n  // narrow the query window instead of deep paging\n  $saved->setParameter('offset', 0);\n  $saved->setParameter('createdStart', $checkpoint_date);\n}","typeGuard":"function fitsResultWindow($offset, $limit, $max = 10000) { return ($offset + $limit) <= $max; }","tryCatchPattern":"try {\n  $phids = $engine->executeSearch($saved);\n} catch (Exception $ex) {\n  if (strpos($ex->getMessage(), 'offset is too large') !== false) {\n    $saved->setParameter('offset', 0);\n    $phids = $engine->executeSearch($saved); // restart page with cursor instead\n  }\n}","preventionTips":["Prefer cursor-based (after/sort-boundary) pagination over offset paging in scripts","Cap saved-query offsets in UI glue code before executing","Alert when result counts approach 10000 - it usually means filters are too broad"],"tags":["phabricator","elasticsearch","pagination","fulltext-search","limit-exceeded"],"backgroundTag":"elasticsearch-result-window-exceeded","analyzedSha":"5720a38cfe95b00ca4be5016dd0d2f3195f4fa04","analyzedAt":"2026-08-21T05:07:25.672Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}