Skip to content

Add redis-proxy: dual-write Redis proxy with pub/sub forwarding#351

Open
bootjp wants to merge 45 commits intomainfrom
feature/redis-proxy
Open

Add redis-proxy: dual-write Redis proxy with pub/sub forwarding#351
bootjp wants to merge 45 commits intomainfrom
feature/redis-proxy

Conversation

@bootjp
Copy link
Owner

@bootjp bootjp commented Mar 17, 2026

Implement a Redis-protocol proxy that supports dual-write to primary (Redis) and secondary (ElasticKV) backends. Includes full pub/sub message forwarding via redcon Detach + go-redis PubSub.

Verified all 581 Misskey unit tests pass through the proxy.

Implement a Redis-protocol proxy that supports dual-write to primary
(Redis) and secondary (ElasticKV) backends. Includes full pub/sub
message forwarding via redcon Detach + go-redis PubSub.

Verified all 581 Misskey unit tests pass through the proxy.
@bootjp bootjp requested a review from Copilot March 17, 2026 05:20
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request delivers a robust Redis-protocol proxy designed to facilitate the migration of Redis data to an ElasticKV store. It provides a flexible architecture for routing commands, allowing for various stages of migration from simple passthrough to full dual-write with shadow reads. The proxy ensures operational stability through bounded asynchronous operations, comprehensive metrics, and proactive error reporting, making it a critical component for managing data consistency during transitions.

Highlights

  • Dual-Write Redis Proxy: Introduced a new Redis-protocol proxy capable of dual-writing operations to a primary Redis backend and a secondary ElasticKV backend, enabling seamless data migration and consistency checks.
  • Flexible Proxy Modes: Implemented multiple operational modes including 'redis-only', 'dual-write', 'dual-write-shadow', 'elastickv-primary', and 'elastickv-only', allowing administrators to control read/write behavior and migration strategies.
  • Pub/Sub Forwarding: Added support for Redis Pub/Sub commands, forwarding messages and managing dedicated Pub/Sub connections using redcon's Detach functionality and go-redis PubSub.
  • Shadow Reading and Divergence Detection: Incorporated shadow reading capabilities to compare read responses from primary and secondary backends, detecting and classifying data divergences (e.g., migration gaps, data mismatches) for monitoring and debugging.
  • Observability and Error Reporting: Integrated Prometheus metrics for command totals, durations, errors, and divergences, along with Sentry reporting for capturing exceptions and data inconsistencies with de-duplication.
  • Transaction and Blocking Command Handling: Provided specific handling for Redis transactions (MULTI/EXEC/DISCARD) and blocking commands (e.g., BZPOPMIN, XREAD BLOCK), ensuring correct behavior and graceful shutdown integration.
Changelog
  • cmd/redis-proxy/main.go
    • Added new file for the main entry point of the redis-proxy application.
  • proxy/backend.go
    • Added new file defining the Backend interface and its RedisBackend implementation.
  • proxy/command.go
    • Added new file for classifying Redis commands into categories for routing.
  • proxy/compare.go
    • Added new file implementing shadow reading and divergence detection logic.
  • proxy/config.go
    • Added new file defining proxy configuration options and modes.
  • proxy/dualwrite.go
    • Added new file containing the core dual-write and command routing logic.
  • proxy/integration_test.go
    • Added new file with integration tests for the proxy's functionality.
  • proxy/metrics.go
    • Added new file for Prometheus metrics definitions and registration.
  • proxy/proxy.go
    • Added new file implementing the main Redis-protocol proxy server.
  • proxy/proxy_test.go
    • Added new file with unit tests for various proxy components.
  • proxy/pubsub.go
    • Added new file for handling Redis Pub/Sub sessions and message forwarding.
  • proxy/sentry.go
    • Added new file for Sentry error and divergence reporting.
