Salting (salt-split routing)

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 to 2..=64 and 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.
Calendar October 6, 2026
Edit Edit this page