The layer clause lets you perform hierarchical queries — a retrieval strategy that divides your index into prioritized scan ranges, each with its own quota and optional query logic. Instead of scanning all documents with a single query, you define a sequence of layers that OpenSearch Retrieval Engine Edition evaluates in order, stopping each layer when its quota is met and carrying any shortfall forward to the next.
Use the layer clause when:
Your index is sorted by an attribute field and you want to skip full scans by targeting a specific range of documents.
You need a guaranteed minimum result count before falling back to a broader query.
You need real-time data to appear in results before sorted historical data.
A single query mode (AND, OR, or RANK) cannot balance result count and query performance for your use case.
Key concepts
| Term | Description |
|---|---|
docid | The internal ID that OpenSearch Retrieval Engine Edition assigns to each document. During a query, documents are scanned by docid in ascending order. |
range | A contiguous slice of the document space, defined by attribute field values or extension keywords. Each layer scans one or more ranges. |
quota | The maximum number of documents to retrieve from a layer. If a layer falls short of its quota, the shortfall is added to the next layer's quota. |
seek | The operation that locates a document during a query. |
layer | A layer can contain one or more ranges. The layer that contains a range determines the query priority of the range. You can specify a layer in the following formats: [query] layer, layer [query] range, and range docid seek name. |
Syntax
{
"layer": [
{
"range": { ... },
"quota": <number>
}
]
}Layers are evaluated in array order — index 0 has the highest priority. The layer clause is optional.
quota
quota sets the maximum number of documents a layer can contribute to the final result set.
Key behaviors:
The total quota across all layers must not exceed
rank_size. If quotas sum to more thanrank_size, later layers are capped. For example, withrank_size=10andquota:5;quota:7, only 5 documents are retrieved from the second layer.If a layer retrieves fewer documents than its quota, the remaining capacity rolls over to the next layer automatically.
OpenSearch Retrieval Engine Edition supports two quota check modes: In the first method (per-document), it checks the remaining quota each time it retrieves a document. In the second method (post-scan), it does not check the quota during retrieval; after scanning all documents in the layer, it checks the remaining quota and adds it to the next layer's quota. In both modes, the total retrieved documents never exceed
rank_size.Default quota:
0. Maximum quota: the maximum value ofuint32_t.
range
range defines which documents OpenSearch Retrieval Engine Edition scans in a layer. If omitted, the full document space [0, docCount) is scanned.
Specify a range using attribute fields:
"range": {
"fields": [
{ "field": "<attribute-field>", "values": [<value1>, <value2>] }
]
}Requirements for attribute field ranges:
Use attribute fields only. Calculation expressions are not supported.
The attribute fields must be sorted using the same method as the queried documents. If the sort method differs, the range is invalid and a full scan runs instead.
List attribute fields continuously. Do not insert extension keywords between fields.
Extension keywords
Use extension keywords in index_type or alongside fields to target predefined data slices:
| Keyword | Targets |
|---|---|
%sorted | Sorted full data and incremental data |
%unsorted | Unsorted data, including real-time data |
%other | Documents not covered by any specified layer |
%docid | A specific range of document IDs |
%segmentid | A specific range of segments |
%percent | A percentage slice of a range, in [value1, value2) format |
If neither %sorted nor %unsorted is specified, OpenSearch Retrieval Engine Edition includes both automatically. In default mode, sorted documents are scanned first; if the quota is not met, real-time data from the next layer is scanned.
Multiple query clauses per layer
Assign a different query to each layer by separating queries with semicolons (;):
{
"query": "A OR B;A RANK B;A AND B",
"layer": [
{ "quota": 1000 },
{ "quota": 1000 },
{ "quota": 1000 }
]
}If the number of layers exceeds the number of clauses, the remaining layers use the last clause.
Examples
Target a sorted range by attribute field
If offline sorting is enabled and your documents are sorted by an attribute field such as site_id, documents from the same site occupy a contiguous range in the index. Targeting that range avoids a full scan.
Search for iphone in sites 1 and 7:
{
"layer": [
{
"range": {
"fields": [
{ "field": "site_id", "values": [1, 7] }
]
},
"quota": 5000
}
]
}Add a fallback layer for sites 5 and 10, scanned only when the first layer falls short of its quota:
{
"layer": [
{
"range": {
"fields": [
{ "field": "site_id", "values": [1, 7] }
]
},
"quota": 5000
},
{
"range": {
"fields": [
{ "field": "site_id", "values": [5, 10] }
]
},
"quota": 0
}
]
}Setting quota to 0 on the second layer means it receives only the quota rolled over from the first layer.
Distribute the quota explicitly across both layers:
{
"layer": [
{
"range": {
"fields": [
{ "field": "site_id", "values": [1, 7] }
]
},
"quota": 4000
},
{
"range": {
"fields": [
{ "field": "site_id", "values": [5, 10] }
]
},
"quota": 1000
}
]
}Target a multi-dimensional range
When documents are sorted by multiple fields — for example, by site_id and then by static_score within each site — define a range across both dimensions.
Retrieve pages with a static score above 100 from sites 1 and 7:
{
"layer": [
{
"range": {
"fields": [
{ "field": "site_id", "values": [1, 7] },
{ "field": "static_score", "values": "[100,]" }
]
},
"quota": 4000
}
]
}Enclose range values for numeric fields in double quotation marks when using open-ended intervals, such as "[100,]".Balance result count and query performance
A single query mode trades off result volume against performance:
| Query mode | Result volume | Performance |
|---|---|---|
A AND B | Low | High |
A OR B | High | Low |
A RANK B | Medium | Medium |
No single mode covers all cases. To obtain sufficient results without compromising query performance, you can specify multiple query modes in one query statement. A layered query chains all three modes: A OR B fills the quota first with the broadest results, A RANK B adds medium-precision results if needed, and A AND B fills any remaining gap with the highest-precision matches.
{
"query": "A OR B;A RANK B;A AND B",
"layer": [
{ "quota": 1000 },
{ "quota": 1000 },
{ "quota": 1000 }
]
}Layers after the first are only scanned when earlier layers fall short of their quota. If the first-layer query (A OR B) returns sufficient results, later layers are not scanned. Set quotas based on the expected result distribution for your data.Prioritize real-time data
When result freshness matters, query %unsorted (real-time) data first, then fall back to sorted data. Use %percent to further control which portion of the sorted range is scanned.
Query real-time data first, then the lower-ranked 50% of sorted documents in services 1 and 3, then the top 50%:
{
"layer": [
{
"range": {
"index_type": "%unsorted"
},
"quota": 5000
},
{
"range": {
"index_type": "%sorted",
"fields": [
{ "field": "service_id", "values": [1, 3] }
],
"percent": "[50,100)"
},
"quota": 0
},
{
"range": {
"index_type": "%sorted",
"fields": [
{ "field": "service_id", "values": [1, 3] }
],
"percent": "[0,50)"
},
"quota": 0
}
]
}%percent accepts a half-open interval in [value1, value2) format, where values represent percentages of the range.