Skip to content

Commit 3d0d38a

Browse files
committed
store a separate key for keeping object`s created_time
1 parent ea71e26 commit 3d0d38a

3 files changed

Lines changed: 32 additions & 11 deletions

File tree

lib/graphql/anycable/cleaner.rb

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,12 @@ def clean_subscriptions
2929
return unless config.use_redis_object_on_cleanup
3030

3131
redis.scan_each(match: "#{redis_key(adapter::SUBSCRIPTION_PREFIX)}*") do |key|
32-
idle = redis.object("IDLETIME", key)
33-
next if idle&.<= config.subscription_expiration_seconds
32+
next unless object_created_time_expired?(key)
3433

35-
redis.del(key)
34+
redis.multi do |pipeline|
35+
pipeline.del(key)
36+
pipeline.hdel(redis_key(adapter::CREATED_AT_KEY), key)
37+
end
3638
end
3739
end
3840

@@ -74,6 +76,16 @@ def config
7476
def redis_key(prefix)
7577
"#{config.redis_prefix}-#{prefix}"
7678
end
79+
80+
def object_created_time_expired?(key)
81+
last_created_time = redis.hget(redis_key(adapter::CREATED_AT_KEY), key)
82+
83+
return false unless last_created_time
84+
85+
expire_date = Time.parse(last_created_time) + config.subscription_expiration_seconds
86+
87+
Time.now >= expire_date
88+
end
7789
end
7890
end
7991
end

lib/graphql/subscriptions/anycable_subscriptions.rb

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
require "anycable"
44
require "graphql/subscriptions"
55
require "graphql/anycable/errors"
6-
76
# rubocop: disable Metrics/AbcSize, Metrics/LineLength, Metrics/MethodLength
87

98
# A subscriptions implementation that sends data as AnyCable broadcastings.
@@ -56,10 +55,11 @@ class AnyCableSubscriptions < GraphQL::Subscriptions
5655

5756
def_delegators :"GraphQL::AnyCable", :redis, :config
5857

59-
SUBSCRIPTION_PREFIX = "subscription:" # HASH: Stores subscription data: query, context, …
60-
FINGERPRINTS_PREFIX = "fingerprints:" # ZSET: To get fingerprints by topic
61-
SUBSCRIPTIONS_PREFIX = "subscriptions:" # SET: To get subscriptions by fingerprint
62-
CHANNEL_PREFIX = "channel:" # SET: Auxiliary structure for whole channel's subscriptions cleanup
58+
SUBSCRIPTION_PREFIX = "subscription:" # HASH: Stores subscription data: query, context, …
59+
FINGERPRINTS_PREFIX = "fingerprints:" # ZSET: To get fingerprints by topic
60+
SUBSCRIPTIONS_PREFIX = "subscriptions:" # SET: To get subscriptions by fingerprint
61+
CHANNEL_PREFIX = "channel:" # SET: Auxiliary structure for whole channel's subscriptions cleanup
62+
CREATED_AT_KEY = "objects:list-created-times" # HASH: Stores name and created_time of object
6363

6464
# @param serializer [<#dump(obj), #load(string)] Used for serializing messages before handing them to `.broadcast(msg)`
6565
def initialize(serializer: Serialize, **rest)
@@ -131,7 +131,6 @@ def write_subscription(query, events)
131131
# Store subscription_id in the channel state to cleanup on disconnect
132132
write_subscription_id(channel, channel_uniq_id)
133133

134-
135134
events.each do |event|
136135
channel.stream_from(redis_key(SUBSCRIPTIONS_PREFIX) + event.fingerprint)
137136
end
@@ -145,8 +144,13 @@ def write_subscription(query, events)
145144
}
146145

147146
redis.multi do |pipeline|
147+
full_subscription_id = "#{redis_key(SUBSCRIPTION_PREFIX)}#{subscription_id}"
148+
148149
pipeline.sadd(redis_key(CHANNEL_PREFIX) + channel_uniq_id, [subscription_id])
149-
pipeline.mapped_hmset(redis_key(SUBSCRIPTION_PREFIX) + subscription_id, data)
150+
pipeline.mapped_hmset(full_subscription_id, data)
151+
152+
pipeline.hset(redis_key(CREATED_AT_KEY), full_subscription_id, Time.now.to_s)
153+
150154
events.each do |event|
151155
pipeline.zincrby(redis_key(FINGERPRINTS_PREFIX) + event.topic, 1, event.fingerprint)
152156
pipeline.sadd(redis_key(SUBSCRIPTIONS_PREFIX) + event.fingerprint, [subscription_id])
@@ -182,7 +186,10 @@ def delete_subscription(subscription_id)
182186
fingerprint_subscriptions[redis_key(FINGERPRINTS_PREFIX) + topic] = score
183187
end
184188
# Delete subscription itself
185-
pipeline.del(redis_key(SUBSCRIPTION_PREFIX) + subscription_id)
189+
full_subscription_id = "#{redis_key(SUBSCRIPTION_PREFIX)}#{subscription_id}"
190+
191+
pipeline.del(full_subscription_id)
192+
pipeline.hdel(redis_key(CREATED_AT_KEY), full_subscription_id)
186193
end
187194
# Clean up fingerprints that doesn't have any subscriptions left
188195
redis.pipelined do |pipeline|

spec/graphql/anycable_spec.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,10 +138,12 @@
138138
expect(redis.exists?("graphql-subscription:some-truly-random-number")).to be true
139139
expect(redis.exists?("graphql-channel:some-truly-random-number")).to be true
140140
expect(redis.exists?("graphql-fingerprints::productUpdated:")).to be true
141+
expect(redis.hexists("graphql-objects:list-created-times", "graphql-subscription:some-truly-random-number")).to be true
141142
subject
142143
expect(redis.exists?("graphql-channel:some-truly-random-number")).to be false
143144
expect(redis.exists?("graphql-fingerprints::productUpdated:")).to be false
144145
expect(redis.exists?("graphql-subscription:some-truly-random-number")).to be false
146+
expect(redis.hexists("graphql-objects:list-created-times", "graphql-subscription:some-truly-random-number")).to be false
145147
end
146148
end
147149

0 commit comments

Comments
 (0)