Skip to content

Bound ZMQ identity slot pool to cap MWorkerQueue growth under REQ churn - #69922

Open
dwoz wants to merge 1 commit into
saltstack:3008.xfrom
dwoz:dwoz/perf/zmq-identity-slot-cap-3008x
Open

Bound ZMQ identity slot pool to cap MWorkerQueue growth under REQ churn#69922
dwoz wants to merge 1 commit into
saltstack:3008.xfrom
dwoz:dwoz/perf/zmq-identity-slot-cap-3008x

Conversation

@dwoz

@dwoz dwoz commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Closes #69920

Summary

  • Cap the _REQ_IDENTITY_SLOT counter with a mod-N pool (default 8, SALT_REQ_IDENTITY_SLOT_MAX) so a long-lived daemon that churns AsyncReqMessageClient instances no longer grows the master ROUTER's per-peer routing-id hashtable indefinitely
  • Expose the CLI identity slot cap (previously hardcoded os.getpid() % 256) as SALT_CLI_IDENTITY_SLOT_MAX so operators can tune below 256 when they see MWorkerQueue growth under CLI-loop churn
  • ROUTER_HANDOVER=1 (already set on the master ROUTER) swaps the older peer entry on slot collision; salt's own request-timeout retry handles the (short) window where an in-flight reply is orphaned

Measured impact

4h stress rig against a 3-worker 3008.x master (flood_events + state.apply + state.highstate + salt-api curl loop):

MWorkerQueue end container mean
Stock 3008.x 541 MB 1010 MB
With this patch 337 MB (-204) 900 MB (-110)

The characteristic step-jump pattern at ~40m and ~103m disappears; growth becomes slow-linear.

Test plan

  • pytest tests/pytests/unit/transport/test_zeromq_identity_slot.py — 12 tests, all passing locally
  • Verify the mod cap is opt-outable by setting SALT_REQ_IDENTITY_SLOT_MAX=999999 and confirming the counter is effectively unbounded
  • Verify ROUTER_HANDOVER=1 remains configured on the master ROUTER
  • Deploy to a heavy salt-api site and monitor MWorkerQueue RSS over 4h

MWorkerQueue on the master accumulates a routing-id entry in libzmq's
per-peer hashtable for every unique identity a REQ client presents; the
entry is retained until LINGER + TCP_KEEPALIVE timeouts expire.

An unbounded process-lifetime counter meant salt-api workers, minions,
or any other long-lived daemon that churns AsyncReqMessageClient
instances would grow the master's routing-id table without bound. A
random-per-CLI-invocation slot (previously mod-256) similarly produced
one hashtable entry per CLI process on monitoring / orchestration
tooling that loops on salt, salt-run, etc.

Cap both slot pools with a small modulus and expose the pool size via
SALT_REQ_IDENTITY_SLOT_MAX (default 8) and SALT_CLI_IDENTITY_SLOT_MAX
(default 256, matching prior hardcoded value). ROUTER_HANDOVER=1 on the
master swaps the older peer entry in place on slot collision; salt's
existing request-timeout retry handles the (short) window where an
in-flight reply is orphaned.

Measured on a 4h stress rig against a 3-worker 3008.x master:
MWorkerQueue RSS 541 MB -> 337 MB (-204, -38%); container mean
1010 MB -> 900 MB (-110, -11%). The characteristic step-jump pattern
at ~40m and ~103m disappears; growth becomes slow-linear.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

test:full Run the full test suite

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant