Kinds¶
A fingerprint says which searches are the same. It does not say what they are for, and a top-N of two hundred hashes is unreadable for exactly that reason. Grouped by kind, the same list is a sentence: where the load goes, which autocompletes are slow, how much traffic never asks for a document at all.
Every digest carries one, on Digest::kind() and in the kind field of
toArray(). It is read off the parsed, canonicalised request — never off the
raw body — so it holds no literal and survives
withText(false)
the way the hash does.
suggest {"query":{"match_phrase_prefix":{"name":"ada lov"}},"size":8}
aggregate {"query":{"term":{"service":"api"}},"aggs":{"hosts":{"terms":{"field":"host"}}},"size":0}
scan {"query":{"term":{"status":"shipped"}},"search_after":[1755561600000],"size":1000}
lookup {"query":{"ids":{"values":["a1","b2"]}}}
browse {"query":{"match":{"title":"waterproof boots"}},"size":20}
unknown {"query":{"span_near":{"clauses":[{"span_term":{"msg":"refused"}}],"slop":2}},"size":10}
What each one is¶
| kind | the request | read from |
|---|---|---|
suggest |
someone is still typing | a suggest section, a match_phrase_prefix or match_bool_prefix, or a prefix that is the whole question |
aggregate |
no documents come back | size: 0, or aggregations with no size and _source: false |
scan |
a walk over a result set | search_after, pit, slice, or a scroll beside body |
lookup |
documents named rather than described | an _id, or one exact clause asking for no more than a page |
browse |
a page of what matches | what a readable request falls back to |
unknown |
documents come back and the query cannot be read | a query that is entirely opaque — a plugin type, a script, a clause a ClauseRenderer described |
They are decided in that order, first match wins, and unknown sits where it
does on purpose: it is a verdict, not a fallback. browse is the fallback.
The two rules that make it right¶
Both are easy to get wrong from the raw body, and both are settled by reading the parsed model instead:
- Only
queryandpost_filterselect. A faceted browse legitimately carriesfilteraggregations withmatchclauses inside them. Classified over the whole body, every faceted page reports as a text search — the aggregations say what is counted, never what is asked.post_filteris in because it narrows the hits, which is also why the model keeps it in a slot of its own. sizeabsent is notsize: 0. A request with aggregations and nosizestill comes back with ten documents, so it is not buckets-only. The intent is only legible by also reading_source: turned off, the caller is saying it will not look at them. A filtered_source— a list of two fields — is still asking for documents.
Where it stops¶
The kind is read from the shape, and the shape does not say everything:
- A
termon a business key cannot be told from a filter.skuandserviceare the same clause to a parser, solookupneeds the request to name an_id, or to be a single exact clause asking for no more than the cluster's default page. A singletermwithsize: 5000is reading in bulk, and reads asbrowse. - A big page is not a
scan. Without a cursor there is no walk, only a large page — the alternative would be a threshold this library invented. - An autocomplete written as a
matchon an edge-ngram field is invisible. It is an ordinarymatchin the DSL, and nothing in the request says the mapping analyses it that way.
None of this moves a fingerprint. The two completion ops the classification
needed — match_phrase_prefix and match_bool_prefix — are modelled apart from
the ops they refine and render exactly as them, so promoting them left every
hash where it was.