Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
53f36d4
feat(gateway): serve the native wavekv v2 sync and push routes
kvinwang Aug 8, 2026
7ef510b
feat(gateway): confine replicated keys to the gateway schema
kvinwang Aug 8, 2026
00ff2c5
feat(gateway): report state digest and peer protocol in WaveKvStatus
kvinwang Aug 8, 2026
45c4daf
test(gateway): pin the wavekv sync route paths
kvinwang Aug 8, 2026
5d02fe7
feat(gateway): surface peers that fail every sync round
kvinwang Aug 8, 2026
83f2a65
test(gateway): drive the sync routes over a local Rocket client
kvinwang Aug 8, 2026
a3b05a6
chore(gateway): pick up wavekv responder identity check
kvinwang Aug 8, 2026
845598d
chore(gateway): pick up wavekv uuid-check revert
kvinwang Aug 8, 2026
69e76ce
fix(gateway): bound decompression on the sync routes
kvinwang Aug 9, 2026
e95a4c4
fix(gateway): allocate this node's own records after the sync bootstrap
kvinwang Aug 9, 2026
422396e
chore(gateway): pin wavekv at the reviewed fixes
kvinwang Aug 9, 2026
cb2b165
chore(gateway): pin wavekv at the tombstone-GC and membership-durabil…
kvinwang Aug 9, 2026
4827ba7
test(gateway): cover the sync routes' authentication gate
kvinwang Aug 10, 2026
495d2b3
test(gateway): cover the v1 sync shim at the route level
kvinwang Aug 10, 2026
f6c0f63
test(gateway): pin the client-side identity check, the sync limits an…
kvinwang Aug 10, 2026
d9351c7
chore(gateway): pin wavekv at the bootstrap ack-adoption guard
kvinwang Aug 10, 2026
a3d467a
chore(gateway): pin wavekv at the divergence-gate and cross-page test…
kvinwang Aug 10, 2026
5b3eeed
chore(gateway): pin wavekv at the remove_peer durability fix
kvinwang Aug 10, 2026
9734d87
chore(gateway): pin wavekv at the v1 rollback membership coverage
kvinwang Aug 10, 2026
ca18064
test(gateway): exercise protocol negotiation against a real TLS peer
kvinwang Aug 10, 2026
e28384f
test(gateway): close the remaining transport gaps
kvinwang Aug 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 18 additions & 28 deletions dstack/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion dstack/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,8 @@ serde-duration = { path = "serde-duration" }
dstack-mr = { path = "dstack-mr" }
dstack-verifier = { path = "verifier", default-features = false }
size-parser = { path = "size-parser" }
wavekv = "1.0.0"
# TODO: repoint to `wavekv = "2.0"` once Phala-Network/wavekv#3 is released to crates.io.
wavekv = { git = "https://github.com/Phala-Network/wavekv", branch = "feat/delta-state-sync" }

# Core dependencies
anyhow = { version = "1.0.97", default-features = false }
Expand Down
22 changes: 21 additions & 1 deletion dstack/gateway/rpc/proto/gateway_rpc.proto
Original file line number Diff line number Diff line change
Expand Up @@ -339,9 +339,22 @@ message PeerSyncStatus {
uint32 id = 1;
uint64 local_ack = 2;
uint64 peer_ack = 3;
uint64 buffered_logs = 4;
// Always 0 since wavekv 2.0, which replicates state instead of operation logs and
// keeps no per-peer log buffers. Retained so existing clients keep decoding.
uint64 buffered_logs = 4 [deprecated = true];
// Last seen timestamps: [(observer_node_id, timestamp), ...]
repeated LastSeenEntry last_seen = 5;
// Whether this peer has ever reported an ack map.
bool heard_from = 6;
// Sync protocol last negotiated with this peer: "v1" or "v2".
string protocol = 7;
// Consecutive quiescent rounds whose state digests disagreed. Non-zero means the
// replicas have silently diverged; wavekv 1.x could not detect this at all.
uint32 digest_mismatches = 8;
// Consecutive sync rounds that failed outright. Only a definitive 404/405 demotes a
// peer to "v1"; a 5xx or a timeout leaves `protocol` untouched by design, so this is
// the only field that moves when a peer is failing every round.
uint32 consecutive_failures = 9;
}

message LastSeenEntry {
Expand All @@ -358,6 +371,13 @@ message StoreSyncStatus {
bool dirty = 5;
bool wal_enabled = 6;
repeated PeerSyncStatus peers = 7;
// Hex SHA-256 over the replicated state. Two converged replicas produce equal
// digests by construction, so comparing this across the cluster is the promotion
// gate for the wavekv v2 rollout and the standing divergence check afterwards.
string digest = 8;
uint64 entries_merged = 9;
// Entries refused by the admission policy or the ingest quotas.
uint64 entries_rejected = 10;
}

// WaveKV sync status response
Expand Down
32 changes: 30 additions & 2 deletions dstack/gateway/src/admin_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -208,16 +208,33 @@ impl AdminRpc for AdminRpcHandler {
.collect()
};

// Per-peer protocol/digest telemetry lives on the sync manager, not the store.
let links = self
.state
.wavekv_sync
.as_ref()
.map(|s| s.link_status())
.unwrap_or_default();
let links_for = |name: &str| -> Vec<wavekv::sync::PeerLinkStatus> {
links
.iter()
.find(|(store, _)| *store == name)
.map(|(_, l)| l.clone())
.unwrap_or_default()
};

Ok(WaveKvStatusResponse {
enabled: self.state.config.sync.enabled,
persistent: Some(build_store_status(
"persistent",
persistent_status,
&links_for("persistent"),
&get_peer_last_seen,
)),
ephemeral: Some(build_store_status(
"ephemeral",
ephemeral_status,
&links_for("ephemeral"),
&get_peer_last_seen,
)),
})
Expand Down Expand Up @@ -718,6 +735,7 @@ fn port_policy_view_to_proto(view: PortPolicyView) -> GetInstancePortPolicyRespo
fn build_store_status(
name: &str,
status: WaveKvNodeStatus,
links: &[wavekv::sync::PeerLinkStatus],
get_peer_last_seen: &impl Fn(u32) -> Vec<(u32, u64)>,
) -> StoreSyncStatus {
StoreSyncStatus {
Expand All @@ -727,6 +745,9 @@ fn build_store_status(
next_seq: status.next_seq,
dirty: status.dirty,
wal_enabled: status.wal,
digest: status.digest,
entries_merged: status.entries_merged,
entries_rejected: status.entries_rejected,
peers: status
.peers
.into_iter()
Expand All @@ -735,12 +756,19 @@ fn build_store_status(
.into_iter()
.map(|(node_id, timestamp)| LastSeenEntry { node_id, timestamp })
.collect();
let link = links.iter().find(|l| l.id == p.id);
#[allow(deprecated)]
ProtoPeerSyncStatus {
id: p.id,
local_ack: p.ack,
peer_ack: p.pack,
buffered_logs: p.logs as u64,
peer_ack: p.peer_ack,
// wavekv 2.0 keeps no per-peer log buffers.
buffered_logs: 0,
last_seen,
heard_from: p.heard_from,
protocol: link.map(|l| l.protocol).unwrap_or_default().to_string(),
digest_mismatches: link.map(|l| l.digest_mismatches).unwrap_or(0),
consecutive_failures: link.map(|l| l.consecutive_failures).unwrap_or(0),
}
})
.collect(),
Expand Down
Loading
Loading