graphql-pro: RedisStorage clean_inactive_subs leaves dead subscription IDs in mixed fingerprint sets under TTL expiry
I maintainer di solito rispondono entro 1 giorno
Nessuno ha ancora preso questa issue.
Valutazione
- Difficoltà
- 3/5
- Tempo stimato
- 1-2 giorni
- Idoneità per principianti
- 30/100
Direzione di ricerca
Start with the clean_inactive_subs Lua script in GraphQL::Pro::Subscriptions::RedisStorage and compare its fingerprint cleanup with the legacy filter_active_subscriptions path. Use the mixed live/dead fingerprint reproduction with a short stale_ttl_s, then verify that the expired bare subscription ID is removed from the fingerprintsubscriptions set while the live ID remains.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
This is a graphql-pro bug (closed-source), but Pro has no public issue tracker, so I'm filing here to reach you — happy to move it to [email protected] if you'd prefer. We're a Pro license holder.
Gem / location
graphql-pro1.30.0GraphQL::Pro::Subscriptions::RedisStorage, in theclean_inactive_subsLua script used byeach_fingerprint_and_subscription_ids.
Summary
When a subscription's query record is removed by Redis TTL (stale_ttl_s) rather than by an explicit delete_subscription, the cleanup that runs while iterating a topic's fingerprints fails to remove that subscription id from its fingerprintsubscriptions:<fingerprint> set — unless every member of that set is dead. Any fingerprint set that still has at least one live member accumulates dead ids indefinitely, so fingerprintsubscriptions:* grows without bound.
Root cause
The fingerprintsubscriptions:<fingerprint> set is populated with the bare subscription id (via sadd(fingerprint_subscriptions_key(f), [sub_id]) in the write path). But in clean_inactive_subs, the SREM against that set is issued with the prefixed query key (<prefix>query:<id>) — the same variable used for the preceding DEL — instead of the bare id. Since that value is never a member of the set, the SREM is a silent no-op and the dead id is left behind.
The leak is masked for fully-dead sets only because run_fingerprint_batch separately DELs the whole set when no active ids remain. Mixed (live + dead) sets are where it leaks.
Strong signal that this is the bug and not intended: the legacy path (filter_active_subscriptions) does it correctly — it SREMs the bare inactive_subscription_ids. Only the fingerprint path uses the prefixed key.
Suggested fix
In clean_inactive_subs, SREM the bare subscription id from fingerprintsubscriptions:<fingerprint> (the bare ids are already available 1:1 alongside the prefixed query keys), matching what the legacy filter_active_subscriptions path does.
Reproduction
- Configure
RedisStoragewith a shortstale_ttl_s. - Create two subscriptions whose queries hash to the same fingerprint (same query + variables), so both ids land in one
fingerprintsubscriptions:<fp>set. - Let one subscription's query record expire via TTL while the other stays live.
- Trigger the topic (runs
each_fingerprint_and_subscription_ids). - Observe: the expired id is still a member of
fingerprintsubscriptions:<fp>(SMEMBERSlists it) and is never reaped.
Impact (our production)
Redis backing GraphQL subscriptions roughly doubled in memory over a few months. ~96% of keys were gql:sub:*, and a sampled ~99% of fingerprintsubscriptions members pointed at query records that had already expired. The affected pattern is recurring per-user fingerprints (e.g. mobile clients that reconnect with a fresh subscription id but an identical query+variables fingerprint): the set always keeps one live member, so the bug applies and dead ids pile up.
Thanks for all your work on graphql-ruby/pro!
- Lingua principale
- Ruby
- Stelle
- 5.4k
- Fork
- 1.4k
- Merge medio
- 1g 8h
- PR unite (30g)
- 74
Preparare l'ambiente
- Nessun Dockerfile né file Docker Compose
- Nessun modello di pull request
- Leggi la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di rmosolgo/graphql-ruby
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 76/100
rmosolgo/graphql-ruby#5843 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 3/5 1-2 giorni Idoneità per principianti 55/100
rmosolgo/graphql-ruby#5763 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 4/5 3-5 giorni Idoneità per principianti 45/100
rmosolgo/graphql-ruby#5761 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 4/5 3-5 giorni Idoneità per principianti 52/100
rmosolgo/graphql-ruby#5707 · 10 commenti ·
I maintainer di solito rispondono entro 1 giorno
-
Cache complexity analyzer in-memory/redisForse già presa Una pull request collegata a questa issue è aperta o già unita. Aperta
Difficoltà 5/5 Più di una settimana Idoneità per principianti 35/100
rmosolgo/graphql-ruby#5632 · 4 commenti ·
I maintainer di solito rispondono entro 1 giorno
Tutte le issue di rmosolgo/graphql-ruby
Issue simili
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 65/100
greenelab/lab-website-template#357 · 1 commento ·
-
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 85/100
shakacode/react_on_rails#5295 ·
I maintainer di solito rispondono entro 1 giorno
-
account: profile bug
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
I maintainer di solito rispondono entro 5 giorni
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 82/100
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 85/100
elastic/elasticsearch-ruby#3078 ·