Repository navigation
feat!: add resolver-driven Memcached auto discovery - #43
Merged
Merged
Conversation
Move discovery, membership updates and source generations into topology, and delegate borrowing and draining to nodes. Reuse discovery connections and cached configurations on timeout, require resolvers to own cleanup, and route rendezvous hashing by node identity. Update lifecycle, routing and telemetry tests for the new contracts. BREAKING CHANGE: custom resolvers must implement Close, and rendezvous hashing no longer includes Priority in its scores.
Share protocol and metrics fixtures, separate daemon compatibility checks, and document test commands. Simplify config response parsing, preserve read errors, and handle cancellation and concurrent shutdown.
Use comparable Addr values containing only Network and Address for node identity. Remove priority and metadata, and move sorting into resolvers so topology preserves their order. Extract the connection factory from node state and update tests and documentation. BREAKING CHANGE: AddrKey, Priority, metadata helpers, and CanonicalAddress are removed. NewAddr now accepts only network and address. Topology preserves resolver order, which affects modulo routing.
Preserve resolver address identity, reject duplicate discovery nodes, and cancel refresh loops during shutdown. Drain removed nodes asynchronously and record routing and borrowing failures in request telemetry.
Update the CLI dependency to the published root commit containing picker and refresh example dependency metadata for GOWORK=off. Cover standalone CLI and example builds in CI. Allocate pool wakeup channels only for capacity waiters, replace escaping release closures with single-use handles, and keep built-in rendezvous inputs on the stack. Preserve routing, custom hash ownership, and release semantics. Fix the concurrent example's shared err data race and document example runs. Validation: - Full root tests and real Memcached 1.6.37 integration tests with -race. - Nine examples run with -race against isolated TCP/UDP Memcached, both with workspace resolution and GOWORK=off; only endpoint literals were redirected through temporary Go overlays. - CLI standalone build/checks, GUI service race tests, go vet, golangci-lint, standalone example builds, and git diff --check. - SASL example compiled only; live SASL authentication was not tested because no SASL-enabled test server is available.
yeqown
marked this pull request as ready for review
October 19, 2026 08:59
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Merged and released: squash merged into
mainas9984c7d; v1.5.0 is published with explicit breaking changes and migration instructions. The existing module import path is retained. All five CLI release assets built successfully, and the downloaded macOS ARM64 asset reportsv1.5.0.Main-library dependency update: PR #44 merged as
aaf581f, updating CLI, GUI, example, and benchmark requirements tov1.5.0with tidied module files. Its CI passed.Static server lists cannot follow Memcached cluster membership changes. This PR implements opt-in discovery through
config get cluster, scheduled topology refreshes, and per-node connection pools that are reused or drained as membership changes.Related to #40. Review updated on 2026-10-19 for
800f365againstmain/9e03067.Follow-up fixes are committed and pushed in
91e4f9a. Standalone CLI/example builds, the performance changes below, and the concurrent example race fix are included. Real integration tests and nine examples passed with the race detector. The deprecated SASL example was compiled only; live SASL authentication was not exercised because no SASL-enabled test server is available.Implementation and discovery scope
resolverandpickerpackages. AddNewWithContext,WithResolveTimeout, resolver-controlled refresh scheduling, and discovery/topology telemetry.cmd/gui, update migration/lifecycle documentation, and make default tests self-contained.Memcached automatic discovery is implemented, but must be enabled explicitly. The default remains
resolver.NewStatic()with no refresh schedule.The resolver implements the ASCII
config get clusterextension used by AWS ElastiCache and Google Memorystore. It reuses its discovery connection, reconnects after failed exchanges, validates configurations, rejects stale generations, sorts discovered nodes, and retains cached data on exchange timeout. Background errors retain the client's last usable topology and retry according to the returned schedule. Each resolver belongs to one client, which owns closing it.Discovery accepts TCP/tcp4/tcp6 endpoints and uses TCP for discovered data nodes, preferring their advertised IPs. This does not provide DNS/SRV or Kubernetes discovery, legacy AWS discovery commands, TLS/authentication configuration for the discovery connection, data migration, or automatic request replay. Ordinary upstream Memcached does not expose this cluster discovery extension. AWS/GCP support was checked through protocol fixtures and topology integration tests; live cloud interoperability has not been exercised.
Breaking API and behavior changes
Addr,NewAddr,Resolver,Picker,Builderresolver.Addr,resolver.Resolver, andpicker.Picker; construct pickers directly.Addrcontains onlyNetworkandAddress; priority and metadata helpers are removed.WithPickBuilderand root*PickBuilderconstructorsWithPicker(picker.NewCRC32HashPicker()),NewMurmur3HashPicker(seed), orNewRendezvousHashPicker(seed)frompicker.Resolve(string) ([]*Addr, error)Resolve(context.Context, string) (resolver.ResolveResult, *time.Time, error)andClose() error. The result exposesGeneration uint64; nil scheduling stops refreshes.hashpackage /Murmur3digestNewand the client command signatures remain available. Custom pickers must handle concurrent calls and changing address snapshots. Static resolution now checks syntax without performing DNS lookups and rejects duplicate identities and invalid address forms.Rendezvous scoring now uses node identity rather than priority and changes existing key placement even with the same addresses and seed. Plan for cache misses during migration. CRC32/Murmur3 retain modulo routing, so membership changes can remap many keys. Retrieval commands now route by their first key, matching storage; multi-key
Gets/GetAndTouchesstill require keys on the same node and do not scatter/gather.Release target: v1.5.0, retaining the existing
github.com/yeqown/memcachedimport path. The release will explicitly list these breaking changes and migration steps. External consumers have not been enumerated; compatibility conclusions cover the modules and examples checked here.CLI/example build fixes and performance
The standalone CLI build failure at
2d985b2is fixed in91e4f9a: its nested module now requires published root pseudoversionv1.4.1-0.20261019055421-2d985b2ae2c6, which supplies the newpickerpackage, and its module files have been tidied. CI addsGOWORK=off go build -mod=readonly ./...fromcmd/memcached-cli, so workspace resolution cannot hide a stale dependency. No relativereplaceis needed. The standalone CLI currently uses the already-published2d985b2implementation; a later root release/dependency update is required to include the new performance optimizations in standalone CLI distributions.The example module dependency files were also stale outside workspace mode and are now tidied. CI builds every standalone example with
GOWORK=offand-mod=readonly. The concurrent example now keeps errors local to each goroutine, fixing a reproduced race caused by assigning to the outererr. Example execution and expected CAS/context-deadline output are documented indocs/testing.md.The performance changes are internal and introduce no additional public API changes:
sync.Onceto preserve concurrent cleanup waiting, idempotence, and panic semantics. The handle still allocates once; it is never copied or reused.The following medians are from five serially executed benchmark runs on Apple M2 / darwin/arm64, Go 1.26.0,
GOMAXPROCS=8, with telemetry disabled. Temporary snapshots ofmain/9e03067, pre-fix head /2d985b2, and the optimized root implementation in91e4f9ause identical HEAD dependency versions to isolate code changes. Runs used-benchmem -benchtime=500ms -count=5 -cpu=8. Get used a dedicated Memcached 1.6.37 with two server threads, a small seeded value, and warm connections; pool reuse used in-memory connections. Parallel cases used eight workers.2d985b2→ optimized91e4f9atime/opCompared with
2d985b2, pool reuse is approximately 24–29% faster in these runs, and default rendezvous selection is approximately 62–64% faster at 10/50 nodes. Get consistently saves 128 B and two allocations per operation. The first serial Get series was 6.8% slower; a follow-up of five alternating before/after pairs (-benchtime=750ms, alternating execution order) did not reproduce that regression: serial medians were 26.63 → 26.21 µs and parallel medians 9.64 → 9.34 µs. End-to-end timing remains noisy, so the supported conclusion is reduced allocation pressure rather than a production latency guarantee.Some overhead remains relative to main: optimized pool reuse is still about 19% slower serially and 42% slower in this parallel microbenchmark, and Get still makes 11 rather than nine allocations. Rendezvous remains O(node count). Default rendezvous inputs larger than the stack buffer and custom hashes still allocate; custom hashes retain their ownership behavior.
Discovery I/O stays off the request path. Each auto-discovery client adds a refresh loop/timer and a discovery connection; unchanged identities retain their existing pools. Pool limits apply per node, so aggregate connection capacity grows with discovered node count. Production load and large-cluster behavior remain unmeasured.
The CLI release version injection is fixed in
800f365: the version is now a linker-overridable variable. A build-and-run check reproduced the previous failure, passed after the fix, and is now part of standalone CLI CI. Release builds can report the actual release tag.Validation
Fresh local verification of
91e4f9a:GOWORK=off go test -race -count=1 ./....go vet ./...,golangci-lint run --timeout=5m ./...(v2.13.2, zero issues), andgit diff --check.GOWORK=off GOPROXY=off go build -mod=readonly ...andgo test -mod=readonly -race -count=1 ./...; workspace CLI checks also pass.go test -race -count=1 ./service/...and benchmark-module compilation against the local library.TestMemcachedIntegrationwith-race -count=1against an isolated local Memcached 1.6.37, covering classic, meta, compression, concurrent reads, and concurrent meta operations.otelran with-raceagainst an isolated Memcached 1.6.37 TCP/UDP daemon, both in workspace mode and withGOWORK=off/-mod=readonly. Only endpoint literals were redirected with temporary Go overlays; the examples otherwise ran unchanged. The concurrent example completed 10,000 Set/Get pairs without race reports in each mode.SASLandotel. SASL validation is compile-only, with no live authentication or daemon compatibility claim.GitHub CI for
91e4f9apassed: Lint, Test (including standalone CLI and every example build), and Memcached compatibility all succeeded (run). The real example executions described above were verified locally in both workspace modes; SASL remains compile-only.Release preparation re-ran the real Memcached 1.6.37 integration suite and all nine examples with the race detector; all passed. The CLI release-version check, standalone build, race invocation, vet, and root lint also passed after
800f365. SASL remains compile-only. Final CI for800f365passed (run); squash merge9984c7dalso passed main CI (run).