Skip to content

[Store] HA optlog support redis backed - #4295

Merged
Icedcoco merged 11 commits into
kvcache-ai:mainfrom
wjd6910502:main
Oct 10, 2026
Merged

Icedcoco merged 11 commits into
kvcache-ai:mainfrom
wjd6910502:main

Conversation

@wjd6910502

@wjd6910502 wjd6910502 commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

Description

A Redis HA cluster could already elect a leader, but the batch OpLog could not follow it. OrderedOpLogWriter and the standby reader talk to a HaKvBackend: one compare-and-swap transaction per batch, then a lexicographic range to catch up. Only the etcd backend implemented that contract, so --enable_oplog=true with ha_backend_type=redis was rejected at startup, and a cluster that already ran Redis still needed a second etcd just to store acknowledged batches. Leadership and the log are separate; this change gives the log a Redis implementation and leaves snapshots on etcd.

What changes

RedisHaKvBackend implements the existing KV contract. The writer, the standby reader, and the applier are unchanged. CreateHaKvBackend is the only place that opens the backend, and the master writer, ClaimProducerView, and the standby follower all go through it. etcd still takes the etcd branch. k8s is rejected. A build without STORE_USE_REDIS or STORE_USE_ETCD returns UNAVAILABLE_IN_CURRENT_MODE from the factory, so the no-backend stub that aborts the process is never called.

Logical keys stay /oplog/{cluster}/producer_view, durable_prefix, and batches/{id}. Redis stores the bytes at mooncake-store/{cluster}/oplog/... and orders those logical keys in the ZSET mooncake-store/{tag}/oplog-index. The hash tag puts the index and the records on one slot, so the Lua script is valid on Redis Cluster. The connection is the backend's own; it is not the connection the leader renew thread uses.

One acknowledged batch is one EVAL. The script checks that producer_view equals the claimed view and durable_prefix equals the expected prefix, then writes the batch record and advances the prefix. A failed compare returns ETCD_TRANSACTION_FAIL. A reply that never comes back resets the connection and returns ETCD_OPERATION_ERROR; the existing read-back path treats that as ambiguous and reconciles. kCreateRevisionEquals returns INVALID_PARAMS before any command is sent. Redis has no create revision, and the snapshot maintenance lease still needs one.

Splitting the log from the snapshot leaves two races the commit of a batch already had, and the Redis mapping has to preserve them:

A range or a delete is [begin, end). limit 0 means the whole interval; a Redis COUNT of 0 would return nothing. The next page starts at last_key + '\0'. Bounds that are empty, a single NUL, or begin > end are rejected, and one transaction may not touch two cluster ids.

A dropped connection can hide a script that did commit. The backend does not retry that call. The caller reads the prefix back and continues from what Redis actually stored.

Behavior change: --enable_oplog on Redis. With STORE_USE_REDIS and a connstring, ha_backend_type=redis now creates the batch writer and the standby follows the same Redis. Before, that combination fataled, and an empty connstring still skips the writer. enable_oplog_snapshot and snapshot restore still require ha_backend_type=etcd. Redis has to run AOF with appendfsync everysec or always; a restart without it can drop batches the master has already acknowledged.

Smaller changes:

HaBackendSupportsOpLog is one switch on HABackendType. Adding a backend is that case plus a factory branch.
The shard scan copies buffer descriptors only for replicas on the segments being re-adopted. The Redis range copies values only for keys inside the requested interval.
NoF placeholder allocators stay out of this path. Nothing in the Redis backend adopts them.
The deployment guide states that the same Redis holds leader election and the batch log, and that clients still use the redis:// master address. The restore-side log is unchanged.
CreateRevisionCompareIsRejected always runs. The four integration tests run only when the binary is built with STORE_USE_REDIS and --redis_endpoint is set; gtest_main does not parse gflags, so this target supplies its own main.

