Salting (salt-split routing) #
Manage salt-split assignments for a collection whose schema declares a
routing field ("routing": true).
One hot routing value — the mega tenant, the biggest org — funnels every
write into a single partition; salting splits that value across a SET of
partitions so the hot key stops being a placement bottleneck.
The API never moves data by itself: assignments state intent, the write path consumes them live (the very next write of an assigned value lands inside its partition set), and migrating documents that are already placed is a separate rehash operation. The joins tutorial walks the complete sequence against a live node.
Assign or clear salt splits #
PUT /<target>/_salting
{
"threshold": 4,
"assignments": {"org-mega": 4}
}
Full-replace semantics, like routing allocation: the second PUT states the
complete assignment set — an assignment disappears unless restated. Pass an
empty assignments object to keep only the threshold (nothing salted).
Path parameters #
<target>
(Required, string) The collection — it must declare exactly one routing field; the salted values are values OF that field.
Request body #
threshold
(Required, integer) The doc-count threshold at which the feed flags a routing value as a salting candidate. Candidate flagging needs a declared threshold; the assignments themselves are never auto-applied.assignments
(Required, object) Mapping of routing value → split count. Split counts are validated to2..=64and keys must be non-empty.
Response #
The assignment set rendered with each value’s computed partition set — the
frozen engine contract hash(<value> #<i>) % partitions for i in
0..splits, sorted and deduped (the effective spread can be below
splits, and the response reports what IS):
{
"acknowledged": true,
"routing_field": "org_id",
"threshold": 4,
"status": "assigned (salt-split routing active on writes)",
"assignments": [
{"value": "org-mega", "splits": 4, "partitions": [76, 83, 85, 254]}
]
}
status is unsalted with no assignments, and
assigned (salt-split routing active on writes) otherwise.
How writes pick a member #
A keyed write of an assigned value picks its member deterministically —
hash(<routing value> #hash(<_key>) % splits) — so the same key always
lands in the same place; auto-ID writes round-robin the members. Unassigned
values keep their single hash(<value>) partition, and searches fan out
across every partition either way: salting changes placement, never
visibility. Key-addressed reads (GET /<target>/_doc/<key>?routing=<value>)
probe the whole partition set.
Inspect the salting state #
GET /<target>/_salting
Returns the same rendering as the PUT response (without acknowledged).
The salting feed #
GET /<target>/_salting/feed?size=10
The operator’s measurement: a terms aggregation over the routing field
through the exact search fan-out, each row annotated with the current
assignment, values at or above threshold flagged candidate: true:
{
"routing_field": "org_id",
"threshold": 4,
"size": 10,
"values": [
{"value": "org-mega", "doc_count": 6, "candidate": true, "assigned_splits": 4},
{"value": "org-a", "doc_count": 3, "candidate": false, "assigned_splits": null}
]
}
Query parameters #
size
(Optional, integer, default:10) How many top routing values to report.
Notes and limits #
- Assignments are stored in cluster metadata (raft-replicated) and consumed live by the write path; clearing an assignment narrows placement on the very next write.
- Documents written BEFORE an assignment stay on their old partition until a rehash migration moves them — reads bridge both placements by probing the assigned set (the migration operation ships separately).
- The bulk keyed fast lane serves field-routed collections through the per-item path (correct placement, no batch grouping) until the lane itself becomes field-aware.