Activity
  • The pull request introduces the initial implementation of the redis-proxy, including all core components, configuration, and testing infrastructure.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a comprehensive Redis proxy with dual-write capabilities, pub/sub forwarding, metrics, and error reporting. The overall architecture is well-structured, with clear separation of concerns into different packages for configuration, backends, command handling, and proxy logic. The use of a semaphore for bounding asynchronous operations and the detailed command classification are excellent. However, I've identified a few critical issues related to binary data handling that could lead to data corruption, as well as some opportunities for improving graceful shutdown and simplifying transaction handling logic.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new redis-proxy component to the codebase: a Redis-protocol proxy that routes commands to a primary backend and optionally dual-writes / shadow-reads against a secondary backend, with Prometheus metrics and optional Sentry reporting.

Changes:

  • Introduce proxy package implementing command classification/routing, dual-write + shadow-read comparison, and Redis protocol response translation.
  • Add Pub/Sub forwarding support using redcon detached connections bridged to go-redis PubSub.
  • Add unit + integration tests plus a cmd/redis-proxy executable with metrics + Sentry wiring.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
cmd/redis-proxy/main.go CLI entrypoint that wires config, backends, metrics endpoint, and proxy server lifecycle.
proxy/backend.go Backend abstraction and go-redis implementation (including optional PubSub factory).
proxy/command.go Command classification table used to decide routing (read/write/admin/blocking/txn/pubsub/script).
proxy/compare.go Shadow-read comparison logic and divergence classification + reporting.
proxy/config.go Proxy configuration and mode parsing/defaults.
proxy/dualwrite.go Dual-write/shadow-read execution logic, async bounding, metrics, and Sentry reporting hooks.
proxy/integration_test.go End-to-end integration tests against real Redis instances (skipped if unavailable).
proxy/metrics.go Prometheus metrics definitions and registration.
proxy/proxy.go Redcon server implementation: command dispatch, txn handling, pubsub session startup, response encoding.
proxy/proxy_test.go Unit tests covering command classification, divergence logic, dual-write behavior, and Sentry cooldown logic.
proxy/pubsub.go Detached pub/sub session implementation bridging client <-> upstream PubSub.
proxy/sentry.go Sentry reporter wrapper with fingerprint-based cooldown and bounded history tracking.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

bootjp and others added 3 commits March 17, 2026 14:28
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
- Extract dispatchPubSubCommand to reduce readClientCommands complexity (CC=11→8)
- Use metricsSrv.Shutdown for graceful metrics server stop
- Guard against nil logger in NewSentryReporter
- Cap ShouldReport map after eviction to prevent unbounded growth
- Normalize command names with strings.ToUpper in DualWriter methods
- Fix goAsync doc comment to match actual behavior
- Simplify execTxn by merging duplicate result-handling branches
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a new Redis-protocol proxy (cmd/redis-proxy) that can route reads/writes across a primary Redis backend and a secondary ElasticKV backend, including optional shadow-read divergence detection, Prometheus metrics, Sentry anomaly reporting, and Pub/Sub forwarding.

Changes:

  • Added a redcon-based proxy server with command classification, transaction handling, and Pub/Sub session bridging.
  • Implemented dual-write + optional shadow-read comparison logic with Prometheus metrics and Sentry reporting.
  • Added unit and integration tests plus a runnable redis-proxy CLI entrypoint.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
cmd/redis-proxy/main.go CLI entrypoint wiring config, metrics server, Sentry, and proxy server lifecycle.
proxy/backend.go Backend abstraction and go-redis based RedisBackend implementation (incl. pipelining + PubSub creation).
proxy/command.go Redis command classification table for routing (read/write/admin/txn/pubsub/etc.).
proxy/compare.go Shadow-read comparison + divergence classification and reporting.
proxy/config.go Proxy configuration and mode parsing/defaults.
proxy/dualwrite.go Dual-write/shadow-read execution, async bounding, and Sentry/metrics instrumentation.
proxy/integration_test.go Integration tests that run against real Redis instances when available.
proxy/metrics.go Prometheus metrics definitions and registration.
proxy/proxy.go Main redcon proxy server: dispatch/routing, txn support, RESP writing, Pub/Sub session startup.
proxy/proxy_test.go Unit tests covering classification, dual-writer behavior, shadow reader, sentry reporter, and backend defaults.
proxy/pubsub.go Detached-connection Pub/Sub session that forwards messages and handles pubsub-mode commands.
proxy/sentry.go Sentry reporter with cooldown-based de-duplication and flush support.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

