Repository navigation
libgearman: opt-in ketama hash-based server selection by job unique - #511
Conversation
|
There's a lot to digest here. I'll get back to this soon. |
|
So will clients like the PHP extension and the Gearman::Client Perl module need to be enhanced to support this feature? |
|
Yes, for the language bindings that actually wrap libgearman — but it depends on which one:
So: PHP and |
|
To be honest, this doesn't seem very useful to me without downstream support from the PHP, Perl, Python, et al. client/worker APIs. I guess we could just hope for the best and have a "build it and they will come" mindset, but it took almost a decade to get PHP to support SSL. I don't suppose we could just make this the default behavior when there's more than one server? What's the downside? Would it break any existing gearmand usage? |
|
This PR now has conflicts which need to be resolved. |
libgearman has always submitted a job to the first idle connection in gearman_client_add_server() order, ignoring the job's unique entirely, so the same unique can land on any server depending on load rather than being routed consistently. libhashkit is already linked in but was never wired up for this. Add gearman_client_set_server_selection_by_unique() to opt a client into picking the destination server by hashing the job's unique against a ketama consistent-hash ring built from the current connection list, with fail-over to the next server in ring order. Disabled by default, and restricted to actual job-submission commands (GET_STATUS and friends are untouched), so existing behavior is unchanged unless a caller opts in. Fixes gearman#64 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Signed-off-by: Alexei Pastuchov <info@maximka.de>
The previous commit's internals.cc tests only exercised client_select_connection_by_key() in isolation, not the opt-in API or the run.cc wiring. Add: - a client_test.cc unit test for the gearman_client_set_server_selection_by_unique() getter/setter contract, including NULL-safety; - multi_client_test.cc integration tests against two real gearmand instances: a second, independently created client with the same server list and the option enabled routes a given unique to the same server as the first (proven by the server coalescing the second background submission into the same job, so both return an identical job_handle), and submission still succeeds by failing over to the surviving server when the hash-picked one is down. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Signed-off-by: Alexei Pastuchov <info@maximka.de>
- Add a Sphinx doc page for gearman_client_set_server_selection_by_unique() / gearman_client_server_selection_by_unique(), following the existing per-function .rst convention, and link it from the libgearman index (whose intro paragraph previously only described the old, order-based routing). - gearman_client_clone() copies the client's other persistent settings (non_blocking, free_tasks, generate_unique, ...) but was missing this one, so a cloned client would silently fall back to default selection. Copy it too, and add clone-preserves-the-flag coverage. - Add an empty-unique end-to-end test: with the option enabled, a task whose unique is "" (unique_length == 0) must fall back to default selection rather than hashing zero bytes. While adding the empty-unique test, the pre-existing fail-over test (from the previous commit) turned out to be flaky under repeated runs. Root cause was in the test itself, not the selection logic: this test suite's server_startup_st never reuses or reindexes a killed server's slot, so after multi_client_test() above kills and restarts both servers, the live ones move from indices 0/1 to 2/3. The fail-over test was shutting down the long-dead index 0, which no-ops and leaves the real server 1 running throughout -- so the assertion was intermittently failing for an unrelated reason (the "down" server was never actually down), not exercising a bug in client_select_connection_by_key(). Fixed by computing the live indices from server_container.count() instead of hardcoding them, and by tolerating the same post-shutdown GEARMAN_LOST_CONNECTION race multi_client_test() already works around. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Signed-off-by: Alexei Pastuchov <info@maximka.de>
cd39b99 to
2ebdaeb
Compare
I'd keep it opt-in, at least for now. Several existing setups would break if it became the default:
On the bindings concern, I agree a new setter on its own doesn't reach PHP/Perl/Python users. I'd like to also expose it as a client option flag, Bindings that pass option flags through can then use it without a new release. I checked pecl-gearman: $client->addOptions(2048); // GEARMAN_CLIENT_SERVER_SELECTION_BY_UNIQUEand a named constant can follow in pecl whenever convenient. ( As for why it's useful even before every binding catches up: a job's unique is only deduplicated within a single server. Without consistent routing, two submits with the same unique can land on different servers and both run. That's what #64 is about. If it proves itself, flipping the default in a later major release with a changelog note seems reasonable to me. |
That sounds like a good idea to me. That's something you would need to add to this PR, right? If so, please do when you have the time. And additional test(s) of this new client option, please, if appropriate. I was going to make the next version of gearmand version 2.1.1, but with this new feature, version 2.2.0 would be more appropriate, I think. Do you agree, @p-alik? |
gearman_client_set_server_selection_by_unique() is only reachable from language bindings that wrap it explicitly. Bindings that already pass an options bitmask through to libgearman, such as pecl-gearman's GearmanClient::addOptions(), could enable the feature without a new release if it were also a gearman_client_options_t flag. Add GEARMAN_CLIENT_SERVER_SELECTION_BY_UNIQUE (1 << 11) and handle it in gearman_client_add_options(), gearman_client_remove_options(), gearman_client_has_option() and gearman_client_options(). Also add it to the usable_options[] list in gearman_client_set_options(), which otherwise silently ignores flags it does not know about. The flag and the dedicated setter change the same setting. Add a test covering add/remove/has/options/set_options, the interplay with the setter and other flags, and gearman_client_clone(). Refs gearman#64 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Alexei Pastuchov <info@maximka.de>
server_selection_by_unique_test submitted one unique from two clients and compared the job handles. Without hash-based selection both clients send to the first server anyway, which coalesces the duplicate unique and returns the same handle, so the test passed with the option disabled and did not show that hashing takes effect. Submit 32 uniques instead and ask each server directly with GET_STATUS_UNIQUE where every job was queued. Require each unique to be on exactly one server and both servers to have received some jobs, then keep the check that a second client lands every unique on the same server. The ring is keyed on host:port and the test ports are random, so a single fixed unique cannot be relied on to map to a particular server. With the setter calls removed the test now fails with "all 32 jobs landed on one server (server 1: 32, server 2: 0)". Refs gearman#64 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Alexei Pastuchov <info@maximka.de>
Yes, I agree with 2.2.0. The PR adds public API (the setter/getter and a new The flag and tests are pushed:
Since 2.2.0 adds library functions, should |
it probably should increase since there are new library functions. Why |
It comes from libtool's version-info update rules for
From
So everything since 1.1.0 has been additive, and Should I add the bump to this PR, or would you rather do it in the 2.2.0 release commit? |
Please do it here in this PR. I'd probably just mess it up. 😄 Thanks for the explanation! I never knew about "age"... |
GEARMAN_LIBRARY_VERSION has been 8:0:0 since 1.1.0. Since then only interfaces have been added to the public API: no GEARMAN_API function was removed, the signatures of the functions present in both are unchanged, and gearman_task_st kept its size and field offsets when is_initialized was dropped from its options. This release adds gearman_client_set_server_selection_by_unique(), gearman_client_server_selection_by_unique() and the GEARMAN_CLIENT_SERVER_SELECTION_BY_UNIQUE flag. Following libtool's rules for added interfaces, increment current and age and reset revision. The soname stays libgearman.so.8, so existing binaries keep working, and the library file becomes libgearman.so.8.1.0. Update the file name listed in the RPM spec accordingly. Refs gearman#64 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Alexei Pastuchov <info@maximka.de>
Done in ab7f394:
So A |
Summary
gearman_client_add_server()order, ignoring the job'suniqueentirely, so the same unique can land on any server depending on load.libhashkitis already linked in but was never wired up for this.gearman_client_set_server_selection_by_unique()/gearman_client_server_selection_by_unique(), that routes a job submission by hashing itsuniqueagainst a ketama consistent-hash ring built from the client's current connection list, with fail-over to the next server in ring order if the hash-picked one is unavailable. Disabled by default, so existing behavior is unchanged unless a caller opts in. Restricted to actual job-submission commands (SUBMIT_JOB*/SUBMIT_REDUCE_JOB*);GET_STATUSand friends are untouched, and an emptyuniquefalls back to the default selection.gearman_client_clone()now copies this option like the client's other persistent settings (it was silently dropped by clone before).gearman_client_set_server_selection_by_unique.rst), linked from the libgearman index, whose intro previously only described the old order-based routing.Test plan
libgearman/server_selection.cc's ketama ring/selection covered directly intests/libgearman-1.0/internals.cc(determinism, busy-connection skipping, ring-order fail-over, empty connection list).tests/libgearman-1.0/client_test.cc(getter/setter contract, NULL-safety, survivesgearman_client_clone()).tests/libgearman-1.0/multi_client_test.ccagainst two realgearmandinstances: same unique routes to the same server across independent clients (proven via identicaljob_handlefrom server-side coalescing), submission with an empty unique falls back to default selection, and submission still succeeds by failing over to the surviving server when the hash-picked one is down.t/client,t/internals,t/multi_client) passes, and the docs build cleanly undersphinx-build -W.🤖 Generated with Claude Code