Module

  • Transfer Engine (mooncake-transfer-engine)
  • Mooncake Store (mooncake-store)
  • Mooncake Conductor (mooncake-conductor)
  • Reshard (mooncake-reshard)
  • Mooncake EP (mooncake-ep)
  • Mooncake PG (mooncake-pg)
  • Integration (mooncake-integration)
  • P2P Store (mooncake-p2p-store)
  • Python Wheel (mooncake-wheel)
  • Common (mooncake-common)
  • Mooncake RL (mooncake-rl)
  • CI/CD
  • Docs
  • Other

Type of Change

  • Bug fix
  • New feature
  • Refactor
  • Breaking change
  • Documentation update
  • Performance improvement
  • Other

How Has This Been Tested?

Test commands:
redis-server --port 6379 --daemonize yes
./build/mooncake-store/tests/redis_ha_kv_backend_test
--redis_endpoint=127.0.0.1:6379

Test results:

  • Unit tests pass
  • Integration tests pass (if applicable)
  • Manual testing done (describe below)

Checklist

  • I have performed a self-review of my own code
  • I have formatted my code using ./scripts/code_format.sh
  • I have run pre-commit on the files changed in this PR and all hooks pass
  • I have updated the documentation (if applicable)
  • I have added tests to prove my changes are effective
  • For changes >500 LOC: I have filed an RFC issue

AI Assistance Disclosure

  • No AI tools were used
  • AI tools were used (specify below)
    Codex assisted with the RedisHaKvBackend implementation (Lua CAS and ZSET range/delete), wiring CreateHaKvBackend into the master, standby, and supervisor, the Redis integration tests, and the deployment-guide notes for Redis OpLog. The test target now parses gflags itself so --redis_endpoint takes effect. The submitter has reviewed every changed line and is responsible for the change.

@Icedcoco

Copy link
Copy Markdown
Collaborator

Thanks for working on Redis-backed OpLog support. The overall direction makes sense: it removes the extra etcd dependency for deployments that already use Redis for HA leadership, while reusing the existing HaKvBackend contract.

I have a few concerns that I think should be discussed before merging.

  1. Redis persistence semantics need to be defined more strictly

    The current documentation recommends either appendfsync everysec or always. I do not think everysec is safe for this OpLog use case. It can acknowledge a batch and then lose only part of the recent history after a crash. That is more dangerous than losing the entire Redis dataset, because a partially missing OpLog can leave the standby with an inconsistent state and make promotion unsafe.

    Could we choose one of these two explicit models?

    • No durability guarantee: Redis is treated as an ephemeral coordination/log store. If Redis loses its data, the system detects the empty or invalid OpLog namespace and rebuilds the whole cluster from zero.
    • Strict durability: every acknowledged OpLog write is synchronously persisted, using appendfsync always or an equivalent mechanism. A single acknowledged batch must not disappear independently.

    I would avoid a middle ground where only part of the acknowledged OpLog may be lost. The current implementation treats a successful transaction as committed, so the persistence guarantee needs to match that assumption.

  2. The Redis failure/recovery behavior should be tested explicitly

    The current tests cover the KV mapping and Lua transaction behavior, but they do not appear to cover:

    • primary writing batches and standby applying them through Redis;
    • leader promotion followed by continued OpLog writes;
    • a connection failure after Redis has committed the Lua script;
    • Redis restart with either a completely empty dataset or a partially missing dataset.

    A focused HA-level test would help establish the intended recovery semantics.

  3. The PR contains unrelated repository changes

    Please also remove the unrelated deletion of the extern/pybind11 submodule and the committed Vim swap file:

    • extern/pybind11
    • mooncake-store/src/ha/kv/.ha_kv_backend_factory.cpp.swp

    The submodule deletion currently causes a normal non-SKBUILD CMake configure to fail because the top-level build unconditionally calls add_subdirectory(extern/pybind11).

  4. Redis Cluster support

    The PR description mentions Redis Cluster hash-slot compatibility. The implementation uses the regular hiredis connection API, so it is worth clarifying whether Redis Cluster is actually supported. Hash tags ensure that the keys share a slot, but the client still needs to connect to the node that owns that slot or use a cluster-aware client/proxy.