bootjp added 2 commits March 17, 2026 16:49
- Pass []byte directly in bytesArgsToInterfaces instead of converting to string
- Handle []byte type assertion in argsToBytes for correct Sentry reporting
- Fix responseEqual panic when comparing mismatched interface types
- Return false in ShouldReport when at capacity to prevent Sentry flooding
When all subscriptions are removed, the detached connection transitions
to normal command mode instead of being stuck in pub/sub-only mode.
Clients can then execute regular Redis commands (GET, SET, transactions,
etc.) or re-enter pub/sub mode with a new SUBSCRIBE/PSUBSCRIBE.

Key changes:
- Add respWriter interface so writeResponse works with both Conn and DetachedConn
- Refactor pubsubSession with commandLoop that handles both pub/sub and normal modes
- Support transactions (MULTI/EXEC/DISCARD) in normal mode on detached connections
- Track forwardMessages goroutine lifecycle for clean pub/sub mode transitions
- Extract command name constants (MULTI, EXEC, DISCARD, PING, QUIT)
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new redis-proxy component to the codebase that speaks the Redis protocol and routes commands to primary/secondary backends to support dual-write migration to ElasticKV, with observability (Prometheus + Sentry) and pub/sub forwarding.

Changes:

  • Implement Redis-protocol proxy server (redcon) with command classification, transactions, and backend routing (dual-write + optional shadow reads).
  • Add pub/sub bridging using redcon.Detach + go-redis PubSub and support transitioning back to normal command mode.
  • Add Prometheus metrics, Sentry reporting/dedup, and accompanying unit + integration tests; introduce cmd/redis-proxy entrypoint.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
cmd/redis-proxy/main.go New CLI binary wiring config, metrics server, and proxy server lifecycle.
proxy/backend.go Backend abstraction + go-redis implementation, including PubSub capability.
proxy/command.go Command classification table for routing decisions.
proxy/compare.go Shadow-read comparison + divergence classification/metrics/Sentry reporting.
proxy/config.go Proxy configuration + supported operating modes.
proxy/dualwrite.go Core dual-write/shadow-read execution, bounded async work, and metrics/Sentry hooks.
proxy/integration_test.go Optional integration tests against real Redis instances.
proxy/metrics.go Prometheus metric definitions/registration for proxy operations.
proxy/proxy.go redcon server handlers, transaction execution, and RESP response writing helpers.
proxy/proxy_test.go Unit tests covering classification, dual-writer behavior, metrics, and Sentry dedup.
proxy/pubsub.go Pub/sub session implementation with message forwarding and mode switching.
proxy/sentry.go Sentry initialization + cooldown-based dedup for anomaly reporting.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Base automatically changed from feature/redis to main March 17, 2026 09:02
bootjp added 3 commits March 17, 2026 19:18
- Fix startForwarding race: capture upstream under lock before goroutine start
- Reject SUBSCRIBE/PSUBSCRIBE during MULTI transaction to prevent state corruption
- Normalize non-redis.Error to "ERR ..." prefix in writeRedisError for valid RESP
- Truncate divergence values in Sentry reports to prevent data leakage
- Add PrimaryDB, PrimaryPassword, SecondaryDB, SecondaryPassword to ProxyConfig
- Add DB and Password fields to BackendOptions, passed to go-redis client
- Add CLI flags: -primary-db, -primary-password, -secondary-db, -secondary-password
- Silently accept SELECT and AUTH commands (return OK without forwarding)
  since DB/auth are configured at the connection-pool level
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a Redis-protocol proxy service (redis-proxy) that can route reads/writes across two Redis-protocol backends (Redis + ElasticKV), with optional shadow reads for divergence detection and support for pub/sub forwarding via detached redcon connections.

