redis_queued_locks 1.19.0 → 1.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.claude/project-overview.md +96 -23
- data/.claude/rules/acquirer.md +39 -20
- data/.claude/rules/arguments.md +3 -2
- data/.claude/rules/git.md +3 -2
- data/.claude/rules/logic.md +32 -8
- data/.claude/rules/swarm.md +14 -9
- data/.claude/rules/tests.md +12 -3
- data/.claude/rules/type-checking.md +2 -1
- data/.claude/rules/visitors.md +10 -11
- data/.claude/skills/rql-code-review/SKILL.md +386 -0
- data/.claude/skills/rql-code-review/templates/report.en.md +31 -0
- data/.claude/skills/rql-code-review/templates/report.ru.md +31 -0
- data/CHANGELOG.md +194 -0
- data/CLAUDE.md +6 -3
- data/README.md +502 -92
- data/lib/redis_queued_locks/acquirer/acquire_lock/dequeue_from_lock_queue/log_visitor.rb +1 -0
- data/lib/redis_queued_locks/acquirer/acquire_lock/instr_visitor.rb +42 -0
- data/lib/redis_queued_locks/acquirer/acquire_lock/log_visitor.rb +45 -0
- data/lib/redis_queued_locks/acquirer/acquire_lock/try_to_lock/log_visitor.rb +1 -0
- data/lib/redis_queued_locks/acquirer/acquire_lock/try_to_lock.rb +695 -128
- data/lib/redis_queued_locks/acquirer/acquire_lock/with_acq_timeout.rb +2 -2
- data/lib/redis_queued_locks/acquirer/acquire_lock/yield_expire/log_visitor.rb +1 -0
- data/lib/redis_queued_locks/acquirer/acquire_lock/yield_expire.rb +8 -6
- data/lib/redis_queued_locks/acquirer/acquire_lock.rb +134 -26
- data/lib/redis_queued_locks/acquirer/clear_dead_requests.rb +229 -24
- data/lib/redis_queued_locks/acquirer/extend_lock_ttl.rb +189 -18
- data/lib/redis_queued_locks/acquirer/is_locked.rb +0 -1
- data/lib/redis_queued_locks/acquirer/is_queued.rb +0 -1
- data/lib/redis_queued_locks/acquirer/keys.rb +151 -7
- data/lib/redis_queued_locks/acquirer/lock_info.rb +39 -41
- data/lib/redis_queued_locks/acquirer/lock_series_poc/instr_visitor.rb +3 -2
- data/lib/redis_queued_locks/acquirer/lock_series_poc/log_visitor.rb +5 -4
- data/lib/redis_queued_locks/acquirer/lock_series_poc.rb +79 -20
- data/lib/redis_queued_locks/acquirer/locks.rb +212 -83
- data/lib/redis_queued_locks/acquirer/queue_info.rb +11 -18
- data/lib/redis_queued_locks/acquirer/queues.rb +123 -50
- data/lib/redis_queued_locks/acquirer/reindex_locks.rb +253 -0
- data/lib/redis_queued_locks/acquirer/release_all_locks.rb +177 -61
- data/lib/redis_queued_locks/acquirer/release_lock.rb +84 -25
- data/lib/redis_queued_locks/acquirer/release_locks_of.rb +274 -70
- data/lib/redis_queued_locks/acquirer/release_read_lock.rb +22 -3
- data/lib/redis_queued_locks/acquirer.rb +1 -0
- data/lib/redis_queued_locks/client.rb +164 -36
- data/lib/redis_queued_locks/config.rb +16 -13
- data/lib/redis_queued_locks/errors.rb +34 -4
- data/lib/redis_queued_locks/instrument/sampler.rb +4 -7
- data/lib/redis_queued_locks/logging/sampler.rb +4 -8
- data/lib/redis_queued_locks/resource.rb +113 -6
- data/lib/redis_queued_locks/swarm/flush_zombies.rb +333 -61
- data/lib/redis_queued_locks/swarm/redis_client_builder.rb +8 -4
- data/lib/redis_queued_locks/swarm/swarm_element/threaded.rb +3 -2
- data/lib/redis_queued_locks/swarm/zombie_info.rb +203 -53
- data/lib/redis_queued_locks/swarm.rb +97 -5
- data/lib/redis_queued_locks/utilities.rb +1 -1
- data/lib/redis_queued_locks/version.rb +2 -2
- data/sig/redis_queued_locks/acquirer/acquire_lock/instr_visitor.rbs +14 -0
- data/sig/redis_queued_locks/acquirer/acquire_lock/log_visitor.rbs +13 -0
- data/sig/redis_queued_locks/acquirer/acquire_lock/try_to_lock.rbs +107 -1
- data/sig/redis_queued_locks/acquirer/acquire_lock/yield_expire.rbs +3 -3
- data/sig/redis_queued_locks/acquirer/acquire_lock.rbs +3 -1
- data/sig/redis_queued_locks/acquirer/clear_dead_requests.rbs +19 -1
- data/sig/redis_queued_locks/acquirer/extend_lock_ttl.rbs +20 -4
- data/sig/redis_queued_locks/acquirer/keys.rbs +11 -1
- data/sig/redis_queued_locks/acquirer/locks.rbs +15 -6
- data/sig/redis_queued_locks/acquirer/queues.rbs +9 -2
- data/sig/redis_queued_locks/acquirer/reindex_locks.rbs +52 -0
- data/sig/redis_queued_locks/acquirer/release_all_locks.rbs +8 -1
- data/sig/redis_queued_locks/acquirer/release_lock.rbs +6 -3
- data/sig/redis_queued_locks/acquirer/release_locks_of.rbs +20 -1
- data/sig/redis_queued_locks/acquirer/release_read_lock.rbs +1 -1
- data/sig/redis_queued_locks/client.rbs +21 -2
- data/sig/redis_queued_locks/errors.rbs +5 -2
- data/sig/redis_queued_locks/instrument/sampler.rbs +0 -2
- data/sig/redis_queued_locks/logging/sampler.rbs +0 -2
- data/sig/redis_queued_locks/resource.rbs +7 -1
- data/sig/redis_queued_locks/swarm/flush_zombies.rbs +51 -1
- data/sig/redis_queued_locks/swarm/zombie_info.rbs +58 -7
- data/sig/vendor/redis_client.rbs +2 -0
- metadata +6 -1
data/README.md
CHANGED
|
@@ -10,7 +10,7 @@ In addition to the classic `queued` (FIFO) strategy RQL supports `random` (RANDO
|
|
|
10
10
|
|
|
11
11
|
Supports `Read`/`Write` locks and `Reentrant` locks.
|
|
12
12
|
|
|
13
|
-
Provides flexible invocation flow, parametrized limits (lock request ttl, lock ttl, queue ttl, lock attempts limit, fast failing, etc), **zombie locks elimination**, logging and instrumentation (and much more).
|
|
13
|
+
Provides flexible invocation flow, parametrized limits (lock request ttl, lock ttl, queue ttl, lock attempts limit, fast failing, etc), **zombie locks elimination**, **[lock indexing](#lock-index)**, logging and instrumentation (and much more).
|
|
14
14
|
|
|
15
15
|
---
|
|
16
16
|
|
|
@@ -47,6 +47,7 @@ Provides flexible invocation flow, parametrized limits (lock request ttl, lock t
|
|
|
47
47
|
- [locks_info](#locks_info---get-list-of-locks-with-their-info)
|
|
48
48
|
- [queues_info](#queues_info---get-list-of-queues-with-their-info)
|
|
49
49
|
- [clear_dead_requests](#clear_dead_requests)
|
|
50
|
+
- [reindex_locks](#reindex_locks)
|
|
50
51
|
- [current_acquirer_id](#current_acquirer_id)
|
|
51
52
|
- [current_host_id](#current_host_id)
|
|
52
53
|
- [possible_host_ids](#possible_host_ids)
|
|
@@ -64,6 +65,9 @@ Provides flexible invocation flow, parametrized limits (lock request ttl, lock t
|
|
|
64
65
|
- [zombie_locks](#zombie_locks)
|
|
65
66
|
- [zombie_hosts](#zombie_hosts)
|
|
66
67
|
- [zombie_acquirers](#zombie_acquirers)
|
|
68
|
+
- [Lock Index](#lock-index)
|
|
69
|
+
- [Lock Index Performance](#lock-index-performance)
|
|
70
|
+
- [Lock Index Size](#lock-index-size)
|
|
67
71
|
- [Lock Access Strategies](#lock-access-strategies)
|
|
68
72
|
- [queued](#lock-access-strategies)
|
|
69
73
|
- [random](#lock-access-strategies)
|
|
@@ -75,6 +79,7 @@ Provides flexible invocation flow, parametrized limits (lock request ttl, lock t
|
|
|
75
79
|
- [Instrumentation Configuration](#instrumentation-configuration)
|
|
76
80
|
- [Instrumentation Events](#instrumentation-events)
|
|
77
81
|
- ["redis_queued_locks.lock_obtained"](#redis_queued_lockslock_obtained)
|
|
82
|
+
- ["redis_queued_locks.lock_obtaining_failed"](#redis_queued_lockslock_obtaining_failed)
|
|
78
83
|
- ["redis_queued_locks.extendable_reentrant_lock_obtained"](#redis_queued_locksextendable_reentrant_lock_obtained)
|
|
79
84
|
- ["redis_queued_locks.reentrant_lock_obtained"](#redis_queued_locksreentrant_lock_obtained)
|
|
80
85
|
- ["redis_queued_locks.lock_hold_and_release"](#redis_queued_lockslock_hold_and_release)
|
|
@@ -85,6 +90,9 @@ Provides flexible invocation flow, parametrized limits (lock request ttl, lock t
|
|
|
85
90
|
- ["redis_queued_locks.explicit_read_lock_release"](#redis_queued_locksexplicit_read_lock_release)
|
|
86
91
|
- ["redis_queued_locks.explicit_all_locks_release"](#redis_queued_locksexplicit_all_locks_release)
|
|
87
92
|
- ["redis_queued_locks.release_locks_of"](#redis_queued_locksrelease_locks_of)
|
|
93
|
+
- ["redis_queued_locks.extend_lock_ttl"](#redis_queued_locksextend_lock_ttl)
|
|
94
|
+
- ["redis_queued_locks.clear_dead_requests"](#redis_queued_locksclear_dead_requests)
|
|
95
|
+
- ["redis_queued_locks.reindex_locks"](#redis_queued_locksreindex_locks)
|
|
88
96
|
- [Roadmap](#roadmap)
|
|
89
97
|
- [Build and Develop](#build-and-develop)
|
|
90
98
|
- [Contributing](#contributing)
|
|
@@ -188,14 +196,19 @@ client = RedisQueuedLocks::Client.new(redis_client) do |config|
|
|
|
188
196
|
|
|
189
197
|
# (seconds) (supports nil)
|
|
190
198
|
# - nil means "no timeout" and you are only limited by "retry_count" config;
|
|
199
|
+
# - 0 is the same as nil (no timeout);
|
|
191
200
|
config['try_to_lock_timeout'] = 10
|
|
192
201
|
|
|
193
202
|
# (milliseconds) (default: 5_000)
|
|
194
203
|
# - lock's time to live
|
|
204
|
+
# - should be a positive Integer
|
|
195
205
|
config['default_lock_ttl'] = 5_000
|
|
196
206
|
|
|
197
207
|
# (seconds) (default: 15)
|
|
198
208
|
# - lock request timeout. after this timeout your lock request in queue will be requeued with new position (at the end of the queue);
|
|
209
|
+
# - (the enabled lock index) the lock request queue lives as long as its latest lock request (the queue TTL is refreshed with
|
|
210
|
+
# the queue ttl on each lock request): queues of dead acquirers expire by themselves;
|
|
211
|
+
# - should be a positive Integer
|
|
199
212
|
config['default_queue_ttl'] = 15
|
|
200
213
|
|
|
201
214
|
# (boolean) (default: false)
|
|
@@ -252,24 +265,35 @@ client = RedisQueuedLocks::Client.new(redis_client) do |config|
|
|
|
252
265
|
|
|
253
266
|
# (default: 100)
|
|
254
267
|
# - how many items will be released at a time in #clear_locks and in #clear_dead_requests (uses SCAN);
|
|
268
|
+
# - (the enabled lock index) how many locks (lock names of the lock index) will be processed at a time
|
|
269
|
+
# (the ZSCAN count of the lock index pass);
|
|
255
270
|
# - affects the performance of your Redis and Ruby Application (configure thoughtfully);
|
|
271
|
+
# - should be a positive Integer
|
|
256
272
|
config['lock_release_batch_size'] = 100
|
|
257
273
|
|
|
258
274
|
# (default: 300)
|
|
259
275
|
# - how many items will be released at a time in #clear_locks_of and in #clear_current_locks methods (uses SCAN for batch extraction and DEL for batch deletion);
|
|
276
|
+
# - (the enabled lock index) how many locks (lock names of the lock index) will be processed at a time
|
|
277
|
+
# (the ZSCAN count of the lock index pass, DEL for batch deletion);
|
|
260
278
|
# - affects the ruby's memory (cuz this batch will be stored in Set object in first, and then will be splatted to the DEL redis method invocation) (configure thoughtfully);
|
|
261
279
|
# - affects the performance of your Redis (configure thoughtfully);
|
|
280
|
+
# - should be a positive Integer
|
|
262
281
|
config['clear_locks_of__lock_scan_size'] = 300
|
|
263
282
|
|
|
264
283
|
# (default: 300)
|
|
265
|
-
# - how many queues will be
|
|
284
|
+
# - how many lock queues will be processed in one round trip in #clear_locks_of and in #clear_current_locks methods
|
|
285
|
+
# (lock queues are found by the same SCAN pass as locks or derived from the lock names of the same lock index pass);
|
|
266
286
|
# - affects the performance of your Redis (configure thoughtfully);
|
|
287
|
+
# - should be a positive Integer
|
|
267
288
|
config['clear_locks_of__queue_scan_size'] = 300
|
|
268
289
|
|
|
269
290
|
# (default: 500)
|
|
270
291
|
# - how many items should be extracted from redis during the #locks, #queues, #keys
|
|
271
292
|
# #locks_info, and #queues_info operations (uses SCAN);
|
|
293
|
+
# - (the enabled lock index) how many locks (lock names of the lock index) should be processed at a time
|
|
294
|
+
# (the ZSCAN count of the lock index pass);
|
|
272
295
|
# - affects the performance of your Redis and Ruby Application (configure thoughtfully);
|
|
296
|
+
# - should be a positive Integer
|
|
273
297
|
config['key_extraction_batch_size'] = 500
|
|
274
298
|
|
|
275
299
|
# (default: 1 day)
|
|
@@ -277,6 +301,22 @@ client = RedisQueuedLocks::Client.new(redis_client) do |config|
|
|
|
277
301
|
# - used for `#clear_dead_requests` as default vaule of `:dead_ttl` option;
|
|
278
302
|
config['dead_request_ttl'] = (1 * 24 * 60 * 60 * 1000) # one day in milliseconds
|
|
279
303
|
|
|
304
|
+
# (boolean) (default: false)
|
|
305
|
+
# - find locks via the lock index (`rql:index:locks`) instead of the full Redis keyspace `SCAN` in all lock-iterating operations
|
|
306
|
+
# (the swarm, #locks, #queues, #keys, #clear_locks, #clear_locks_of, #clear_dead_requests, ...);
|
|
307
|
+
# - the lock index is maintained by the lock acquirement itself only when it is enabled;
|
|
308
|
+
# - the client-wide mode (not a per-call option): use the same value on all your processes that share the Redis database;
|
|
309
|
+
# after enabling invoke `#reindex_locks` once (see the "Lock Index" documentation section);
|
|
310
|
+
config['lock_index_enabled'] = false
|
|
311
|
+
|
|
312
|
+
# (milliseconds) (default: 60_000)
|
|
313
|
+
# - the lock index grace period: the lock name stays in the lock index (`rql:index:locks`) for this period
|
|
314
|
+
# after the expiration of all its keys (see the "Lock Index" documentation section);
|
|
315
|
+
# - tolerance of the client clock skew between your hosts and of the network latency: should be greater than
|
|
316
|
+
# the maximal clock skew between your hosts plus the network latency;
|
|
317
|
+
# - used only with the enabled lock index (`config['lock_index_enabled']`);
|
|
318
|
+
config['lock_index_grace_period'] = 60_000
|
|
319
|
+
|
|
280
320
|
# (default: RedisQueuedLocks::Instrument::VoidNotifier)
|
|
281
321
|
# - instrumentation layer;
|
|
282
322
|
# - you can provide your own instrumenter that should realize `#notify(event, payload = {})` interface:
|
|
@@ -299,15 +339,28 @@ client = RedisQueuedLocks::Client.new(redis_client) do |config|
|
|
|
299
339
|
# - should implement `debug(progname = nil, &block)` (minimal requirement) or be an instance of Ruby's `::Logger` class/subclass;
|
|
300
340
|
# - supports `SemanticLogger::Logger` (see "semantic_logger" gem)
|
|
301
341
|
# - at this moment the only debug logs are realised in following cases:
|
|
302
|
-
# - "[redis_queued_locks.start_lock_obtaining]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat");
|
|
303
|
-
# - "[redis_queued_locks.start_try_to_lock_cycle]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat");
|
|
304
|
-
# - "[redis_queued_locks.dead_score_reached__reset_acquirer_position]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat");
|
|
305
|
-
# - "[redis_queued_locks.lock_obtained]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "
|
|
306
|
-
# - "[redis_queued_locks.
|
|
307
|
-
# - "[redis_queued_locks.
|
|
308
|
-
# - "[redis_queued_locks.
|
|
309
|
-
# - "[redis_queued_locks.
|
|
310
|
-
# - "[redis_queued_locks.
|
|
342
|
+
# - "[redis_queued_locks.start_lock_obtaining]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
|
|
343
|
+
# - "[redis_queued_locks.start_try_to_lock_cycle]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
|
|
344
|
+
# - "[redis_queued_locks.dead_score_reached__reset_acquirer_position]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
|
|
345
|
+
# - "[redis_queued_locks.lock_obtained]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "acq_time");
|
|
346
|
+
# - "[redis_queued_locks.lock_obtaining_failed]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "acq_time", "result");
|
|
347
|
+
# - "[redis_queued_locks.extendable_reentrant_lock_obtained]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "acq_time");
|
|
348
|
+
# - "[redis_queued_locks.reentrant_lock_obtained]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "acq_time");
|
|
349
|
+
# - "[redis_queued_locks.fail_fast_or_limits_reached_or_deadlock__dequeue]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
|
|
350
|
+
# - "[redis_queued_locks.expire_lock]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
|
|
351
|
+
# - "[redis_queued_locks.decrease_lock]" (logs "lock_key", "decreased_ttl", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
|
|
352
|
+
# - "[redis_queued_locks.start_lock_series_obtaining]" (logs "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
|
|
353
|
+
# - "[redis_queued_locks.lock_series_obtained]" (logs "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "acq_time");
|
|
354
|
+
# - "[redis_queued_locks.expire_lock_series]" (logs "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
|
|
355
|
+
# - "[redis_queued_locks.explicit_lock_release]" (logs "lock_key", "lock_key_queue", "rel_time", "at");
|
|
356
|
+
# - "[redis_queued_locks.explicit_read_lock_release]" (logs "lock_key", "acq_id", "lock_res", "rel_time", "at");
|
|
357
|
+
# - "[redis_queued_locks.explicit_all_locks_release]" (logs "at", "rel_time", "rel_key_cnt");
|
|
358
|
+
# - "[redis_queued_locks.release_locks_of]" (logs "at", "hst_id", "acq_id", "rel_time", "rel_key_cnt", "tch_queue_cnt");
|
|
359
|
+
# - "[redis_queued_locks.extend_lock_ttl]" (logs "lock_key", "rw_mode", "all_read_locks", "ext_ttl", "extended_locks_count", "ext_time", "at");
|
|
360
|
+
# - "[redis_queued_locks.clear_dead_requests]" (logs "dead_ttl", "prc_queue_cnt", "rel_time", "at");
|
|
361
|
+
# - "[redis_queued_locks.reindex_locks]" (logs "idx_key_cnt", "idx_time", "at");
|
|
362
|
+
# - "rw_mode" is the requested lock mode ('write'/'read'), the held lock mode for "expire_lock" and "decrease_lock",
|
|
363
|
+
# the mode of the series for lock series logs;
|
|
311
364
|
# - by default uses VoidLogger that does nothing;
|
|
312
365
|
config['logger'] = RedisQueuedLocks::Logging::VoidLogger
|
|
313
366
|
|
|
@@ -315,19 +368,25 @@ client = RedisQueuedLocks::Client.new(redis_client) do |config|
|
|
|
315
368
|
# - adds additional debug logs;
|
|
316
369
|
# - enables additional logs for each internal try-retry lock acquiring (a lot of logs can be generated depending on your retry configurations);
|
|
317
370
|
# - it adds following debug logs in addition to the existing:
|
|
318
|
-
# - "[redis_queued_locks.try_lock.start]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat");
|
|
319
|
-
# - "[redis_queued_locks.try_lock.rconn_fetched]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat");
|
|
320
|
-
# - "[redis_queued_locks.try_lock.same_process_conflict_detected]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat");
|
|
321
|
-
# - "[redis_queued_locks.try_lock.same_process_conflict_analyzed]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "spc_status");
|
|
322
|
-
# - "[redis_queued_locks.try_lock.reentrant_lock__extend_and_work_through]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "spc_status", "last_ext_ttl", "last_ext_ts");
|
|
323
|
-
# - "[redis_queued_locks.try_lock.reentrant_lock__work_through]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "spc_status", last_spc_ts);
|
|
324
|
-
# - "[redis_queued_locks.try_lock.
|
|
325
|
-
# - "[redis_queued_locks.try_lock.
|
|
326
|
-
# - "[redis_queued_locks.try_lock.
|
|
327
|
-
# - "[redis_queued_locks.try_lock.
|
|
328
|
-
# - "[redis_queued_locks.try_lock.
|
|
329
|
-
# - "[redis_queued_locks.try_lock.
|
|
330
|
-
# - "[redis_queued_locks.try_lock.
|
|
371
|
+
# - "[redis_queued_locks.try_lock.start]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
|
|
372
|
+
# - "[redis_queued_locks.try_lock.rconn_fetched]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
|
|
373
|
+
# - "[redis_queued_locks.try_lock.same_process_conflict_detected]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
|
|
374
|
+
# - "[redis_queued_locks.try_lock.same_process_conflict_analyzed]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "spc_status");
|
|
375
|
+
# - "[redis_queued_locks.try_lock.reentrant_lock__extend_and_work_through]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "spc_status", "last_ext_ttl", "last_ext_ts");
|
|
376
|
+
# - "[redis_queued_locks.try_lock.reentrant_lock__work_through]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "spc_status", "last_spc_ts");
|
|
377
|
+
# - "[redis_queued_locks.try_lock.single_process_lock_conflict__dead_lock]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "spc_status", "last_spc_ts");
|
|
378
|
+
# - "[redis_queued_locks.try_lock.single_process_lock_conflict__lock_upgrade]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "spc_status", "last_spc_ts");
|
|
379
|
+
# - "[redis_queued_locks.try_lock.acq_added_to_queue]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
|
|
380
|
+
# - "[redis_queued_locks.try_lock.remove_expired_acqs]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
|
|
381
|
+
# - "[redis_queued_locks.try_lock.get_first_from_queue]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "first_acq_id_in_queue");
|
|
382
|
+
# - "[redis_queued_locks.try_lock.exit__queue_ttl_reached]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
|
|
383
|
+
# - "[redis_queued_locks.try_lock.exit__no_first]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "first_acq_id_in_queue", "<current_lock_data>");
|
|
384
|
+
# - "[redis_queued_locks.try_lock.exit__lock_still_obtained]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "first_acq_id_in_queue", "locked_by_acq_id", "<current_lock_data>");
|
|
385
|
+
# - "[redis_queued_locks.try_lock.exit__write_request_ahead]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "ahead_acq_id");
|
|
386
|
+
# - "[redis_queued_locks.try_lock.exit__read_request_ahead]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "ahead_acq_id");
|
|
387
|
+
# - "[redis_queued_locks.try_lock.exit__read_lock_still_obtained]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "first_acq_id_in_queue");
|
|
388
|
+
# - "[redis_queued_locks.try_lock.obtain__free_to_acquire]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
|
|
389
|
+
# - "rw_mode" is the requested lock mode ('write'/'read');
|
|
331
390
|
config['log_lock_try'] = false
|
|
332
391
|
|
|
333
392
|
# (default: false)
|
|
@@ -338,7 +397,7 @@ client = RedisQueuedLocks::Client.new(redis_client) do |config|
|
|
|
338
397
|
|
|
339
398
|
# (default: 15)
|
|
340
399
|
# - the percent of cases that should be logged;
|
|
341
|
-
# - take an effect when <config['
|
|
400
|
+
# - take an effect when <config['log_sampling_enabled']> is true;
|
|
342
401
|
# - works in tandem with <config['log_sampling_enabled']> and <config['log_sampler']> configs;
|
|
343
402
|
config['log_sampling_percent'] = 15
|
|
344
403
|
|
|
@@ -355,11 +414,11 @@ client = RedisQueuedLocks::Client.new(redis_client) do |config|
|
|
|
355
414
|
# - enables <instrumentaion sampling>: only the configured percent of RQL cases will be instrumented;
|
|
356
415
|
# - disabled by default;
|
|
357
416
|
# - works in tandem with <config['instr_sampling_percent'] and <config['instr_sampler']>;
|
|
358
|
-
config['
|
|
417
|
+
config['instr_sampling_enabled'] = false
|
|
359
418
|
|
|
360
419
|
# (default: 15)
|
|
361
420
|
# - the percent of cases that should be instrumented;
|
|
362
|
-
# - take an effect when <config['
|
|
421
|
+
# - take an effect when <config['instr_sampling_enabled']> is true;
|
|
363
422
|
# - works in tandem with <config['instr_sampling_enabled']> and <config['instr_sampler']> configs;
|
|
364
423
|
config['instr_sampling_percent'] = 15
|
|
365
424
|
|
|
@@ -381,15 +440,18 @@ end
|
|
|
381
440
|
<sup>\[[back to top](#table-of-contents)\]</sup>
|
|
382
441
|
|
|
383
442
|
- [lock](#lock---obtain-a-lock)
|
|
384
|
-
- [lock!](#lock---
|
|
443
|
+
- [lock!](#lock---exceptional-lock-obtaining)
|
|
385
444
|
- [lock_series](#lock_series---poc-acquire-a-series-of-locks) (PoC)
|
|
386
445
|
- [lock_series!](#lock_series---poc-exceptional-lock_series) (PoC)
|
|
387
446
|
- [lock_info](#lock_info)
|
|
388
447
|
- [queue_info](#queue_info)
|
|
389
448
|
- [locked?](#locked)
|
|
390
449
|
- [queued?](#queued)
|
|
391
|
-
- [unlock](#unlock---release-a-lock)
|
|
392
|
-
- [
|
|
450
|
+
- [unlock](#unlock---release-a-lock) (aka `release_lock`)
|
|
451
|
+
- [unlock_read](#unlock_read---release-the-read-lock-of-the-current-acquirer) (aka `release_read_lock`)
|
|
452
|
+
- [clear_locks](#clear_locks---release-all-locks-and-lock-queues) (aka `release_locks`)
|
|
453
|
+
- [clear_locks_of](#clear_locks_of) (aka `release_locks_of`)
|
|
454
|
+
- [clear_current_locks](#clear_current_locks) (aka `release_current_locks`)
|
|
393
455
|
- [extend_lock_ttl](#extend_lock_ttl)
|
|
394
456
|
- [locks](#locks---get-list-of-obtained-locks)
|
|
395
457
|
- [queues](#queues---get-list-of-lock-request-queues)
|
|
@@ -397,10 +459,15 @@ end
|
|
|
397
459
|
- [locks_info](#locks_info---get-list-of-locks-with-their-info)
|
|
398
460
|
- [queues_info](#queues_info---get-list-of-queues-with-their-info)
|
|
399
461
|
- [clear_dead_requests](#clear_dead_requests)
|
|
462
|
+
- [reindex_locks](#reindex_locks)
|
|
400
463
|
- [current_acquirer_id](#current_acquirer_id)
|
|
401
464
|
- [current_host_id](#current_host_id)
|
|
402
465
|
- [possible_host_ids](#possible_host_ids)
|
|
403
466
|
|
|
467
|
+
The numeric options of the methods (ttls, timeouts, retries, scan and batch sizes, zombie ttl) are validated with the same
|
|
468
|
+
types and ranges as their `config` keys: `Integer` values only (`timeout: 0.5` is rejected), `RedisQueuedLocks::ArgumentError`
|
|
469
|
+
(`RedisQueuedLocks::SwarmArgumentError` for the swarm methods) otherwise.
|
|
470
|
+
|
|
404
471
|
---
|
|
405
472
|
|
|
406
473
|
#### #lock - obtain a lock
|
|
@@ -411,6 +478,10 @@ end
|
|
|
411
478
|
- If block is passed:
|
|
412
479
|
- the obtained lock will be released after the block execution or the lock's ttl (what will happen first);
|
|
413
480
|
- if you want to timeout (fail with timeout) the block execution with lock's TTL use `timed: true` option;
|
|
481
|
+
- (limitation) the write lock is released after the block without an owner check: if the block (without `timed: true`)
|
|
482
|
+
runs longer than the lock's ttl, the expired lock can be already obtained by another acquirer and the release
|
|
483
|
+
deletes that lock (the same for the ttl decrease of `:extendable_work_through` reentrant locks); use `timed: true`
|
|
484
|
+
or extend the lock (`#extend_lock_ttl`) for long blocks (read locks are released per acquirer and are not affected);
|
|
414
485
|
- the block's result will be returned;
|
|
415
486
|
- If block is not passed:
|
|
416
487
|
- the obtained lock will be released after lock's ttl;
|
|
@@ -421,6 +492,7 @@ def lock(
|
|
|
421
492
|
lock_name,
|
|
422
493
|
ttl: config['default_lock_ttl'],
|
|
423
494
|
queue_ttl: config['default_queue_ttl'],
|
|
495
|
+
lock_index_grace_period: config['lock_index_grace_period'],
|
|
424
496
|
timeout: config['try_to_lock_timeout'],
|
|
425
497
|
timed: config['is_timed_by_default'],
|
|
426
498
|
retry_count: config['retry_count'],
|
|
@@ -454,15 +526,29 @@ def lock(
|
|
|
454
526
|
- Lock name to be obtained.
|
|
455
527
|
- `ttl` - (optional) - [Integer]
|
|
456
528
|
- Lock's time to live (in milliseconds);
|
|
529
|
+
- should be a positive `Integer` (write and read locks);
|
|
457
530
|
- pre-configured in `config['default_lock_ttl']`;
|
|
458
531
|
- `queue_ttl` - (optional) `[Integer]`
|
|
459
532
|
- Lifetime of the acuier's lock request. In seconds.
|
|
533
|
+
- (the enabled lock index) the lock request queue lives as long as its latest lock request (the queue TTL is refreshed
|
|
534
|
+
on each lock request);
|
|
460
535
|
- pre-configured in `config['default_queue_ttl']`;
|
|
536
|
+
- `lock_index_grace_period` - (optional) `[Integer]`
|
|
537
|
+
- The lock index grace period (in milliseconds): the lock name stays in the lock index for this period after the expiration
|
|
538
|
+
of all its keys (tolerance of the client clock skew between your hosts and of the network latency);
|
|
539
|
+
- used only with the enabled lock index (`config['lock_index_enabled']`, the client-wide mode);
|
|
540
|
+
- see the [Lock Index](#lock-index) documentation section;
|
|
541
|
+
- pre-configured in `config['lock_index_grace_period']`;
|
|
461
542
|
- `timeout` - (optional) `[Integer,NilClass]`
|
|
462
543
|
- Time period a client should try to acquire the lock (in seconds). Nil means "without timeout".
|
|
544
|
+
- `0` is the same as `nil` (without timeout);
|
|
463
545
|
- pre-configured in `config['try_to_lock_timeout']`;
|
|
546
|
+
- the timeout can not be swallowed by `rescue` of your logger/instrumenter (see `timed` below);
|
|
464
547
|
- `timed` - (optiona) `[Boolean]`
|
|
465
548
|
- Limit the invocation time period of the passed block of code by the lock's TTL.
|
|
549
|
+
- the block is interrupted by an internal `RedisQueuedLocks::Exception` (an `::Exception`, like `Timeout::ExitException` of Ruby's `Timeout`): `rescue => e`,
|
|
550
|
+
`rescue StandardError`, `rescue Timeout::Error` and `rescue nil` inside the block can not swallow it (only `rescue Exception`),
|
|
551
|
+
`ensure` of the block still runs;
|
|
466
552
|
- pre-configured in `config['is_timed_by_default']`;
|
|
467
553
|
- `false` by default;
|
|
468
554
|
- `retry_count` - (optional) `[Integer,NilClass]`
|
|
@@ -561,7 +647,7 @@ def lock(
|
|
|
561
647
|
- pre-configured in `config['log_sampling_enabled']`;
|
|
562
648
|
- `log_sampling_percent` - (optional) `[Integer]`
|
|
563
649
|
- the percent of cases that should be logged;
|
|
564
|
-
- take an effect when `
|
|
650
|
+
- take an effect when `log_sampling_enabled` is true;
|
|
565
651
|
- works in tandem with `log_sampling_enabled` and `log_sampler` options;
|
|
566
652
|
- pre-configured in `config['log_sampling_percent']`;
|
|
567
653
|
- `log_sampler` - (optional) `[#sampling_happened?,Module<RedisQueuedLocks::Logging::Sampler>]`
|
|
@@ -583,7 +669,7 @@ def lock(
|
|
|
583
669
|
- pre-configured in `config['instr_sampling_enabled']`;
|
|
584
670
|
- `instr_sampling_percent` - (optional) `[Integer]`
|
|
585
671
|
- the percent of cases that should be instrumented;
|
|
586
|
-
- take an effect when `
|
|
672
|
+
- take an effect when `instr_sampling_enabled` is true;
|
|
587
673
|
- works in tandem with `instr_sampling_enabled` and `instr_sampler` options;
|
|
588
674
|
- pre-configured in `config['instr_sampling_percent']`;
|
|
589
675
|
- `instr_sampler` - (optional) `[#sampling_happened?,Module<RedisQueuedLocks::Instrument::Sampler>]`
|
|
@@ -621,6 +707,7 @@ Return value:
|
|
|
621
707
|
result: {
|
|
622
708
|
lock_key: "rql:lock:my_lock",
|
|
623
709
|
acq_id: "rql:acq:26672/2280/2300/2320/70ea5dbf10ea1056",
|
|
710
|
+
hst_id: "rql:hst:26672/2280/2320/70ea5dbf10ea1056",
|
|
624
711
|
ts: 1711909612.653696,
|
|
625
712
|
ttl: 10000,
|
|
626
713
|
process: :lock_obtaining
|
|
@@ -634,7 +721,8 @@ Return value:
|
|
|
634
721
|
- `:lock_obtaining` - classic lock obtaining proces. Default behavior (`conflict_strategy: :wait_for_lock`);
|
|
635
722
|
- `:extendable_conflict_work_through` - reentrant lock acquiring process with lock's TTL extension. Suitable for `conflict_strategy: :extendable_work_through`;
|
|
636
723
|
- `:conflict_work_through` - reentrant lock acquiring process without lock's TTL extension. Suitable for `conflict_strategy: :work_through`;
|
|
637
|
-
-
|
|
724
|
+
- (`conflict_strategy: :dead_locking` does not obtain the lock already obtained by the current acquirer:
|
|
725
|
+
it fails with `{ ok: false, result: :conflict_dead_lock }`);
|
|
638
726
|
- For more details see [Deadlocks and Reentrant locks](#deadlocks-and-reentrant-locks) readme section;
|
|
639
727
|
- For successful lock obtaining:
|
|
640
728
|
```ruby
|
|
@@ -646,7 +734,7 @@ Return value:
|
|
|
646
734
|
hst_id: String, # host identifier ("process_id/thread_id/ractor_id/identity")
|
|
647
735
|
ts: Float, # time (epoch) when lock was obtained (float, Time#to_f)
|
|
648
736
|
ttl: Integer, # lock's time to live in milliseconds (integer)
|
|
649
|
-
process: Symbol # which logical process has acquired the lock (:lock_obtaining, :extendable_conflict_work_through, :conflict_work_through
|
|
737
|
+
process: Symbol # which logical process has acquired the lock (:lock_obtaining, :extendable_conflict_work_through, :conflict_work_through)
|
|
650
738
|
}
|
|
651
739
|
}
|
|
652
740
|
```
|
|
@@ -658,21 +746,21 @@ Return value:
|
|
|
658
746
|
result: {
|
|
659
747
|
lock_key: "rql:lock:my_lock",
|
|
660
748
|
acq_id: "rql:acq:26672/2280/2300/2320/70ea5dbf10ea1056",
|
|
661
|
-
|
|
749
|
+
hst_id: "rql:hst:26672/2280/2320/70ea5dbf10ea1056",
|
|
662
750
|
ts: 1711909612.653696,
|
|
663
751
|
ttl: 10000,
|
|
664
|
-
process: :lock_obtaining # for
|
|
752
|
+
process: :lock_obtaining # for reentrant locks (conflict strategies): :conflict_work_through, :extendable_conflict_work_through
|
|
665
753
|
}
|
|
666
754
|
}
|
|
667
755
|
```
|
|
668
756
|
- For failed lock obtaining:
|
|
669
757
|
```ruby
|
|
670
|
-
{ ok: false, result: :timeout_reached }
|
|
671
|
-
{ ok: false, result: :
|
|
758
|
+
{ ok: false, result: :timeout_reached } # see <timeout> option
|
|
759
|
+
{ ok: false, result: :retry_limit_reached } # see <retry_count> option
|
|
672
760
|
{ ok: false, result: :conflict_dead_lock } # see <conflict_strategy> option for details (:dead_locking strategy)
|
|
761
|
+
{ ok: false, result: :conflict_lock_upgrade } # read-to-write lock upgrade (see <read_write_mode> and <conflict_strategy> options)
|
|
673
762
|
{ ok: false, result: :fail_fast_no_try } # see <fail_fast> option
|
|
674
763
|
{ ok: false, result: :fail_fast_after_try } # see <fail_fast> option
|
|
675
|
-
{ ok: false, result: :unknown }
|
|
676
764
|
```
|
|
677
765
|
|
|
678
766
|
Examples:
|
|
@@ -733,8 +821,9 @@ rql.lock_info("my_lock")
|
|
|
733
821
|
# =>
|
|
734
822
|
{
|
|
735
823
|
"lock_key" => "rql:lock:my_lock",
|
|
824
|
+
"rw_mode" => "write",
|
|
736
825
|
"acq_id" => "rql:acq:123/456/567/678/374dd74324",
|
|
737
|
-
"hst_id" => "rql:
|
|
826
|
+
"hst_id" => "rql:hst:123/456/678/374dd74324",
|
|
738
827
|
"ts" => 123456789,
|
|
739
828
|
"ini_ttl" => 123456,
|
|
740
829
|
"rem_ttl" => 123440,
|
|
@@ -842,6 +931,7 @@ def lock!(
|
|
|
842
931
|
lock_name,
|
|
843
932
|
ttl: config['default_lock_ttl'],
|
|
844
933
|
queue_ttl: config['default_queue_ttl'],
|
|
934
|
+
lock_index_grace_period: config['lock_index_grace_period'],
|
|
845
935
|
timeout: config['try_to_lock_timeout'],
|
|
846
936
|
timed: config['is_timed_by_default'],
|
|
847
937
|
retry_count: config['retry_count'],
|
|
@@ -1017,9 +1107,9 @@ client.lock_series!("x", "y", "z") { ...some_code... }
|
|
|
1017
1107
|
- `"acq_id"` - `string` - acquirer identifier (process_id/thread_id/fiber_id/ractor_id/identity);
|
|
1018
1108
|
- `"hst_id"` - `string` - host identifier (process_id/thread_id/ractor_id/identity);
|
|
1019
1109
|
- `"ts"` - `numeric`/`epoch` - the time when lock was obtained;
|
|
1020
|
-
- `"
|
|
1110
|
+
- `"ini_ttl"` - `integer` - (milliseconds) initial lock key ttl;
|
|
1021
1111
|
- `"rem_ttl"` - `integer` - (milliseconds) remaining lock key ttl;
|
|
1022
|
-
- `<custom metadata>`- `string`/`integer` - custom metadata passed to the `lock`/`lock!` methods via `meta:` keyword argument (see [lock](
|
|
1112
|
+
- `<custom metadata>`- `string`/`integer` - custom metadata passed to the `lock`/`lock!` methods via `meta:` keyword argument (see [lock](#lock---obtain-a-lock) method documentation);
|
|
1023
1113
|
- additional keys for **reentrant locks** and **extendable reentrant locks**:
|
|
1024
1114
|
- for any type of reentrant locks:
|
|
1025
1115
|
- `"spc_cnt"` - `integer` - how many times the lock was obtained as reentrant lock;
|
|
@@ -1039,7 +1129,7 @@ rql.lock_info("your_lock_name")
|
|
|
1039
1129
|
"lock_key" => "rql:lock:your_lock_name",
|
|
1040
1130
|
"rw_mode" => "write",
|
|
1041
1131
|
"acq_id" => "rql:acq:123/456/567/678/374dd74324",
|
|
1042
|
-
"hst_id" => "rql:
|
|
1132
|
+
"hst_id" => "rql:hst:123/456/678/374dd74324",
|
|
1043
1133
|
"ts" => 123456789.12345,
|
|
1044
1134
|
"ini_ttl" => 5_000,
|
|
1045
1135
|
"rem_ttl" => 4_999
|
|
@@ -1056,7 +1146,7 @@ rql.lock_info("your_lock_name")
|
|
|
1056
1146
|
"lock_key" => "rql:lock:your_lock_name",
|
|
1057
1147
|
"rw_mode" => "write",
|
|
1058
1148
|
"acq_id" => "rql:acq:123/456/567/678/374dd74324",
|
|
1059
|
-
"hst_id" => "rql:
|
|
1149
|
+
"hst_id" => "rql:hst:123/456/678/374dd74324",
|
|
1060
1150
|
"ts" => 123456789.12345,
|
|
1061
1151
|
"ini_ttl" => 5_000,
|
|
1062
1152
|
"rem_ttl" => 4_999,
|
|
@@ -1079,12 +1169,12 @@ rql.lock_info("your_lock_name")
|
|
|
1079
1169
|
"lock_key" => "rql:lock:your_lock_name",
|
|
1080
1170
|
"rw_mode" => "write",
|
|
1081
1171
|
"acq_id" => "rql:acq:123/456/567/678/374dd74324",
|
|
1082
|
-
"hst_id" => "rql:
|
|
1172
|
+
"hst_id" => "rql:hst:123/456/678/374dd74324",
|
|
1083
1173
|
"ts" => 123456789.12345,
|
|
1084
1174
|
"ini_ttl" => 5_000,
|
|
1085
1175
|
"rem_ttl" => 9_444,
|
|
1086
1176
|
# ==> keys for any type of reentrant lock:
|
|
1087
|
-
"
|
|
1177
|
+
"spc_cnt" => 2, # how many times the lock was obtained as reentrant lock
|
|
1088
1178
|
# ==> keys for extendable reentarnt locks with `:extendable_work_through` strategy:
|
|
1089
1179
|
"spc_ext_ttl" => 5_000, # sum of TTL of the each <extendable> reentrant lock (3_000 + 2_000)
|
|
1090
1180
|
"l_spc_ext_ini_ttl" => 2_000, # TTL of the last <extendable> reentrant lock
|
|
@@ -1160,9 +1250,8 @@ rql.lock_info("your_lock_name")
|
|
|
1160
1250
|
|
|
1161
1251
|
Returns an information about the required lock queue by the lock name. The result
|
|
1162
1252
|
represnts the ordered lock request queue that is ordered by score (Redis Sets) and shows
|
|
1163
|
-
lock acquirers and their position in queue.
|
|
1164
|
-
|
|
1165
|
-
you can receive the lock queue info with empty queue value (an empty array).
|
|
1253
|
+
lock acquirers and their position in queue. The queue of write lock requests is an empty array
|
|
1254
|
+
(`"queue" => []`) when only the queue of read lock requests exists.
|
|
1166
1255
|
|
|
1167
1256
|
- get the lock queue information;
|
|
1168
1257
|
- queue represents the ordered set of lock key reqests:
|
|
@@ -1232,6 +1321,7 @@ rql.queued?("your_lock_name") # => true/false
|
|
|
1232
1321
|
|
|
1233
1322
|
- release the concrete lock with lock request queue;
|
|
1234
1323
|
- queue will be relased first;
|
|
1324
|
+
- (the enabled lock index) the lock name is dropped from the [Lock Index](#lock-index) in the same transaction;
|
|
1235
1325
|
- has an alias: `#release_lock`;
|
|
1236
1326
|
- accepts:
|
|
1237
1327
|
- `lock_name` - (required) `[String]` - the lock name that should be released.
|
|
@@ -1276,7 +1366,7 @@ rql.queued?("your_lock_name") # => true/false
|
|
|
1276
1366
|
Return:
|
|
1277
1367
|
- `[Hash<Symbol,Boolean|Hash<Symbol,Numeric|String|Symbol>>]` (`{ ok: true/false, result: Hasn }`);
|
|
1278
1368
|
- `:result` format;
|
|
1279
|
-
- `:rel_time` - `Float` - time spent to process redis commands (in
|
|
1369
|
+
- `:rel_time` - `Float` - time spent to process redis commands (in milliseconds);
|
|
1280
1370
|
- `:rel_key` - `String` - released lock key (RedisQueudLocks-internal lock key name from Redis);
|
|
1281
1371
|
- `:rel_queue` - `String` - released lock queue key (RedisQueuedLocks-internal queue key name from Redis);
|
|
1282
1372
|
- `:queue_res` - `Symbol` - `:released` (or `:nothing_to_release` if the required queue does not exist);
|
|
@@ -1291,7 +1381,7 @@ rql.unlock("your_lock_name")
|
|
|
1291
1381
|
{
|
|
1292
1382
|
ok: true,
|
|
1293
1383
|
result: {
|
|
1294
|
-
rel_time: 0.02, # time spent to lock release (in
|
|
1384
|
+
rel_time: 0.02, # time spent to lock release (in milliseconds)
|
|
1295
1385
|
rel_key: "rql:lock:your_lock_name", # released lock key
|
|
1296
1386
|
rel_queue: "rql:lock_queue:your_lock_name", # released lock key queue
|
|
1297
1387
|
queue_res: :released, # or :nothing_to_release
|
|
@@ -1364,11 +1454,17 @@ rql.unlock_read('report', acquirer_id: reader['acq_id'])
|
|
|
1364
1454
|
<sup>\[[back to top](#usage)\]</sup>
|
|
1365
1455
|
|
|
1366
1456
|
- release all obtained locks and related lock request queues;
|
|
1367
|
-
- queues
|
|
1457
|
+
- all lock keys (locks, read locks and lock queues) are found in one `SCAN` pass and deleted during it with one `DEL` per batch;
|
|
1458
|
+
- (the enabled [Lock Index](#lock-index)) all locks are found in one `ZSCAN` pass over the lock index (the keyspace is never
|
|
1459
|
+
scanned): all the keys of each batch of locks (locks, read locks and lock queues) are deleted with one `DEL` in one transaction
|
|
1460
|
+
with the removal of their lock names from the lock index; keys without the lock index entry (keys of older RQL versions) are not
|
|
1461
|
+
found (see [#reindex_locks](#reindex_locks));
|
|
1368
1462
|
- has an alias: `#release_locks`;
|
|
1369
1463
|
- accepts:
|
|
1370
1464
|
- `:batch_size` - (optional) `[Integer]`
|
|
1371
|
-
- the size of batch of locks and lock queus that should be cleared under the one
|
|
1465
|
+
- the size of batch of locks and lock queus that should be cleared under the one `DEL` redis command at once (and the `SCAN` count);
|
|
1466
|
+
- (the enabled lock index) the number of locks (lock names) whose keys should be cleared under the one `DEL` redis command
|
|
1467
|
+
at once (and the `ZSCAN` count);
|
|
1372
1468
|
- pre-configured in `config['lock_release_batch_size']`;
|
|
1373
1469
|
- `:logger` - (optional) `[::Logger,#debug]`
|
|
1374
1470
|
- custom logger object;
|
|
@@ -1445,10 +1541,13 @@ rql.clear_locks
|
|
|
1445
1541
|
- `:acquirer_id` - (required) `[String]`
|
|
1446
1542
|
- acquirer indentifier whose locks we want to release and which we want to remove from all locks queues;
|
|
1447
1543
|
- `:lock_scan_size` - (optional) `[Integer]`
|
|
1448
|
-
- how many items will be released at a time (uses `SCAN
|
|
1544
|
+
- how many items will be released at a time (uses one `SCAN` pass for batch extraction and `DEL` for batch deletion);
|
|
1545
|
+
- (the enabled [Lock Index](#lock-index)) how many locks will be processed at a time (one `ZSCAN` pass over the lock index
|
|
1546
|
+
for batch extraction and `DEL` for batch deletion);
|
|
1449
1547
|
- pre-configuerd value in `config['clear_locks_of__lock_scan_size']`;
|
|
1450
1548
|
- `:queue_scan_size` - (optional) `[Integer]`
|
|
1451
|
-
- how many queues will be
|
|
1549
|
+
- how many lock queues will be processed in one round trip (lock queues are found by the same `SCAN` pass as locks or
|
|
1550
|
+
derived from the lock names of the same lock index pass);
|
|
1452
1551
|
- pre-configuerd value in `config['clear_locks_of__queue_scan_size']`;
|
|
1453
1552
|
- `:logger` - (optional) `[::Logger,#debug]`
|
|
1454
1553
|
- custom logger object;
|
|
@@ -1550,10 +1649,13 @@ rql.clear_current_locks
|
|
|
1550
1649
|
```
|
|
1551
1650
|
- produces `"redis_queued_locks.release_locks_of"` instrumentation event;
|
|
1552
1651
|
- `:lock_scan_size` - (optional) `[Integer]`
|
|
1553
|
-
- how many items will be released at a time (uses `SCAN
|
|
1652
|
+
- how many items will be released at a time (uses one `SCAN` pass for batch extraction and `DEL` for batch deletion);
|
|
1653
|
+
- (the enabled [Lock Index](#lock-index)) how many locks will be processed at a time (one `ZSCAN` pass over the lock index
|
|
1654
|
+
for batch extraction and `DEL` for batch deletion);
|
|
1554
1655
|
- pre-configuerd value in `config['clear_locks_of__lock_scan_size']`;
|
|
1555
1656
|
- `:queue_scan_size` - (optional) `[Integer]`
|
|
1556
|
-
- how many queues will be
|
|
1657
|
+
- how many lock queues will be processed in one round trip (lock queues are found by the same `SCAN` pass as locks or
|
|
1658
|
+
derived from the lock names of the same lock index pass);
|
|
1557
1659
|
- pre-configuerd value in `config['clear_locks_of__queue_scan_size']`;
|
|
1558
1660
|
- `:logger` - (optional) `[::Logger,#debug]`
|
|
1559
1661
|
- custom logger object;
|
|
@@ -1622,11 +1724,14 @@ rql.release_locks_of(host_id: rql.current_host_id, acquirer_id: rql.current_acqu
|
|
|
1622
1724
|
|
|
1623
1725
|
- extends lock ttl by the required number of milliseconds;
|
|
1624
1726
|
- expects the lock name and the number of milliseconds;
|
|
1727
|
+
- (the enabled lock index) the lock index score of the lock is increased by the same number of milliseconds
|
|
1728
|
+
(see [Lock Index](#lock-index));
|
|
1625
1729
|
- accepts:
|
|
1626
1730
|
- `lock_name` - (required) `[String]`
|
|
1627
1731
|
- the lock name which ttl should be extended;
|
|
1628
1732
|
- `milliseconds` - (required) `[Integer]`
|
|
1629
1733
|
- how many milliseconds should be added to the lock's TTL;
|
|
1734
|
+
- should be a positive `Integer`;
|
|
1630
1735
|
- `:read_write_mode` - (optional) `[Symbol]`
|
|
1631
1736
|
- `:write` (default) - extends the write lock (the behavior described below);
|
|
1632
1737
|
- `:read` - extends the read lock of the current acquirer only (see [Read/Write Locks](#readwrite-locks)):
|
|
@@ -1686,17 +1791,10 @@ rql.release_locks_of(host_id: rql.current_host_id, acquirer_id: rql.current_acqu
|
|
|
1686
1791
|
and for the read lock of the current acquirer, the number of extended read locks for `all_read_locks: true`;
|
|
1687
1792
|
- returns `{ ok: false, result: :async_expire_or_no_lock }` when a lock not found or a lock is already expired during
|
|
1688
1793
|
some steps of invocation (see **Important** section below);
|
|
1689
|
-
- **Important
|
|
1690
|
-
- the
|
|
1691
|
-
- the
|
|
1692
|
-
|
|
1693
|
-
- (2) set new ttl that is calculated as "current pttl + additional milliseconds";
|
|
1694
|
-
- the method uses Redis'es **CAS** (check-and-set) behavior;
|
|
1695
|
-
- what can happen during these steps:
|
|
1696
|
-
- lock is expired between commands or before the first command;
|
|
1697
|
-
- lock is expired before the second command;
|
|
1698
|
-
- lock is expired AND newly acquired by another process (so you will extend the
|
|
1699
|
-
totally new lock with fresh PTTL);
|
|
1794
|
+
- **Important** (`:write` mode):
|
|
1795
|
+
- the write lock TTL is extended atomically by one Lua script (`PTTL` + `PEXPIRE` with "current pttl + additional milliseconds");
|
|
1796
|
+
- the lock owner is not checked: the current write lock is extended, so when your lock is expired and newly obtained
|
|
1797
|
+
by another acquirer, the lock of that acquirer is extended;
|
|
1700
1798
|
- use it at your own risk and consider the async nature when calling this method;
|
|
1701
1799
|
|
|
1702
1800
|
```ruby
|
|
@@ -1717,8 +1815,11 @@ rql.extend_lock_ttl("my_lock", 5_000) # NOTE: add 5_000 milliseconds
|
|
|
1717
1815
|
|
|
1718
1816
|
- get list of obtained locks;
|
|
1719
1817
|
- uses redis `SCAN` under the hood;
|
|
1818
|
+
- (the enabled [Lock Index](#lock-index)) uses the lock index under the hood (one `ZSCAN` pass, the keyspace is never scanned):
|
|
1819
|
+
keys without the lock index entry (keys of older RQL versions) are not found (see [#reindex_locks](#reindex_locks));
|
|
1720
1820
|
- accepts:
|
|
1721
|
-
- `:scan_size` - `Integer` - (`config['key_extraction_batch_size']` by default)
|
|
1821
|
+
- `:scan_size` - `Integer` - (`config['key_extraction_batch_size']` by default) - the `SCAN` count (or the `ZSCAN` count and
|
|
1822
|
+
the batch size of locks with the enabled lock index);
|
|
1722
1823
|
- `:with_info` - `Boolean` - `false` by default (for details see [#locks_info](#locks_info---get-list-of-locks-with-their-info));
|
|
1723
1824
|
- returns:
|
|
1724
1825
|
- `Set<String>` (for `with_info: false`);
|
|
@@ -1750,8 +1851,11 @@ rql.locks # or rql.locks(scan_size: 123)
|
|
|
1750
1851
|
|
|
1751
1852
|
- get list of lock request queues;
|
|
1752
1853
|
- uses redis `SCAN` under the hood;
|
|
1854
|
+
- (the enabled [Lock Index](#lock-index)) uses the lock index under the hood (one `ZSCAN` pass, the keyspace is never scanned):
|
|
1855
|
+
keys without the lock index entry (keys of older RQL versions) are not found (see [#reindex_locks](#reindex_locks));
|
|
1753
1856
|
- accepts
|
|
1754
|
-
- `:scan_size` - `Integer` - (`config['key_extraction_batch_size']` by default)
|
|
1857
|
+
- `:scan_size` - `Integer` - (`config['key_extraction_batch_size']` by default) - the `SCAN` count (or the `ZSCAN` count and
|
|
1858
|
+
the batch size of locks with the enabled lock index);
|
|
1755
1859
|
- `:with_info` - `Boolean` - `false` by default (for details see [#queues_info](#queues_info---get-list-of-queues-with-their-info));
|
|
1756
1860
|
- returns:
|
|
1757
1861
|
- `Set<String>` (for `with_info: false`);
|
|
@@ -1781,10 +1885,15 @@ rql.queues # or rql.queues(scan_size: 123)
|
|
|
1781
1885
|
|
|
1782
1886
|
<sup>\[[back to top](#usage)\]</sup>
|
|
1783
1887
|
|
|
1784
|
-
- get list of taken locks and queues
|
|
1785
|
-
|
|
1888
|
+
- get list of taken locks and queues (all the existing keys of the locks: locks, lock queues, read locks registries,
|
|
1889
|
+
read lock queues, read lock data);
|
|
1890
|
+
- uses redis `SCAN` under the hood (all `rql:lock*` keys);
|
|
1891
|
+
- (the enabled [Lock Index](#lock-index)) uses the lock index under the hood (one `ZSCAN` pass, the keyspace is never scanned):
|
|
1892
|
+
keys without the lock index entry (keys of older RQL versions) and other `rql:lock*` keys are not listed
|
|
1893
|
+
(see [#reindex_locks](#reindex_locks));
|
|
1786
1894
|
- accepts:
|
|
1787
|
-
`:scan_size` - `Integer` - (`config['key_extraction_batch_size']` by default)
|
|
1895
|
+
`:scan_size` - `Integer` - (`config['key_extraction_batch_size']` by default) - the `SCAN` count (or the `ZSCAN` count and
|
|
1896
|
+
the batch size of locks with the enabled lock index);
|
|
1788
1897
|
- returns: `Set<String>`
|
|
1789
1898
|
|
|
1790
1899
|
```ruby
|
|
@@ -1816,7 +1925,8 @@ rql.keys # or rql.keys(scan_size: 123)
|
|
|
1816
1925
|
<sup>\[[back to top](#usage)\]</sup>
|
|
1817
1926
|
|
|
1818
1927
|
- get list of locks with their info;
|
|
1819
|
-
- uses redis `SCAN` under the hod
|
|
1928
|
+
- uses redis `SCAN` under the hod (one pass) or the [Lock Index](#lock-index) (one `ZSCAN` pass, the keyspace is never scanned:
|
|
1929
|
+
the enabled lock index), lock info is extracted in batches of `scan_size` locks (two round trips per batch);
|
|
1820
1930
|
- accepts `scan_size:`/`Integer` option (`config['key_extraction_batch_size']` by default);
|
|
1821
1931
|
- returns `Set<Hash<Symbol,Any>>` (see [#lock_info](#lock_info) and examples below for details).
|
|
1822
1932
|
- contained data: `{ lock: String, status: Symbol, info: Hash<String,Any> }`;
|
|
@@ -1840,6 +1950,8 @@ rql.locks_info # or rql.locks_info(scan_size: 123)
|
|
|
1840
1950
|
"hst_id"=>"rql:hst:41478/4320/4360/848818f09d8c3420",
|
|
1841
1951
|
"ts"=>1711607112.670343,
|
|
1842
1952
|
"ini_ttl"=>15000,
|
|
1953
|
+
"lock_key"=>"rql:lock:some-lock-123",
|
|
1954
|
+
"rw_mode"=>"write",
|
|
1843
1955
|
"rem_ttl"=>13998}},
|
|
1844
1956
|
{:lock=>"rql:lock:some-lock-456",
|
|
1845
1957
|
:status=>:released,
|
|
@@ -1854,7 +1966,8 @@ rql.locks_info # or rql.locks_info(scan_size: 123)
|
|
|
1854
1966
|
<sup>\[[back to top](#usage)\]</sup>
|
|
1855
1967
|
|
|
1856
1968
|
- get list of queues with their info;
|
|
1857
|
-
- uses redis `SCAN` under the hod
|
|
1969
|
+
- uses redis `SCAN` under the hod (one pass) or the [Lock Index](#lock-index) (one `ZSCAN` pass, the keyspace is never scanned:
|
|
1970
|
+
the enabled lock index), queue info is extracted in batches of `scan_size` queues (one round trip per batch);
|
|
1858
1971
|
- accepts `scan_size:`/`Integer` option (`config['key_extraction_batch_size']` by default);
|
|
1859
1972
|
- returns `Set<Hash<Symbol,Any>>` (see [#queue_info](#queue_info) and examples below for details).
|
|
1860
1973
|
- contained data: `{ queue: String, requests: Array<Hash<String,Any>> }`
|
|
@@ -1889,8 +2002,14 @@ before the lock acquire moment occurs and when no any other process does not nee
|
|
|
1889
2002
|
For this case your lock reuquest will be cleared only when any process will try
|
|
1890
2003
|
to acquire this lock again (cuz lock acquirement triggers the removement of expired requests).
|
|
1891
2004
|
|
|
2005
|
+
NOTE: with the enabled [Lock Index](#lock-index) the lock request queue lives as long as its latest lock request (the queue TTL
|
|
2006
|
+
is refreshed with the request `queue_ttl` on each lock request), so the queue of dead requests expires by itself (queues of older
|
|
2007
|
+
RQL versions and queues created with the disabled lock index have no TTL: see [#reindex_locks](#reindex_locks)).
|
|
2008
|
+
|
|
1892
2009
|
In order to help with these dead requests you may periodically call `#clear_dead_requests`
|
|
1893
2010
|
with corresponding `:dead_ttl` option, that is pre-configured by default via `config['dead_request_ttl']`.
|
|
2011
|
+
Lock queues are found via the redis `SCAN` (one pass) or via the [Lock Index](#lock-index) (one `ZSCAN` pass, the keyspace is
|
|
2012
|
+
never scanned: the enabled lock index).
|
|
1894
2013
|
|
|
1895
2014
|
`:dead_ttl` option is required because of it is no any **fast** and **resource-free** way to understand which request
|
|
1896
2015
|
is dead now and is it really dead cuz each request queue can host their requests with
|
|
@@ -1902,6 +2021,8 @@ Accepts:
|
|
|
1902
2021
|
- has a preconfigured value in `config['dead_request_ttl']` (1 day by default);
|
|
1903
2022
|
- `:scan_size` - (optional) `[Integer]`
|
|
1904
2023
|
- the batch of scanned keys for Redis'es SCAN command;
|
|
2024
|
+
- (the enabled lock index) the `ZSCAN` count of the [Lock Index](#lock-index) pass and the number of locks (both lock queues
|
|
2025
|
+
of each lock) processed in one round trip;
|
|
1905
2026
|
- has a preconfigured valie in `config['lock_release_batch_size']`;
|
|
1906
2027
|
- `:logger` - (optional) `[::Logger,#debug]`
|
|
1907
2028
|
- custom logger object;
|
|
@@ -1939,7 +2060,8 @@ Accepts:
|
|
|
1939
2060
|
- makes sense when instrumentation sampling is enabled;
|
|
1940
2061
|
- `false` by default;
|
|
1941
2062
|
|
|
1942
|
-
Returns: `{ ok: true, processed_queues: Set<String> }` returns the
|
|
2063
|
+
Returns: `{ ok: true, result: { processed_queues: Set<String> } }` returns the set of processed lock request queues
|
|
2064
|
+
(write and read lock request queues);
|
|
1943
2065
|
|
|
1944
2066
|
```ruby
|
|
1945
2067
|
rql.clear_dead_requests(dead_ttl: 60 * 60 * 1000) # 1 hour in milliseconds
|
|
@@ -1947,17 +2069,91 @@ rql.clear_dead_requests(dead_ttl: 60 * 60 * 1000) # 1 hour in milliseconds
|
|
|
1947
2069
|
# =>
|
|
1948
2070
|
{
|
|
1949
2071
|
ok: true,
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
2072
|
+
result: {
|
|
2073
|
+
processed_queues: #<Set: {
|
|
2074
|
+
"rql:lock_queue:some-lock-123",
|
|
2075
|
+
"rql:lock_queue:some-lock-456",
|
|
2076
|
+
"rql:lock_read_queue:some-lock-456",
|
|
2077
|
+
"rql:lock_queue:your-other-lock",
|
|
2078
|
+
...
|
|
2079
|
+
}>
|
|
2080
|
+
}
|
|
1956
2081
|
}
|
|
1957
2082
|
```
|
|
1958
2083
|
|
|
1959
2084
|
---
|
|
1960
2085
|
|
|
2086
|
+
#### #reindex_locks
|
|
2087
|
+
|
|
2088
|
+
<sup>\[[back to top](#usage)\]</sup>
|
|
2089
|
+
|
|
2090
|
+
- adds the lock keys without the lock index entry (keys of older RQL versions and keys created with the disabled lock index)
|
|
2091
|
+
to the lock index (see [Lock Index](#lock-index));
|
|
2092
|
+
- invoke it once after the lock index is enabled (`config['lock_index_enabled'] = true`) on **all** your processes: with the
|
|
2093
|
+
enabled lock index all lock-iterating operations (the swarm, `#locks`, `#queues`, `#keys`, `#clear_locks`, `#clear_locks_of`,
|
|
2094
|
+
`#clear_dead_requests`, ...) find locks via the lock index only;
|
|
2095
|
+
- works regardless of `config['lock_index_enabled']` (it is not needed while the lock index is disabled: the lock index is
|
|
2096
|
+
neither used nor maintained then);
|
|
2097
|
+
- one `SCAN` pass over the RQL keys (`rql:lock*`) with pipelined batches (two round trips per batch);
|
|
2098
|
+
- write locks and read locks registries are indexed with their remaining TTL (+ the grace period), lock queues without TTL
|
|
2099
|
+
(older RQL versions) get the `queue_ttl` TTL first; read lock data is not indexed (it is found via the read locks registry);
|
|
2100
|
+
- idempotent: the lock index scores are never decreased (it can be invoked at any time);
|
|
2101
|
+
- accepts:
|
|
2102
|
+
- `:scan_size` - (optional) `[Integer]`
|
|
2103
|
+
- the batch of scanned keys for Redis'es SCAN command;
|
|
2104
|
+
- pre-configured in `config['lock_release_batch_size']`;
|
|
2105
|
+
- `:queue_ttl` - (optional) `[Integer]`
|
|
2106
|
+
- TTL of the lock queues without TTL (in seconds);
|
|
2107
|
+
- pre-configured in `config['default_queue_ttl']`;
|
|
2108
|
+
- `:lock_index_grace_period` - (optional) `[Integer]`
|
|
2109
|
+
- the lock index grace period (in milliseconds);
|
|
2110
|
+
- pre-configured in `config['lock_index_grace_period']`;
|
|
2111
|
+
- `:logger` - (optional) `[::Logger,#debug]`
|
|
2112
|
+
- custom logger object;
|
|
2113
|
+
- pre-configured in `config['logger']`;
|
|
2114
|
+
- `:instrumenter` - (optional) `[#notify]`
|
|
2115
|
+
- custom instrumenter object;
|
|
2116
|
+
- pre-configured in `config['instrumetner']`;
|
|
2117
|
+
- `:instrument` - (optional) `[NilClass,Any]`;
|
|
2118
|
+
- custom instrumentation data wich will be passed to the instrumenter's payload with :instrument key;
|
|
2119
|
+
- `nil` by default (no additional data);
|
|
2120
|
+
- `:log_sampling_enabled` - (optional) `[Boolean]`
|
|
2121
|
+
- enables **log sampling**;
|
|
2122
|
+
- pre-configured in `config['log_sampling_enabled']`;
|
|
2123
|
+
- `:log_sampling_percent` - (optional) `[Integer]`
|
|
2124
|
+
- **log sampling**:the percent of cases that should be logged;
|
|
2125
|
+
- pre-configured in `config['log_sampling_percent']`;
|
|
2126
|
+
- `:log_sampler` - (optional) `[#sampling_happened?,Module<RedisQueuedLocks::Logging::Sampler>]`
|
|
2127
|
+
- **log sampling**: percent-based log sampler that decides should be RQL case logged or not;
|
|
2128
|
+
- pre-configured in `config['log_sampler']`;
|
|
2129
|
+
- `log_sample_this` - (optional) `[Boolean]`
|
|
2130
|
+
- marks the method that everything should be logged despite the enabled log sampling;
|
|
2131
|
+
- makes sense when log sampling is enabled;
|
|
2132
|
+
- `false` by default;
|
|
2133
|
+
- `:instr_sampling_enabled` - (optional) `[Boolean]`
|
|
2134
|
+
- enables **instrumentaion sampling**;
|
|
2135
|
+
- pre-configured in `config['instr_sampling_enabled']`;
|
|
2136
|
+
- `instr_sampling_percent` - (optional) `[Integer]`
|
|
2137
|
+
- the percent of cases that should be instrumented;
|
|
2138
|
+
- pre-configured in `config['instr_sampling_percent']`;
|
|
2139
|
+
- `instr_sampler` - (optional) `[#sampling_happened?,Module<RedisQueuedLocks::Instrument::Sampler>]`
|
|
2140
|
+
- percent-based log sampler that decides should be RQL case instrumented or not;
|
|
2141
|
+
- pre-configured in `config['instr_sampler']`;
|
|
2142
|
+
- `instr_sample_this` - (optional) `[Boolean]`
|
|
2143
|
+
- marks the method that everything should be instrumneted despite the enabled instrumentation sampling;
|
|
2144
|
+
- makes sense when instrumentation sampling is enabled;
|
|
2145
|
+
- `false` by default;
|
|
2146
|
+
- returns `{ ok: true, result: { idx_key_cnt: Integer } }` (the number of indexed keys);
|
|
2147
|
+
|
|
2148
|
+
```ruby
|
|
2149
|
+
rql.reindex_locks
|
|
2150
|
+
|
|
2151
|
+
# =>
|
|
2152
|
+
{ ok: true, result: { idx_key_cnt: 123 } }
|
|
2153
|
+
```
|
|
2154
|
+
|
|
2155
|
+
---
|
|
2156
|
+
|
|
1961
2157
|
#### #current_acquirer_id
|
|
1962
2158
|
|
|
1963
2159
|
<sup>\[[back to top](#usage)\]</sup>
|
|
@@ -2084,6 +2280,12 @@ rql.possible_host_ids
|
|
|
2084
2280
|
|
|
2085
2281
|
**This documentation section is in progress!** (see the changelog and the usage preview for details at this moment)
|
|
2086
2282
|
|
|
2283
|
+
- zombie locks and zombie lock requests are found in one `SCAN` pass over the RQL keys (`rql:lock*`) or, with the enabled
|
|
2284
|
+
[Lock Index](#lock-index) (`config['lock_index_enabled']`), in one `ZSCAN` pass over the lock names: the swarm never scans
|
|
2285
|
+
the whole Redis keyspace then (keys of your other applications are never visited);
|
|
2286
|
+
- after enabling the lock index invoke [#reindex_locks](#reindex_locks) once (when all your processes use the lock index):
|
|
2287
|
+
locks created without the lock index are not indexed and are not found by the swarm until then;
|
|
2288
|
+
|
|
2087
2289
|
[(work and usage preview (temporary example-based docs))](#work-and-usage-preview-temporary-example-based-docs)
|
|
2088
2290
|
|
|
2089
2291
|
- [How to Swarm](#how-to-swarm)
|
|
@@ -2115,6 +2317,8 @@ rql.possible_host_ids
|
|
|
2115
2317
|
# NOTE: auto-swarm your RQL client after initalization (run swarm elements and their supervisor)
|
|
2116
2318
|
config['swarm.auto_swarm'] = false
|
|
2117
2319
|
|
|
2320
|
+
# NOTE: the periods, the zombie ttl and the scan sizes should be positive Integers
|
|
2321
|
+
|
|
2118
2322
|
# supervisor configs
|
|
2119
2323
|
config['swarm.supervisor.liveness_probing_period'] = 2 # NOTE: in seconds
|
|
2120
2324
|
|
|
@@ -2131,8 +2335,8 @@ rql.possible_host_ids
|
|
|
2131
2335
|
config['swarm.flush_zombies.enabled_for_swarm'] = true # NOTE: run zombie flushing or not
|
|
2132
2336
|
config['swarm.flush_zombies.zombie_flush_period'] = 10 # NOTE: (in seconds) period of time when the zombie flusher is triggered
|
|
2133
2337
|
config['swarm.flush_zombies.zombie_ttl'] = 15_000 # NOTE: (in milliseconds) when the lock/host/acquirer is considered a zombie
|
|
2134
|
-
config['swarm.flush_zombies.zombie_lock_scan_size'] = 500 # NOTE:
|
|
2135
|
-
config['swarm.flush_zombies.zombie_queue_scan_size'] = 500 # NOTE:
|
|
2338
|
+
config['swarm.flush_zombies.zombie_lock_scan_size'] = 500 # NOTE: SCAN count (or ZSCAN count with the enabled lock index) and the batch size of locks during zombie flushing
|
|
2339
|
+
config['swarm.flush_zombies.zombie_queue_scan_size'] = 500 # NOTE: the batch size of lock queues during zombie flushing (one round trip per batch)
|
|
2136
2340
|
# (flush_zombies) individual redis config
|
|
2137
2341
|
config['swarm.flush_zombies.redis_config.sentinel'] = false # NOTE: individual redis config
|
|
2138
2342
|
config['swarm.flush_zombies.redis_config.pooled'] = false # NOTE: individual redis config
|
|
@@ -2267,6 +2471,120 @@ rql.possible_host_ids
|
|
|
2267
2471
|
```
|
|
2268
2472
|
</details>
|
|
2269
2473
|
|
|
2474
|
+
<details>
|
|
2475
|
+
<summary>lock index key in Redis (the enabled lock index only)</summary>
|
|
2476
|
+
|
|
2477
|
+
```ruby
|
|
2478
|
+
"rql:index:locks"
|
|
2479
|
+
```
|
|
2480
|
+
</details>
|
|
2481
|
+
|
|
2482
|
+
---
|
|
2483
|
+
|
|
2484
|
+
## Lock Index
|
|
2485
|
+
|
|
2486
|
+
<sup>\[[back to top](#table-of-contents)\]</sup>
|
|
2487
|
+
|
|
2488
|
+
> Find locks without scanning the whole Redis keyspace (opt-in: `config['lock_index_enabled'] = true`).
|
|
2489
|
+
|
|
2490
|
+
> [!IMPORTANT]
|
|
2491
|
+
> Your locks should work either **always with** the lock index or **always without** it (the same
|
|
2492
|
+
> `config['lock_index_enabled']` value on all your processes that share the Redis database): locks obtained without the lock
|
|
2493
|
+
> index are not indexed and are invisible to the lock-iterating operations of the processes with the enabled lock index
|
|
2494
|
+
> (the swarm, `#locks`, `#queues`, `#keys`, `#clear_locks`, ...). So every time you enable the lock index after working without it
|
|
2495
|
+
> (including the upgrade from older RQL versions) invoke [#reindex_locks](#reindex_locks) once all your processes use the lock index.
|
|
2496
|
+
|
|
2497
|
+
- **disabled by default**: lock-iterating operations scan the Redis keyspace (`SCAN MATCH rql:lock*`, the classic way),
|
|
2498
|
+
no lock index commands are sent and lock request queues have no TTL; enable the lock index via config:
|
|
2499
|
+
|
|
2500
|
+
```ruby
|
|
2501
|
+
RedisQueuedLocks::Client.new(redis_client) do |config|
|
|
2502
|
+
config['lock_index_enabled'] = true
|
|
2503
|
+
# (milliseconds) (default: 60_000)
|
|
2504
|
+
# - the lock name stays in the lock index for this period after the expiration of all its keys;
|
|
2505
|
+
# - why: the lock index scores use the client clock, so this margin protects the index from the clock skew
|
|
2506
|
+
# between your hosts and from the network latency (otherwise a live lock can drop out of the index and
|
|
2507
|
+
# the swarm/lock-iterating operations will not see it);
|
|
2508
|
+
# - keep it greater than the maximal clock skew between your hosts plus the network latency;
|
|
2509
|
+
config['lock_index_grace_period'] = 60_000
|
|
2510
|
+
end
|
|
2511
|
+
```
|
|
2512
|
+
- the client-wide mode (not a per-call option): the lock acquirement maintains the lock index and all lock-iterating operations
|
|
2513
|
+
read it only when it is enabled; use the same mode on **all** your processes that share the Redis database (locks of
|
|
2514
|
+
the processes without the lock index are not indexed: lock-iterating operations of the processes with the lock index do not
|
|
2515
|
+
see them); the swarm reads the mode when it is started;
|
|
2516
|
+
- the lock index is a sorted set `rql:index:locks`: the member is the lock name, the score is the upper bound of the lifetime
|
|
2517
|
+
of all lock keys (write lock, read locks registry, lock request queues): `client time (epoch, ms) + key ttl + grace period`;
|
|
2518
|
+
- all lock-iterating operations iterate over the lock index (`ZSCAN`) and derive lock keys from the lock names instead of
|
|
2519
|
+
the full keyspace `SCAN`: their cost depends on the number of your locks only (not on the size of your Redis database):
|
|
2520
|
+
- the swarm: `#flush_zombies`, the zombie flusher, `#zombie_locks`, `#zombie_acquirers`, `#zombies_info`;
|
|
2521
|
+
- `#locks`/`#locks_info`, `#queues`/`#queues_info`, `#keys`, `#clear_locks`, `#clear_locks_of`/`#clear_current_locks`,
|
|
2522
|
+
`#clear_dead_requests`;
|
|
2523
|
+
- only [#reindex_locks](#reindex_locks) scans the keyspace (it adds the keys without the lock index entries to the lock index);
|
|
2524
|
+
- the index is maintained by the lock acquirement itself (no background jobs, no additional Redis round trips):
|
|
2525
|
+
- each lock request adds the lock name to the index (`ZADD GT` with the queue ttl), sets the lock request queue TTL
|
|
2526
|
+
(the queue lives as long as its latest lock request) and drops the expired lock names from the index (`ZREMRANGEBYSCORE`):
|
|
2527
|
+
4 commands in the existing queueing pipeline;
|
|
2528
|
+
- each lock obtaining (write and read) adds the lock name with the lock ttl in the same transaction (1 command);
|
|
2529
|
+
- each lock extension (`#extend_lock_ttl`, `:extendable_work_through` reentrant locks) increases the score by the extension;
|
|
2530
|
+
- the lock name is dropped from the index when all its keys are expired and the grace period is over, by `#unlock` (in the
|
|
2531
|
+
release transaction) and by `#clear_locks` (in the transaction that deletes the keys of the lock);
|
|
2532
|
+
- `config['lock_index_grace_period']` (`60_000` milliseconds by default) and `lock_index_grace_period:` option of
|
|
2533
|
+
`#lock`/`#lock!`/`#lock_series`/`#lock_series!`: the lock name stays in the index for this period after the expiration of
|
|
2534
|
+
all its keys; it should be greater than the maximal client clock skew between your hosts plus the duration of one lock
|
|
2535
|
+
acquirement attempt (network latency) (otherwise a lock can be dropped from the index before its expiration and the swarm
|
|
2536
|
+
will not see it);
|
|
2537
|
+
- (enabling) locks of older RQL versions and locks created with the disabled lock index have no lock index entries (they are not
|
|
2538
|
+
found by lock-iterating operations till then): enable the lock index on **all** your processes, then invoke
|
|
2539
|
+
[#reindex_locks](#reindex_locks) once;
|
|
2540
|
+
- (disabling) lock-iterating operations scan the keyspace again; the lock index key is neither used nor maintained anymore
|
|
2541
|
+
(you can delete it: `DEL rql:index:locks`); invoke [#reindex_locks](#reindex_locks) again after the next enabling;
|
|
2542
|
+
- (limitation) the lock index is one Redis key written on each lock request (a "hot" key): fine for a standalone Redis
|
|
2543
|
+
(and Sentinel); Redis Cluster is not supported (RQL transactions use several keys of each lock without hash tags);
|
|
2544
|
+
|
|
2545
|
+
#### Lock Index Performance
|
|
2546
|
+
|
|
2547
|
+
<sup>\[[back to top](#lock-index)\]</sup>
|
|
2548
|
+
|
|
2549
|
+
- measured on a local Redis with 1 million keys of other applications in the same database and ~1 200 locks (+100 lock queues
|
|
2550
|
+
for the lock-iterating operations, a zombie host for `#flush_zombies`): wall time and Redis CPU time of one call with
|
|
2551
|
+
the keyspace `SCAN` (`config['lock_index_enabled'] = false`) and with the lock index (`config['lock_index_enabled'] = true`):
|
|
2552
|
+
|
|
2553
|
+
| Operation | Keyspace `SCAN` (wall / Redis CPU) | Lock index (wall / Redis CPU) | Speedup (wall / Redis CPU) |
|
|
2554
|
+
|---|---|---|---|
|
|
2555
|
+
| `#flush_zombies` (the zombie flusher) | 263 ms / 181 ms | 20 ms / 2.4 ms | **x13** / **x75** |
|
|
2556
|
+
| `#locks` | 269 ms / 192 ms | 7.7 ms / 1.4 ms | **x35** / **x137** |
|
|
2557
|
+
| `#locks_info` | 288 ms / 194 ms | 23 ms / 2.3 ms | **x12.5** / **x84** |
|
|
2558
|
+
| `#queues` / `#queues_info` | 260-280 ms / 190 ms | 8 ms / 1-2 ms | **x32-35** / **x95-190** |
|
|
2559
|
+
| `#keys` | 272 ms / 193 ms | 17 ms / 2.6 ms | **x16** / **x74** |
|
|
2560
|
+
| `#clear_dead_requests` | 574 ms / 224 ms | 18 ms / 2.2 ms | **x32** / **x102** |
|
|
2561
|
+
| `#clear_locks_of` / `#clear_current_locks` | 322 ms / 200 ms | 14 ms / 1.9 ms | **x23** / **x105** |
|
|
2562
|
+
| `#clear_locks` | 574 ms / 225 ms | 14 ms / 5.1 ms | **x41** / **x44** |
|
|
2563
|
+
|
|
2564
|
+
- the speedup grows with the number of keys in your Redis database: the keyspace `SCAN` visits all keys of the database (keys
|
|
2565
|
+
of your other applications too), the lock index visits the lock names only (the cost depends on the number of your locks and
|
|
2566
|
+
on the lock index size, see [Lock Index Size](#lock-index-size));
|
|
2567
|
+
- the zombie flusher runs in every process with the swarm (every `zombie_flush_period`): the saved Redis CPU is multiplied by
|
|
2568
|
+
the number of your processes;
|
|
2569
|
+
- the price is paid by the lock acquirement (it maintains the lock index without new round trips: 4 more commands in
|
|
2570
|
+
the queueing pipeline and 1 more command in the obtaining transaction): ~10% fewer locks per second in a single-thread
|
|
2571
|
+
`lock {}` loop on a local Redis, 280 -> 348 allocations per `lock {}`;
|
|
2572
|
+
|
|
2573
|
+
#### Lock Index Size
|
|
2574
|
+
|
|
2575
|
+
<sup>\[[back to top](#lock-index)\]</sup>
|
|
2576
|
+
|
|
2577
|
+
- the lock index size is about `<the number of distinct lock names per second> * (max(queue_ttl, ttl) + grace period)`
|
|
2578
|
+
(`~75` seconds of lock names with the default settings: `config['default_queue_ttl']` (15 seconds) +
|
|
2579
|
+
`config['lock_index_grace_period']` (60 seconds));
|
|
2580
|
+
- with very diverse lock names (for example, `order:#{id}`) lock-iterating operations (the swarm, `#locks`, `#queues`, `#keys`,
|
|
2581
|
+
`#clear_locks`, ...) visit the dead lock names of this window too (lock names whose keys are already expired);
|
|
2582
|
+
- if it becomes noticeable, there are two levers:
|
|
2583
|
+
- reduce `config['lock_index_grace_period']` (keep it greater than the maximal client clock skew between your hosts plus
|
|
2584
|
+
the network latency);
|
|
2585
|
+
- **TODO**: a cheap existence check of the lock name (`EXISTS` of its keys) before reading the lock details in
|
|
2586
|
+
lock-iterating operations;
|
|
2587
|
+
|
|
2270
2588
|
---
|
|
2271
2589
|
|
|
2272
2590
|
## Lock Access Strategies
|
|
@@ -2292,7 +2610,7 @@ rql.possible_host_ids
|
|
|
2292
2610
|
<sup>\[[back to top](#table-of-contents)\]</sup>
|
|
2293
2611
|
|
|
2294
2612
|
- `read_write_mode:` attribute of `#lock` and `#lock!`:
|
|
2295
|
-
- `:write` (default) - exclusive lock (the classic RQL lock): waits for the write lock and for all read locks;
|
|
2613
|
+
- `:write` (**default**) - exclusive lock (the classic RQL lock): waits for the write lock and for all read locks;
|
|
2296
2614
|
- `:read` - shared lock: waits for the write lock only (read locks of different acquirers work in parallel);
|
|
2297
2615
|
|
|
2298
2616
|
```ruby
|
|
@@ -2301,7 +2619,9 @@ rql.lock('report', read_write_mode: :read) { read_report }
|
|
|
2301
2619
|
|
|
2302
2620
|
# writer waits for all readers (and readers wait for the writer)
|
|
2303
2621
|
rql.lock('report', read_write_mode: :write) { rebuild_report }
|
|
2304
|
-
|
|
2622
|
+
|
|
2623
|
+
# write mode is default:
|
|
2624
|
+
rql.lock('report') { rebuild_report } # read_write_mode: :write by default
|
|
2305
2625
|
```
|
|
2306
2626
|
|
|
2307
2627
|
- lock request ordering (`access_strategy`):
|
|
@@ -2311,10 +2631,11 @@ rql.lock('report') { rebuild_report } # (write mode is the default mode)
|
|
|
2311
2631
|
- `:random` - lock queues are ignored: any free lock is obtained (a continuous flow of readers can starve writers);
|
|
2312
2632
|
- read locks:
|
|
2313
2633
|
- each read lock has its own `ttl` and is released (after the block execution) or expired independently of other read locks;
|
|
2314
|
-
- `ttl` should be a positive `Integer
|
|
2634
|
+
- `ttl` should be a positive `Integer`;
|
|
2315
2635
|
- each read lock has its own data with the same structure as the write lock data: acquirer, host, timestamp,
|
|
2316
2636
|
initial ttl, `:meta` (with the same reserved keys) and reentrant lock data (`spc_cnt`, `spc_ext_ttl`, ...);
|
|
2317
|
-
the read lock data lives as long as the read lock and is returned by `#lock_info`/`#locks_info` for each reader
|
|
2637
|
+
the read lock data lives as long as the read lock and is returned by `#lock_info`/`#locks_info` for each reader
|
|
2638
|
+
(acquirer and host are not stored: both are derived from the acquirer id);
|
|
2318
2639
|
- `#unlock` releases the lock entirely: the write lock, all read locks (with their data) and all lock requests;
|
|
2319
2640
|
- `#unlock_read` (aka `#release_read_lock`) releases the read lock of the current acquirer
|
|
2320
2641
|
(suitable for read locks obtained without a block), or the read lock of the acquirer passed via `acquirer_id:`;
|
|
@@ -2363,7 +2684,7 @@ rql.lock('report') { rebuild_report } # (write mode is the default mode)
|
|
|
2363
2684
|
- `rql:lock_queue:<lock_name>` - write lock requests (`ZSET`, the classic RQL lock queue);
|
|
2364
2685
|
- `rql:lock_readers:<lock_name>` - read locks (`ZSET`: acquirer id => read lock expiration time in Redis time, milliseconds);
|
|
2365
2686
|
- `rql:lock_read_queue:<lock_name>` - read lock requests (`ZSET`);
|
|
2366
|
-
- `rql:lock_reader:<lock_name>:<acquirer_id>` - read lock data (`HASH`, the same structure as the write lock, read lock ttl);
|
|
2687
|
+
- `rql:lock_reader:<lock_name>:<acquirer_id>` - read lock data (`HASH`, the same structure as the write lock without acquirer and host ids, read lock ttl);
|
|
2367
2688
|
- implementation notes:
|
|
2368
2689
|
- optimistic transactions only (`WATCH`/`MULTI`): readers watch the write lock key, writers watch the write lock key and the read locks key,
|
|
2369
2690
|
so readers never invalidate transactions of each other;
|
|
@@ -2410,6 +2731,7 @@ rql.lock('report') { rebuild_report } # (write mode is the default mode)
|
|
|
2410
2731
|
"[redis_queued_locks.start_try_to_lock_cycle]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
|
|
2411
2732
|
"[redis_queued_locks.dead_score_reached__reset_acquirer_position]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
|
|
2412
2733
|
"[redis_queued_locks.lock_obtained]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "acq_time");
|
|
2734
|
+
"[redis_queued_locks.lock_obtaining_failed]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "acq_time", "result");
|
|
2413
2735
|
"[redis_queued_locks.extendable_reentrant_lock_obtained]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "acq_time");
|
|
2414
2736
|
"[redis_queued_locks.reentrant_lock_obtained]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "acq_time");
|
|
2415
2737
|
"[redis_queued_locks.fail_fast_or_limits_reached_or_deadlock__dequeue]" # (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
|
|
@@ -2448,6 +2770,19 @@ rql.lock('report') { rebuild_report } # (write mode is the default mode)
|
|
|
2448
2770
|
"[redis_queued_locks.expire_lock_series]" # (logs "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
|
|
2449
2771
|
```
|
|
2450
2772
|
|
|
2773
|
+
- operation logs (one log line per operation that changes Redis keys, with the time of the whole operation; the same data as
|
|
2774
|
+
the instrumentation event of the operation without `instrument`):
|
|
2775
|
+
|
|
2776
|
+
```ruby
|
|
2777
|
+
"[redis_queued_locks.explicit_lock_release]" # (logs "lock_key", "lock_key_queue", "rel_time", "at");
|
|
2778
|
+
"[redis_queued_locks.explicit_read_lock_release]" # (logs "lock_key", "acq_id", "lock_res", "rel_time", "at");
|
|
2779
|
+
"[redis_queued_locks.explicit_all_locks_release]" # (logs "at", "rel_time", "rel_key_cnt");
|
|
2780
|
+
"[redis_queued_locks.release_locks_of]" # (logs "at", "hst_id", "acq_id", "rel_time", "rel_key_cnt", "tch_queue_cnt");
|
|
2781
|
+
"[redis_queued_locks.extend_lock_ttl]" # (logs "lock_key", "rw_mode", "all_read_locks", "ext_ttl", "extended_locks_count", "ext_time", "at");
|
|
2782
|
+
"[redis_queued_locks.clear_dead_requests]" # (logs "dead_ttl", "prc_queue_cnt", "rel_time", "at");
|
|
2783
|
+
"[redis_queued_locks.reindex_locks]" # (logs "idx_key_cnt", "idx_time", "at");
|
|
2784
|
+
```
|
|
2785
|
+
|
|
2451
2786
|
- read/write try-lock logs (see [Read/Write Locks](#readwrite-locks)):
|
|
2452
2787
|
- `single_process_lock_conflict__lock_upgrade` - write lock request of the acquirer that holds the read lock
|
|
2453
2788
|
(read-to-write lock upgrade is not supported for `:work_through` and `:extendable_work_through` conflict strategies);
|
|
@@ -2463,7 +2798,7 @@ rql.lock('report') { rebuild_report } # (write mode is the default mode)
|
|
|
2463
2798
|
<sup>\[[back to top](#table-of-contents)\]</sup>
|
|
2464
2799
|
|
|
2465
2800
|
**NOTICE**: logging can be sampled via:
|
|
2466
|
-
- `config['
|
|
2801
|
+
- `config['log_sampling_enabled'] = true` (**false** by default);
|
|
2467
2802
|
- `config['log_sampler'] = RedisQueuedLocks::Logging::Sampler` (used by default);
|
|
2468
2803
|
- see **RedisQueuedLocks::Logging::Sampler** implementation in source code for customization details;
|
|
2469
2804
|
|
|
@@ -2477,6 +2812,7 @@ rql.lock('report') { rebuild_report } # (write mode is the default mode)
|
|
|
2477
2812
|
# - "[redis_queued_locks.start_try_to_lock_cycle]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
|
|
2478
2813
|
# - "[redis_queued_locks.dead_score_reached__reset_acquirer_position]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
|
|
2479
2814
|
# - "[redis_queued_locks.lock_obtained]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "acq_time");
|
|
2815
|
+
# - "[redis_queued_locks.lock_obtaining_failed]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "acq_time", "result");
|
|
2480
2816
|
# - "[redis_queued_locks.extendable_reentrant_lock_obtained]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "acq_time");
|
|
2481
2817
|
# - "[redis_queued_locks.reentrant_lock_obtained]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "acq_time");
|
|
2482
2818
|
# - "[redis_queued_locks.fail_fast_or_limits_reached_or_deadlock__dequeue]" (logs "lock_key", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
|
|
@@ -2485,6 +2821,13 @@ rql.lock('report') { rebuild_report } # (write mode is the default mode)
|
|
|
2485
2821
|
# - "[redis_queued_locks.start_lock_series_obtaining]" (logs "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
|
|
2486
2822
|
# - "[redis_queued_locks.lock_series_obtained]" (logs "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode", "acq_time");
|
|
2487
2823
|
# - "[redis_queued_locks.expire_lock_series]" (logs "lock_keys", "queue_ttl", "acq_id", "hst_id", "acs_strat", "rw_mode");
|
|
2824
|
+
# - "[redis_queued_locks.explicit_lock_release]" (logs "lock_key", "lock_key_queue", "rel_time", "at");
|
|
2825
|
+
# - "[redis_queued_locks.explicit_read_lock_release]" (logs "lock_key", "acq_id", "lock_res", "rel_time", "at");
|
|
2826
|
+
# - "[redis_queued_locks.explicit_all_locks_release]" (logs "at", "rel_time", "rel_key_cnt");
|
|
2827
|
+
# - "[redis_queued_locks.release_locks_of]" (logs "at", "hst_id", "acq_id", "rel_time", "rel_key_cnt", "tch_queue_cnt");
|
|
2828
|
+
# - "[redis_queued_locks.extend_lock_ttl]" (logs "lock_key", "rw_mode", "all_read_locks", "ext_ttl", "extended_locks_count", "ext_time", "at");
|
|
2829
|
+
# - "[redis_queued_locks.clear_dead_requests]" (logs "dead_ttl", "prc_queue_cnt", "rel_time", "at");
|
|
2830
|
+
# - "[redis_queued_locks.reindex_locks]" (logs "idx_key_cnt", "idx_time", "at");
|
|
2488
2831
|
# - "rw_mode" is the requested lock mode ('write'/'read'), the held lock mode for "expire_lock" and "decrease_lock",
|
|
2489
2832
|
# the mode of the series for lock series logs;
|
|
2490
2833
|
# - by default uses VoidLogger that does nothing;
|
|
@@ -2523,7 +2866,7 @@ config['log_sampling_enabled'] = false
|
|
|
2523
2866
|
|
|
2524
2867
|
# (default: 15)
|
|
2525
2868
|
# - the percent of cases that should be logged;
|
|
2526
|
-
# - take an effect when <config['
|
|
2869
|
+
# - take an effect when <config['log_sampling_enabled']> is true;
|
|
2527
2870
|
# - works in tandem with <config['log_sampling_enabled']> and <config['log_sampler']> configs;
|
|
2528
2871
|
config['log_sampling_percent'] = 15
|
|
2529
2872
|
|
|
@@ -2590,7 +2933,7 @@ config['instr_sampling_enabled'] = false
|
|
|
2590
2933
|
|
|
2591
2934
|
# (default: 15)
|
|
2592
2935
|
# - the percent of cases that should be instrumented;
|
|
2593
|
-
# - take an effect when <config['
|
|
2936
|
+
# - take an effect when <config['instr_sampling_enabled']> is true;
|
|
2594
2937
|
# - works in tandem with <config['instr_sampling_enabled']> and <config['instr_sampler']> configs;
|
|
2595
2938
|
config['instr_sampling_percent'] = 15
|
|
2596
2939
|
|
|
@@ -2613,6 +2956,7 @@ config['instr_sampler'] = RedisQueuedLocks::Instrument::Sampler
|
|
|
2613
2956
|
List of instrumentation events
|
|
2614
2957
|
|
|
2615
2958
|
- ["redis_queued_locks.lock_obtained"](#redis_queued_lockslock_obtained)
|
|
2959
|
+
- ["redis_queued_locks.lock_obtaining_failed"](#redis_queued_lockslock_obtaining_failed)
|
|
2616
2960
|
- ["redis_queued_locks.extendable_reentrant_lock_obtained"](#redis_queued_locksextendable_reentrant_lock_obtained)
|
|
2617
2961
|
- ["redis_queued_locks.reentrant_lock_obtained"](#redis_queued_locksreentrant_lock_obtained)
|
|
2618
2962
|
- ["redis_queued_locks.lock_hold_and_release"](#redis_queued_lockslock_hold_and_release)
|
|
@@ -2623,6 +2967,17 @@ List of instrumentation events
|
|
|
2623
2967
|
- ["redis_queued_locks.explicit_read_lock_release"](#redis_queued_locksexplicit_read_lock_release)
|
|
2624
2968
|
- ["redis_queued_locks.explicit_all_locks_release"](#redis_queued_locksexplicit_all_locks_release)
|
|
2625
2969
|
- ["redis_queued_locks.release_locks_of"](#redis_queued_locksrelease_locks_of)
|
|
2970
|
+
- ["redis_queued_locks.extend_lock_ttl"](#redis_queued_locksextend_lock_ttl)
|
|
2971
|
+
- ["redis_queued_locks.clear_dead_requests"](#redis_queued_locksclear_dead_requests)
|
|
2972
|
+
- ["redis_queued_locks.reindex_locks"](#redis_queued_locksreindex_locks)
|
|
2973
|
+
|
|
2974
|
+
What is instrumented (each event has a log line with the same name, see [Logging](#logging)):
|
|
2975
|
+
- the lock acquirement (`#lock`/`#lock!`, `#lock_series`/`#lock_series!`): each meaningful outcome (the lock is obtained,
|
|
2976
|
+
not obtained, held and released);
|
|
2977
|
+
- each operation that changes Redis keys (`#unlock`, `#unlock_read`, `#clear_locks`, `#clear_locks_of`/`#clear_current_locks`,
|
|
2978
|
+
`#extend_lock_ttl`, `#clear_dead_requests`, `#reindex_locks`): one event with the time of the whole operation;
|
|
2979
|
+
- lock information queries (`#lock_info`, `#locks`, `#queues`, `#keys`, ...) are not instrumented;
|
|
2980
|
+
- the swarm (`#probe_hosts`, `#flush_zombies` and the swarm elements) is not instrumented yet;
|
|
2626
2981
|
|
|
2627
2982
|
Detalized event semantics and payload structure:
|
|
2628
2983
|
|
|
@@ -2640,6 +2995,23 @@ Detalized event semantics and payload structure:
|
|
|
2640
2995
|
- `:acq_time` - `float`/`milliseconds` - time spent on lock acquiring;
|
|
2641
2996
|
- `:instrument` - `nil`/`Any` - custom data passed to the `#lock`/`#lock!` method as `:instrument` attribute;
|
|
2642
2997
|
|
|
2998
|
+
##### `"redis_queued_locks.lock_obtaining_failed"`
|
|
2999
|
+
- <sup>\[[back to the list](#instrumentation-events)\]</sup>
|
|
3000
|
+
- a moment when the lock acquirement is failed (the lock is not obtained);
|
|
3001
|
+
- raised from `#lock`/`#lock!` (with `raise_errors: true` - before the error is raised) and from `#lock_series`/`#lock_series!`
|
|
3002
|
+
(for the lock of the series that is not obtained);
|
|
3003
|
+
- payload:
|
|
3004
|
+
- `:lock_key` - `string` - lock name;
|
|
3005
|
+
- `:rw_mode` - `symbol` - requested lock mode: `:write` or `:read` (see [Read/Write Locks](#readwrite-locks));
|
|
3006
|
+
- `:ttl` - `integer`/`milliseconds` - requested lock ttl;
|
|
3007
|
+
- `:acq_id` - `string` - lock acquirer identifier;
|
|
3008
|
+
- `:hst_id` - `string` - lock's host identifier;
|
|
3009
|
+
- `:ts` - `numeric`/`epoch` - the time when the lock acquirement is failed;
|
|
3010
|
+
- `:acq_time` - `float`/`milliseconds` - time spent on lock acquiring;
|
|
3011
|
+
- `:result` - `symbol` - the failure result: `:timeout_reached`, `:retry_limit_reached`, `:fail_fast_no_try`,
|
|
3012
|
+
`:fail_fast_after_try`, `:conflict_dead_lock`, `:conflict_lock_upgrade` (see [lock](#lock---obtain-a-lock));
|
|
3013
|
+
- `:instrument` - `nil`/`Any` - custom data passed to the `#lock`/`#lock!` method as `:instrument` attribute;
|
|
3014
|
+
|
|
2643
3015
|
##### `"redis_queued_locks.extendable_reentrant_lock_obtained"`
|
|
2644
3016
|
- <sup>\[[back to the list](#instrumentation-events)\]</sup>
|
|
2645
3017
|
- an event signalizes about the "extendable reentrant lock" obtaining is happened;
|
|
@@ -2742,6 +3114,7 @@ Detalized event semantics and payload structure:
|
|
|
2742
3114
|
- `:rel_time` - `float`/`milliseconds` - time spent on lock releasing;
|
|
2743
3115
|
- `:lock_key` - `string` - released lock (lock name);
|
|
2744
3116
|
- `:lock_key_queue` - `string` - released lock queue (the queue of write lock requests);
|
|
3117
|
+
- `:instrument` - `nil`/`Any` - custom data passed to the `#unlock` method as `:instrument` attribute;
|
|
2745
3118
|
|
|
2746
3119
|
##### `"redis_queued_locks.explicit_read_lock_release"`
|
|
2747
3120
|
- <sup>\[[back to the list](#instrumentation-events)\]</sup>
|
|
@@ -2764,6 +3137,7 @@ Detalized event semantics and payload structure:
|
|
|
2764
3137
|
- `:at` - `float`/`epoch` - the time when the operation has ended;
|
|
2765
3138
|
- `:rel_key_cnt` - `integer` - released redis keys count (write locks, readers registries, read lock data,
|
|
2766
3139
|
write and read lock request queues);
|
|
3140
|
+
- `:instrument` - `nil`/`Any` - custom data passed to the `#clear_locks` method as `:instrument` attribute;
|
|
2767
3141
|
|
|
2768
3142
|
##### `"redis_queued_locks.release_locks_of"`
|
|
2769
3143
|
- <sup>\[[back to the list](#instrumentation-events)\]</sup>
|
|
@@ -2776,6 +3150,42 @@ Detalized event semantics and payload structure:
|
|
|
2776
3150
|
- `:hst_id` - `string` - refused host identifier;
|
|
2777
3151
|
- `:rel_key_cnt` - `integer` - released locks count (write locks and read locks);
|
|
2778
3152
|
- `:tch_queue_cnt` - `:integer` - the number of queues (write and read lock request queues) from which the concrete host/acquirer was removed;
|
|
3153
|
+
- `:instrument` - `nil`/`Any` - custom data passed to the `#clear_locks_of`/`#clear_current_locks` method as `:instrument` attribute;
|
|
3154
|
+
|
|
3155
|
+
##### `"redis_queued_locks.extend_lock_ttl"`
|
|
3156
|
+
- <sup>\[[back to the list](#instrumentation-events)\]</sup>
|
|
3157
|
+
- an event signalizes about the lock TTL extension (successful or not);
|
|
3158
|
+
- raised from `#extend_lock_ttl`;
|
|
3159
|
+
- payload:
|
|
3160
|
+
- `:lock_key` - `string` - lock key (`rql:lock:<lock_name>`);
|
|
3161
|
+
- `:rw_mode` - `symbol` - `:write` (the write lock) or `:read` (the read lock of the acquirer or all read locks);
|
|
3162
|
+
- `:all_read_locks` - `boolean` - all read locks of the lock are extended (`:read` mode);
|
|
3163
|
+
- `:ext_ttl` - `integer`/`milliseconds` - the extension (`milliseconds` argument);
|
|
3164
|
+
- `:extended_locks_count` - `integer` - the number of extended locks (`0` when nothing is extended);
|
|
3165
|
+
- `:ext_time` - `float`/`milliseconds` - time spent on the extension;
|
|
3166
|
+
- `:at` - `float`/`epoch` - the time when the operation has ended;
|
|
3167
|
+
- `:instrument` - `nil`/`Any` - custom data passed to the `#extend_lock_ttl` method as `:instrument` attribute;
|
|
3168
|
+
|
|
3169
|
+
##### `"redis_queued_locks.clear_dead_requests"`
|
|
3170
|
+
- <sup>\[[back to the list](#instrumentation-events)\]</sup>
|
|
3171
|
+
- an event signalizes about the dropped dead lock requests;
|
|
3172
|
+
- raised from `#clear_dead_requests`;
|
|
3173
|
+
- payload:
|
|
3174
|
+
- `:dead_ttl` - `integer`/`milliseconds` - lock requests older than `dead_ttl` are dropped;
|
|
3175
|
+
- `:prc_queue_cnt` - `integer` - the number of processed lock request queues (write and read);
|
|
3176
|
+
- `:rel_time` - `float`/`milliseconds` - time spent on the operation;
|
|
3177
|
+
- `:at` - `float`/`epoch` - the time when the operation has ended;
|
|
3178
|
+
- `:instrument` - `nil`/`Any` - custom data passed to the `#clear_dead_requests` method as `:instrument` attribute;
|
|
3179
|
+
|
|
3180
|
+
##### `"redis_queued_locks.reindex_locks"`
|
|
3181
|
+
- <sup>\[[back to the list](#instrumentation-events)\]</sup>
|
|
3182
|
+
- an event signalizes about the lock keys added to the [Lock Index](#lock-index);
|
|
3183
|
+
- raised from `#reindex_locks`;
|
|
3184
|
+
- payload:
|
|
3185
|
+
- `:idx_key_cnt` - `integer` - the number of indexed keys;
|
|
3186
|
+
- `:idx_time` - `float`/`milliseconds` - time spent on the operation;
|
|
3187
|
+
- `:at` - `float`/`epoch` - the time when the operation has ended;
|
|
3188
|
+
- `:instrument` - `nil`/`Any` - custom data passed to the `#reindex_locks` method as `:instrument` attribute;
|
|
2779
3189
|
|
|
2780
3190
|
---
|
|
2781
3191
|
|