You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
bug: wildcard watch on apiexport virtual workspace fails with 'Too large resource version' #4497
Watch requests on the APIExport virtual workspace wildcard path (/services/apiexport/<export>/clusters/*/<resource>?watch=true) immediately fail with:
Too large resource version: <list RV>, current: <watch cache RV> (HTTP 504)
The client's informer then spins in a retry loop without ever receiving events. Sync agents effectively only get data from the initial LIST; afterwards no watch events are delivered — objects change on the fly unnoticed.
To Reproduce
Run kcp v0.33.1 with an APIExport virtual workspace.
With agent credentials: kubectl get --raw "<VW-url>/clusters/*/<resource>?watch=true" → immediate Too large resource version error.
The same LIST (without watch=true) succeeds and returns a resource version ahead of the VW watch cache; the delta never closes.
Observed with api-syncagent v0.7.0 as the client: it only picks up changes after a restart (fresh LIST).
Expected behavior
Wildcard watch on the virtual workspace delivers events; the VW watch cache catches up to the LIST resource version.
Additional context
Diagnosis from our side: LIST returns the RV from etcd, while the VW watch cache lags behind by a persistent delta. Restarting the shard and the agents does not help.
Seen in production: kcp v0.33.1, api-syncagent v0.7.0, wildcard VW of a platform APIExport.
i could not reproduce this locally on the current main branch of kcp, @2mind
do you have a reproducer script, that e2e builds kcp, runs it and produces the bug?
Update from our side — we found the root cause in our deployment and can explain why this is hard to reproduce:
Why we couldn't reproduce it with load. We tried extensively: local single-shard and 2-replica setups, sustained multi-tenant churn (up to ~900 rev/s), 190 workspaces with APIBindings, deletion waves, and rolling restarts of all shards under load — hundreds of wildcard LIST→watch probes, zero TooLargeResourceVersion. The trigger is not write throughput; on a healthy cacher the 3s blockTimeout window is unreachable.
What actually broke for us. Our production kcp (fork base e006560fc76a, v0.33.1) had WatchList structurally disabled:
experimental-watch-progress-notify-interval was not set on etcd.
The shard ran without the server-side WatchList feature gate — with the gate off, CheckClient is never even called (see store.New).
Result: every client (sync agents included) fell back to LIST+watch. The wildcard LIST returns the etcd RV, the watch connects to a replica cacher that is behind, waits up to blockTimeout, and returns TooLargeResourceVersion → client-go relists → gets a fresh etcd RV again → repeats. The lag was persistent in production (post-restart + large accumulated dataset), which is why restarting the agent (landing on a healthy replica / fresh cacher) temporarily fixed it while shard restarts did not.
What fixed it for us (all three required, verified live — sendInitialEvents on the wildcard VW now returns the k8s.io/initial-events-end bookmark):
etcd ≥ 3.5.13 (we run 3.5.21),
experimental-watch-progress-notify-interval=1s on etcd,
WatchList=true feature gate on the shard, and clients migrated to WatchList.
After the migration: churn ~145 ops/s across 100+ logical clusters + rolling restart of all shards → 0 TooLarge, agents keep syncing.
What we think is still worth fixing upstream (our issue was filed against the fork's behavior, but the semantics are upstream kcp design):
A watch established with an RV sourced from an etcd-backed LIST should not surface TooLargeResourceVersion to the client as a terminal-ish error; returning a retryable 410 Gone (or making wildcard LIST cache-consistent) would turn the infinite relist loop into a bounded retry.
Worth documenting the etcd ≥ 3.5.13 requirement for WatchList — the failure mode (silent "RequestWatchProgress is disabled" despite etcd accepting the flag) is expensive to diagnose; ours took a full root-cause pass through the fork source.
Happy to share our repro/measure scripts (storm + wildcard-watch victim probe) if useful.
Activity
neolit123 commentedon Oct 30, 2026
i could not reproduce this locally on the current main branch of kcp, @2mind
do you have a reproducer script, that e2e builds kcp, runs it and produces the bug?
2mind commentedon Nov 4, 2026
Update from our side — we found the root cause in our deployment and can explain why this is hard to reproduce:
Why we couldn't reproduce it with load. We tried extensively: local single-shard and 2-replica setups, sustained multi-tenant churn (up to ~900 rev/s), 190 workspaces with APIBindings, deletion waves, and rolling restarts of all shards under load — hundreds of wildcard LIST→watch probes, zero TooLargeResourceVersion. The trigger is not write throughput; on a healthy cacher the 3s
blockTimeoutwindow is unreachable.What actually broke for us. Our production kcp (fork base e006560fc76a, v0.33.1) had WatchList structurally disabled:
etcdfeature.DefaultFeatureSupportCheckeronly enablesRequestWatchProgressfor etcd ≥ 3.5.13 (endpointSupportsRequestWatchProgress, hard-coded version gate due to Etcd not respond to progress notification on newly created watch RPC until there is a event in a watch etcd-io/etcd#17507). We ran etcd v3.5.12 — the check silently returned "unsupported" even with the etcd flag set.experimental-watch-progress-notify-intervalwas not set on etcd.WatchListfeature gate — with the gate off,CheckClientis never even called (seestore.New).Result: every client (sync agents included) fell back to LIST+watch. The wildcard LIST returns the etcd RV, the watch connects to a replica cacher that is behind, waits up to
blockTimeout, and returnsTooLargeResourceVersion→ client-go relists → gets a fresh etcd RV again → repeats. The lag was persistent in production (post-restart + large accumulated dataset), which is why restarting the agent (landing on a healthy replica / fresh cacher) temporarily fixed it while shard restarts did not.What fixed it for us (all three required, verified live —
sendInitialEventson the wildcard VW now returns thek8s.io/initial-events-endbookmark):experimental-watch-progress-notify-interval=1son etcd,WatchList=truefeature gate on the shard, and clients migrated to WatchList.After the migration: churn ~145 ops/s across 100+ logical clusters + rolling restart of all shards → 0 TooLarge, agents keep syncing.
What we think is still worth fixing upstream (our issue was filed against the fork's behavior, but the semantics are upstream kcp design):
TooLargeResourceVersionto the client as a terminal-ish error; returning a retryable410 Gone(or making wildcard LIST cache-consistent) would turn the infinite relist loop into a bounded retry.Happy to share our repro/measure scripts (storm + wildcard-watch victim probe) if useful.