Changes:

  • Introduces the proxy server core (command classification/routing, dual-write + shadow-read logic, RESP response writer, transaction handling).
  • Adds pub/sub support using redcon.Detach() + go-redis PubSub forwarding, including a “return to normal mode” when unsubscribed.
  • Adds observability: Prometheus metrics + optional Sentry reporting, plus unit/integration tests for non-pubsub behavior.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
cmd/redis-proxy/main.go CLI entrypoint wiring config, backends, metrics endpoint, and graceful shutdown.
proxy/backend.go Backend abstraction and go-redis implementation (including a dedicated PubSub connection).
proxy/command.go Redis command classification table for routing decisions.
proxy/config.go Proxy config + modes (redis-only, dual-write, shadow, elastickv-primary/only).
proxy/dualwrite.go Dual-writer/shadow-reader execution paths + bounded async work.
proxy/compare.go Shadow-read comparison, divergence classification, and divergence reporting hook.
proxy/metrics.go Prometheus metrics definitions/registration.
proxy/sentry.go Optional Sentry reporter with cooldown-based de-duplication support.
proxy/proxy.go redcon server, RESP writing helpers, command dispatch, and transaction support.
proxy/pubsub.go Detached pub/sub session implementation and upstream message forwarding.
proxy/proxy_test.go Unit tests for core proxy components (classification, dual-write, sentry gating, etc.).
proxy/integration_test.go Integration tests against real Redis instances for basic command flows.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

bootjp added 3 commits March 18, 2026 05:01
proxy/sentry: fix exhaustive lint error in truncateValue
- exitPubSubMode and cleanup now close dconn after timeout to unblock
  forwardMessages stuck on Flush, preventing writeMu deadlock
- Add nolint:exhaustive for reflect.Kind switch that intentionally
  uses default for all non-container types
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a Redis-protocol “redis-proxy” that can dual-write to a primary (Redis) and secondary (ElasticKV) backend, with shadow-read divergence detection and full Pub/Sub forwarding using redcon detached connections.

Changes:

  • Added a detached Pub/Sub session implementation that forwards upstream Pub/Sub messages and supports transitioning back to normal command mode without reconnecting.
  • Implemented dual-write + optional shadow-read comparison with Prometheus metrics and Sentry reporting/deduplication.
  • Added unit and integration tests for command classification, txn handling, Pub/Sub behavior, and dual-write flows.

Reviewed changes