The feature itself is useful, but I would prefer to settle the durability model and recovery behavior before merging. Once those semantics are explicit and covered by tests, the Redis-backed OpLog design should be much easier to review and operate safely.

@wjd6910502

Copy link
Copy Markdown
Contributor Author

@Icedcoco about first question,my thinking is below: Thanks for raising this. I agree that the current wording is too ambiguous about what a successful OpLog write/transaction guarantees.

The key point is that appendfsync everysec should not be presented as providing strict durability for acknowledged OpLog batches. A partial loss of an acknowledged batch could indeed leave the standby with an incomplete OpLog and make promotion unsafe.

For the current implementation, I propose to explicitly define Redis as an ephemeral OpLog/coordination store, rather than claiming strict durability. In particular:

A successful Redis transaction means that Redis accepted the write, but does not imply durable commitment.

We will not rely on Redis persistence for safe promotion.

If the OpLog namespace is missing, empty, or fails validation after recovery, the system will treat the Redis state as lost and rebuild the cluster state from the authoritative source.

appendfsync everysec can therefore remain the default operational setting, since the correctness model does not depend on recovering acknowledged OpLog entries from Redis.

I agree that if we want to support promotion from the Redis/standby OpLog without a full rebuild, we would need a stronger contract: explicit commit semantics, replication guarantees, and a rule that an acknowledged batch is fully recoverable.

I will update the documentation to make this distinction explicit and avoid implying that appendfsync everysec provides strict OpLog durability.

@wjd6910502

Copy link
Copy Markdown
Contributor Author

@Icedcoco Thanks for the detailed review. I have addressed the issues raised above.

  1. Redis failure/recovery behavior

    Fixed. I added HA-level tests covering the Redis-backed OpLog recovery scenarios, including:

    • primary writing batches and standby applying them through Redis;
    • leader promotion followed by continued OpLog writes;
    • connection failure after the Redis Lua transaction has committed;
    • Redis restart with an empty or invalid/partially missing OpLog state.

    These tests also make the intended recovery behavior explicit.

  2. Unrelated repository changes

    Fixed. I removed the unrelated changes:

    • extern/pybind11
    • mooncake-store/src/ha/kv/.ha_kv_backend_factory.cpp.swp

    The normal non-SKBUILD CMake configuration now works as expected.

  3. Redis Cluster support

    This was a mistake in the PR description rather than an intended feature of this implementation.

    The current implementation uses the regular hiredis connection API and is not intended to provide Redis Cluster support. While the use of hash tags makes the related keys map to the same Redis Cluster slot, that alone does not make the client Cluster-aware or guarantee that the connection is established to the node owning the slot.

    I have corrected the PR description to remove the Redis Cluster support claim. Redis Cluster support, if needed in the future, would require a separate change using a Cluster-aware client or an appropriate proxy layer.

Thanks again for pointing these out. The unrelated repository changes and the incorrect Cluster support description have been fixed, and the HA recovery behavior is now covered by focused tests.

@wjd6910502
wjd6910502 requested a review from Icedcoco October 9, 2026 12:55
@Icedcoco
Icedcoco merged commit 981f287 into kvcache-ai:main Oct 10, 2026
28 checks passed
Aionw added a commit to Icedcoco/Mooncake that referenced this pull request Oct 12, 2026
Removing hot_standby_snapshot_bootstrap_test.cpp also dropped every
StandbyControllerTest case, including the Redis promotion gate that kvcache-ai#4295
added. Move the cases that do not depend on the legacy catalog snapshot
path into standby_controller_test.cpp, and point the real-etcd snapshot
smoke back at the lifecycle case. Cases that configure the removed
enable_snapshot_restore and snapshot_catalog_store_type options stay
deleted.
LujhCoconut added a commit to LujhCoconut/Mooncake that referenced this pull request Oct 15, 2026
Mainline kvcache-ai#4295 removed this include block; a conflict resolution kept it.
No symbol from either header is used in this file.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation run-ci Store

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants