graphql-pro: RedisStorage clean_inactive_subs leaves dead subscription IDs in mixed fingerprint sets under TTL expiry
Nobody has claimed this yet.
Assessment
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Newbie friendliness
- 30/100
Research direction
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.
Written by the indexing model from the issue text.
Description
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 support@graphql.pro 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!
- Dominant language
- Ruby
- Stars
- 5.4k
- Forks
- 1.4k
- Avg merge
- 23h 19m
- Merged PRs (30d)
- 28
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from rmosolgo/graphql-ruby
-
Difficulty 4/5 3-5 days Newbie friendliness 52/100
rmosolgo/graphql-ruby#5707 · 10 comments ·
-
Difficulty 5/5 Over a week Newbie friendliness 35/100
rmosolgo/graphql-ruby#5632 · 4 comments ·
-
Migrate to RDoc Open
Difficulty 5/5 Over a week Newbie friendliness 30/100
rmosolgo/graphql-ruby#5576 · 2 comments ·
-
Difficulty 3/5 1-2 days Newbie friendliness 48/100
rmosolgo/graphql-ruby#5572 · 5 comments ·
-
Difficulty 5/5 Over a week Newbie friendliness 25/100
rmosolgo/graphql-ruby#5535 · 2 comments ·
All issues in rmosolgo/graphql-ruby
Similar issues
-
バグ
Difficulty 1/5 Under an hour Newbie friendliness 92/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
voxpupuli/puppet-epel#186 · 1 comment ·
-
external_created_at is no longer used for the message timestamp since the new message UI (v4.4.0) OpenBug Frontend
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 70/100
TheOdinProject/curriculum#31402 · 1 comment ·
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 78/100