feature: Valkey cache integration (reuses go-redis client)#1263
feature: Valkey cache integration (reuses go-redis client)#1263daric93 wants to merge 26 commits into
Conversation
Implement Valkey as a first-class cache store backend using valkey-glide Go client. This enables Gorse users to use Valkey as an alternative to Redis for the cache layer. Changes: - Add valkey://, valkeys://, valkey+cluster://, valkeys+cluster:// URL prefix constants and config validation - Implement full cache.Database interface in storage/cache/valkey.go using valkey-glide v2 client (standalone + cluster modes) - Time series implemented via Sorted Set + Hash with Go-side bucket aggregation (no TimeSeries module dependency) - Score/document operations use valkey-search FT.* commands via CustomCommand - Add integration test suite embedding baseTestSuite - Add integration report with analysis and task breakdown Signed-off-by: Daria Korenieva <daric2612@gmail.com>
Add valkey/valkey-bundle:unstable (valkey-search 1.2.0+ with full-text search support) to storage/docker-compose.yml on port 6380. Update default test URI to valkey://127.0.0.1:6380/. Signed-off-by: Daria Korenieva <daric2612@gmail.com>
…/gorse-io-gorse into feature/valkey-cache-integration
Signed-off-by: Daria Korenieva <daric2612@gmail.com>
Signed-off-by: Daria Korenieva <daric2612@gmail.com>
…er ops, handle username in URL Signed-off-by: Daria Korenieva <daric2612@gmail.com>
…ix TAG escaping: new escapeTag() covers all special chars (| * { } etc.) to prevent query injection via user-controlled values - Fix CROSSSLOT error: delete keys one-at-a-time in cluster mode via vDel() - Fix race condition: remove non-atomic Exists+HSet in UpdateScores - Add max iteration guard (100) to DeleteScores loop - Extract wrapper methods (vDel, vHSet, vZAdd, vHGetAll) to reduce cluster/standalone branching duplication - Extract groupTimeSeriesPoints() helper to DRY AddTimeSeriesPoints
Signed-off-by: Daria Korenieva <daric2612@gmail.com>
…/gorse-io-gorse into feature/valkey-cache-integration # Conflicts: # storage/cache/valkey.go
…s - Add valkey/valkey-bundle:unstable service on port 6380 to unit_test job - Add VALKEY_URI env var for Linux unit tests - Add TestValkey to -skip pattern for macOS and Windows jobs (no Valkey service available on those runners) Signed-off-by: Daria Korenieva <daric2612@gmail.com>
…glide requires CGO (Rust FFI core) and cannot build with CGO_ENABLED=0, which gorse uses for static Docker binaries. This is a confirmed upstream limitation (valkey-io/valkey-glide#4253). valkey-go is the official pure-Go Valkey client from the same org. It supports all needed commands (FT.SEARCH, HSET, ZADD, SCAN, etc.), works with CGO_ENABLED=0, and simplifies the code by using a single Client interface for both standalone and cluster modes. Changes: - Replace valkey-glide/go/v2 with valkey-io/valkey-go in go.mod - Rewrite storage/cache/valkey.go using valkey-go command builder API - Eliminate all if-isCluster branching (valkey-go handles routing) - Use DoMulti for pipeline batching instead of standalone batch - Update tests to use valkey-go client for FLUSHDB - Update URL parsers to return plain strings instead of glide types Signed-off-by: Daria Korenieva <daric2612@gmail.com>
…/gorse-io-gorse into feature/valkey-cache-integration
… offset in SearchScores, clarify UpdateScores pagination - Set(): use DoMulti to pipeline all SET commands in a single round-trip - AddScores(): use DoMulti to pipeline all HSET commands - SearchScores(): pass begin as LIMIT offset and (end-begin) as count instead of fetching from 0 and slicing in Go - UpdateScores(): add clarifying comments for the first-page re-fetch optimization that avoids pagination drift Signed-off-by: Daria Korenieva <daric2612@gmail.com>
zhenghaoz
left a comment
There was a problem hiding this comment.
Too many duplicate codes comparing to Redis backend. Please fix all tests. Could you reuse Redis client, but implement special timeseries interface if Valkey is detected?
…nt SetupSuite panic - Fix wrong variable mapping in parseValkeyURL test: was assigning username to password variable (skipped port+password instead of port+username) - Use suite.Require() in ValkeyTestSuite.SetupSuite to fail fast instead of panicking on nil pointer when service is unavailable Signed-off-by: Daria Korenieva <daric2612@gmail.com>
- Add RedisValkey type embedding Redis, overriding AddTimeSeriesPoints and GetTimeSeriesPoints with sorted-set + hash + Go-side aggregation - Register valkey:// prefixes via go-redis (wire-compatible) - Disable old standalone valkey.go/valkey_test.go with //go:build ignore - Add comprehensive time series tests to baseTestSuite - Add ValkeyViaRedisTestSuite and RedisSortedSetTSTestSuite - Add EXPERIMENT_REPORT.md with context and next steps
- Embed Redis struct in RedisValkey, inherit all methods unchanged - Override AddTimeSeriesPoints/GetTimeSeriesPoints with sorted-set + hash - Register valkey://, valkeys://, valkey+cluster://, valkeys+cluster:// schemes - Remove standalone valkey.go (~839 lines) and valkey-go dependency - Add TestValkeyViaRedis and TestRedisSortedSetTS test suites
…ve error handling - Fix CI service port: expose container port 6379 (not 6380) for Valkey - Pin valkey-bundle image to 9.1-rc2 (includes search 1.2.0) - Add Init() override with clear error for missing valkey-search module - Return empty slice instead of nil from GetTimeSeriesPoints - Add TODO for OTel semconv.DBSystemValkey - Document ZRangeByScore pagination limitation
|
Done! I've reworked the implementation based on your feedback:
The duplicate code is eliminated. All tests pass against both Redis (with TimeSeries) and Valkey (with valkey-search). |
|
@zhenghaoz Please take a look when you have time - I have reworked the implementation based on your feedback. |
…redis:// URL scheme for Valkey connections. Detect Valkey at connect time via INFO server (server_name:valkey) and return RedisValkey with sorted-set time series when detected. - Remove valkey://, valkeys://, valkey+cluster://, valkeys+cluster:// prefixes - Add isValkey() detection using INFO server response - RedisValkey embeds Redis, overrides only AddTimeSeriesPoints/GetTimeSeriesPoints - All other operations inherited unchanged (wire-compatible) Signed-off-by: Daria Korenieva <daric2612@gmail.com>
|
@zhenghaoz Pushed the rework — removed all |
…with RedisClientName)
Signed-off-by: Daria Korenieva <daric2612@gmail.com>
|
@zhenghaoz Ready for review whenever you get a chance. |
|
@zhenghaoz PR is ready for review whenever you get a chance. |
|
@zhenghaoz could you review this when you have a moment? |
Summary
Fixes: #1260
Adds Valkey as a cache storage backend by reusing the existing
go-redisclient. Valkey is wire-compatible with Redis for all commands except the TimeSeries module (TS.ADD,TS.RANGE), so instead of adding a separate client library we:go-redisclient (rewritingvalkey://→redis://for URL parsing)Redisstruct in a newRedisValkeytype to inherit all methods unchangedAddTimeSeriesPointsandGetTimeSeriesPointswith a sorted-set + hash implementationThis keeps zero new dependencies for Valkey support.
What's included
RedisValkeytype instorage/cache/redis_valkey.go— embedsRedis, overrides only time series methodsZADDfor timestamp index,HSETfor values, client-side bucket aggregationvalkey://,valkeys://,valkey+cluster://,valkeys+cluster://baseTestSuiteinheritance, escape character tests, pagination tests, sorted-set TS validation against Redisvalkey/valkey-bundle:9.1-rc2),VALKEY_URIenv var, skip patterns for unit-only jobsDesign decisions
go-redisworks directly.RedisValkeyembedsRedis— all KV, queue, scores/search, scan, and purge operations are inherited with zero code duplication.ZADD(score=timestamp_ms, member=timestamp_ms) +HSET(field=timestamp_ms, value=float64) with client-side LAST-bucket aggregation matchingTS.RANGE ... AGGREGATION last.ZADDhandles duplicates naturally — score update = last-write-wins, matching TimeSeriesDUPLICATE_POLICY LAST.FT.CREATE,FT.SEARCH,FT.DROPINDEX,FT._LIST) work identically on Valkey with the valkey-search module.valkey+cluster:///valkeys+cluster://URL schemes.Why not valkey-go or valkey-glide
valkey-glideCGO_ENABLED=0for staticFROM scratchbinaries.valkey-gogo-redis(this PR)Testing
baseTestSuite(TestDocument, TestPurge, TestTimeSeries, TestScanScores, etc.)ValkeyViaRedisTestSuite— runs against real Valkey viaVALKEY_URIRedisSortedSetTSTestSuite— validates sorted-set TS implementation against Redis itselfmaxSearchResultsoverride, non-existent seriesChanged files
storage/cache/redis_valkey.goRedisValkeytype with sorted-set time seriesstorage/cache/redis_test.goValkeyViaRedisTestSuite,RedisSortedSetTSTestSuitestorage/cache/database_test.gostorage/scheme.gostorage/docker-compose.yml.github/workflows/build_test.ymlconfig/config.goconfig/config.tomlconfig/config_test.gogo.mod/go.sum