Copilot reviewed 14 out of 15 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
proxy/backend.go Adds backend abstraction + Redis backend implementation (Do/Pipeline/PubSub).
proxy/command.go Adds command classification table for routing (read/write/blocking/pubsub/admin/txn/script).
proxy/compare.go Adds shadow-read comparison and divergence classification logic.
proxy/config.go Adds proxy configuration + mode parsing/defaults.
proxy/dualwrite.go Implements dual-write, shadow reads, bounded async execution, and metrics/Sentry hooks.
proxy/metrics.go Adds Prometheus metrics definitions + registration.
proxy/proxy.go Implements the redcon server, command dispatch, txn handling, and Pub/Sub session entry.
proxy/pubsub.go Implements detached Pub/Sub session, message forwarding, and Pub/Sub-mode command loop.
proxy/sentry.go Adds Sentry reporter with cooldown-based de-duplication and value truncation helpers.
proxy/proxy_test.go Adds unit tests for classification, divergence logic, Sentry cooldown, dual-write behavior, etc.
proxy/pubsub_test.go Adds unit tests for detached Pub/Sub session behavior and RESP writes.
proxy/integration_test.go Adds integration tests against real Redis instances (skipped if unavailable).
cmd/redis-proxy/main.go Adds a runnable redis-proxy binary with flags + metrics HTTP server.
go.mod / go.sum Adds Sentry dependency and updates module sums.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +87 to +97
func (b *RedisBackend) Pipeline(ctx context.Context, cmds [][]any) ([]*redis.Cmd, error) {
pipe := b.client.Pipeline()
results := make([]*redis.Cmd, len(cmds))
for i, args := range cmds {
results[i] = pipe.Do(ctx, args...)
}
_, err := pipe.Exec(ctx)
if err != nil {
return results, fmt.Errorf("pipeline exec: %w", err)
}
return results, nil
proxy/proxy.go Outdated
Comment on lines +356 to +363
if err != nil {
// Pipeline-level error (connection/transport failure) takes precedence.
writeRedisError(conn, err)
} else if len(results) > 0 {
// Write the EXEC result (last command in the pipeline).
lastResult := results[len(results)-1]
resp, rErr := lastResult.Result()
writeResponse(conn, resp, rErr)
Comment on lines +391 to +401
results, err := s.proxy.dual.Primary().Pipeline(ctx, cmds)

s.writeMu.Lock()
if err != nil {
// Pipeline-level error (connection/transport failure) takes precedence.
writeRedisError(s.dconn, err)
} else if len(results) > 0 {
lastResult := results[len(results)-1]
resp, rErr := lastResult.Result()
writeResponse(s.dconn, resp, rErr)
}
proxy/pubsub.go Outdated
Comment on lines +524 to +552
s.writeMu.Lock()
defer s.writeMu.Unlock()

if len(set) == 0 {
// No subscriptions: single reply with null channel (matching Redis).
s.dconn.WriteArray(pubsubArrayReply)
s.dconn.WriteBulkString(kind)
s.dconn.WriteNull()
s.dconn.WriteInt64(int64(s.subCount()))
_ = s.dconn.Flush()
return
}

// Collect names, then remove one-by-one to decrement count per reply.
names := make([]string, 0, len(set))
for n := range set {
names = append(names, n)
}

for _, n := range names {
if isPattern {
delete(s.patternSet, n)
} else {
delete(s.channelSet, n)
}
s.dconn.WriteArray(pubsubArrayReply)
s.dconn.WriteBulkString(kind)
s.dconn.WriteBulkString(n)
s.dconn.WriteInt64(int64(s.subCount()))
bootjp added 2 commits March 18, 2026 13:51
- Pipeline now returns results with nil error for redis.Error/redis.Nil,
  only propagating transport/context errors. This preserves Redis
  transaction semantics in execTxn callers.
- handleUnsub and writeUnsubAll now mutate goroutine-confined state
  and pre-compute counts before acquiring writeMu, keeping writeMu
  strictly for dconn write serialization.
Implement shadow pub/sub that subscribes to the secondary backend for
the same channels/patterns and compares received messages against the
primary using a time-windowed matching strategy.

- New shadowPubSub struct with sliding-window message comparison
- Messages matched by (channel, payload) key within configurable window
- Primary-only messages after window expiry reported as data_mismatch
- Secondary-only messages with no primary match reported as extra_data
- All shadow operations are fire-and-forget (never block primary path)
- Active only in ModeDualWriteShadow / ModeElasticKVPrimary
- New metrics: pubsub_shadow_divergences_total, pubsub_shadow_errors_total
- New config: PubSubCompareWindow (default 2s)
- Fix Pipeline to not wrap redis.Error (preserves EXEC result semantics)
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a Redis-protocol proxy (redis-proxy) that supports dual-write routing across primary (Redis) and secondary (ElasticKV) backends, including Pub/Sub support with optional “shadow” comparison against the secondary to detect divergence.

Changes:

  • Introduces a detached Pub/Sub session handler that forwards upstream Pub/Sub messages to clients while allowing normal commands once subscriptions are cleared.
  • Adds shadow Pub/Sub comparison logic and new Prometheus/Sentry reporting for Pub/Sub divergences.
  • Adds extensive unit and integration tests covering routing, transactions, Pub/Sub session behavior, and shadow comparison.

Reviewed changes

Copilot reviewed 16 out of 17 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
proxy/pubsub.go Implements detached Pub/Sub session handling, forwarding, and transition back to normal command mode
proxy/shadow_pubsub.go Adds shadow Pub/Sub subscriber that matches primary vs secondary messages and reports divergences
proxy/sentry.go Adds Sentry reporter with cooldown-based de-duplication and value truncation helpers
proxy/metrics.go Introduces Prometheus metrics for proxy operations and Pub/Sub shadowing
proxy/proxy.go Wires Pub/Sub sessions into the main redcon handler; creates shadow Pub/Sub based on mode
proxy/dualwrite.go Implements dual-write + shadow-read routing with bounded async execution
proxy/*_test.go Adds unit tests for Pub/Sub session logic, shadow Pub/Sub matching, and core proxy behaviors
proxy/integration_test.go Adds integration tests that exercise the proxy against real Redis instances
cmd/redis-proxy/main.go Adds CLI entrypoint and metrics server wiring
go.mod / go.sum Updates dependencies (adds sentry-go; removes raft-boltdb related deps)

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +150 to +154
// Record for shadow comparison (outside writeMu to avoid nested locking).
if s.shadow != nil {
s.shadow.RecordPrimary(msg)
}
}
bootjp and others added 3 commits March 18, 2026 17:46
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
- Track started flag so Close is safe before Start (prevents deadlock
  when subscribe fails during setup)
- Guard s.shadow access in forwardMessages with mu to prevent data race
  with concurrent cleanup/exitPubSubMode
- Move closeShadow after fwdDone wait so RecordPrimary completes first
- Remove unused reportDivergenceLocked
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Implements a Redis-protocol proxy that supports dual-write to primary/secondary backends, adds shadow-read/pubsub divergence detection, and exposes observability (Prometheus + Sentry) around mismatches and failures.

Changes:

  • Added detached Pub/Sub session handling (SUBSCRIBE/PSUBSCRIBE, idempotent counts, txn handling in normal mode) and optional shadow pub/sub comparison.
  • Added Sentry reporter with cooldown-based de-duplication and value truncation to reduce data leakage / event size.
  • Added Prometheus metrics, extensive unit tests, and an integration test suite plus a redis-proxy CLI.

Reviewed changes

Copilot reviewed 16 out of 17 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
proxy/shadow_pubsub.go Implements shadow pub/sub comparator between primary and secondary streams.
proxy/shadow_pubsub_test.go Unit tests for shadow pub/sub matching/sweeping/divergence metrics.
proxy/pubsub.go Detached pub/sub session implementation bridging redcon ↔ go-redis, with shadow mirroring hooks.
proxy/pubsub_test.go Unit tests for pub/sub session command dispatch, unsubscribe behavior, txn handling, and wire writes.
proxy/sentry.go Sentry reporting with fingerprint cooldown + truncation helpers.
proxy/metrics.go Prometheus metrics definitions/registration, including pub/sub shadow metrics.
proxy/dualwrite.go Dual-write routing + bounded async executor + shadow read support.
proxy/proxy.go Main redcon server: command routing, transaction support, pub/sub session bootstrap, shadow pub/sub creation.
proxy/compare.go Shadow-read comparison logic, divergence classification, and divergence reporting.
proxy/command.go Command classification table (read/write/blocking/pubsub/admin/txn/script).
proxy/backend.go Backend abstraction + Redis backend implementation with pipelining and pub/sub support.
proxy/proxy_test.go Broad unit test coverage for command classification, config, shadow reader, dual writer, sentry, etc.
proxy/integration_test.go End-to-end integration tests against real Redis instances (skipped if unavailable).
cmd/redis-proxy/main.go New CLI entrypoint: configuration flags, metrics server, and proxy startup/shutdown.
go.mod Adds direct dependency on sentry-go; removes unused raft-boltdb dependency.
go.sum Dependency checksum updates reflecting module graph changes.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

bootjp and others added 3 commits March 18, 2026 19:46
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
…, divergence reporting

- Include Pattern in msgKey for correct pmessage matching
- Release lock before reporting in matchSecondary to avoid blocking I/O under mu
- Add sweepAll to flush all pending on channel close (not just expired)
- Set Primary/Secondary in reportDivergence based on DivergenceKind
- Guard s.shadow assignment in reenterPubSub with mu
- Promote divergenceEvent to package-level type shared by sweep methods
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a Redis-protocol proxy server that can route commands to a primary backend (Redis) while optionally dual-writing to a secondary backend (ElasticKV), including support for Pub/Sub with optional shadow (compare) subscriptions for divergence detection.

Changes:

  • Added ProxyServer + DualWriter command routing (read/write/blocking/admin/script/txn) with Prometheus metrics and optional Sentry reporting.
  • Implemented detached Pub/Sub session handling (SUBSCRIBE/PSUBSCRIBE, unsubscribe semantics, ping/quit, txn behavior in normal mode) plus shadow Pub/Sub comparison.
  • Added unit/integration tests for command classification, divergence detection, pub/sub session behavior, and shadow pub/sub matching.

Reviewed changes

Copilot reviewed 16 out of 17 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
proxy/proxy.go Core redcon server handler; detaches connections for pub/sub sessions; creates shadow pub/sub.
proxy/pubsub.go Implements detached pub/sub session state machine, forwarding, and command handling.
proxy/shadow_pubsub.go Shadow subscriber comparison logic + divergence reporting for pub/sub messages.
proxy/sentry.go Sentry reporter with cooldown-based dedup + truncation helpers.
proxy/metrics.go Prometheus metrics definitions/registration (including pub/sub shadow metrics).
proxy/*.go tests Unit/integration tests for the new proxy, pub/sub handling, and shadow behavior.
cmd/redis-proxy/main.go New executable wiring config/metrics/sentry/backends and starting the proxy.
go.mod, go.sum Dependency updates (notably adding Sentry).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

proxy/metrics.go Outdated
Namespace: "proxy",
Name: "pubsub_shadow_divergences_total",
Help: "Total pub/sub message mismatches detected by shadow subscribe.",
}, []string{"channel", "kind"}),
bootjp and others added 4 commits March 18, 2026 23:07
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
}

// sweepExpired reports primary messages that were not matched within the window.
func (sp *shadowPubSub) sweepExpired() {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 [golangci] reported by reviewdog 🐶
calculated cyclomatic complexity for function sweepExpired is 12, max is 10 (cyclop)

bootjp added 2 commits March 19, 2026 13:45
Correctness:
- Remove redis.Nil double-wrapping in DualWriter methods
- Add empty transaction result handling in execTxn
- Add WATCH/UNWATCH to command table
- Intercept HELLO to prevent RESP3 upgrade
- Reject SELECT for non-configured DB
- Fix UNSUBSCRIBE to emit per-argument replies

Concurrency/distributed failures:
- Split asyncSem into writeSem + shadowSem to prevent starvation
- Add DualWriter.Close() with WaitGroup for graceful shutdown drain
- Add AsyncDrops metric for semaphore backpressure visibility

Performance:
- Uppercase command name once in handleCommand, pass through to DualWriter
- Remove channel label from PubSubShadowDivergences to prevent cardinality explosion

Test coverage:
- Add writeRedisValue/writeRedisError/writeResponse tests
- Add forwardMessages message/pmessage branch tests
- Add reenterPubSub error path tests
- Add truncateValue type coverage tests
- Add ClassifyCommand tests for new commands
- Add Pipeline transport error test
matchSecondary now buffers unmatched secondaries instead of reporting
immediately. Adjust the test to wait for the comparison window and
call sweepExpired to trigger divergence reporting.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants