redis_queued_locks 1.4.0 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 448f8b89e4a34bb2543b55d46877d80f76cf8e4b5d07735486f53eb1edb37432
4
- data.tar.gz: 4fbefadd9a162ab298b100bca38387045c65c89918ffd70c3f4aa418aa72d22a
3
+ metadata.gz: 5be49c89b572553d2de7af37b8bbc38439bcefc5b2b33cd4d8e542fb3c9dd3e3
4
+ data.tar.gz: 8a3a5509e82bd45ff9576677e2c2017c670fdd6a825da8ad0281f0fc1b6e7e69
5
5
  SHA512:
6
- metadata.gz: 738c503d6189ca4673d1bc5af829066378654a070b78691ae7d1001e832795dbb91f1cde3e89252ff6b9831a659daf517eea1ea2ab721101c9140afe16b44974
7
- data.tar.gz: cb2b4893238e55688b01c56f5f0004217d206122d5ae5ef3a0ea8f2d206323442e4c10bf78f3d504f947795864decc712f5385448b205151ccfe7ca39ab9ea0a
6
+ metadata.gz: 8f151272fadc4be25fd1344f9b29a5b263bc8ab63d17e8d09c55178232e4e57acac456c5987c0040172643e0a479976f9c12d0ff81549ba223ed742adb0dfcd1
7
+ data.tar.gz: c862d4040d1dbe421a7a8e2a10c8e24c8e22cd74c65d64746cb74ad96c2d31b3c1ec7a55f22cc0149ea0a65014f4d891d8a13f4336b07d6f56130673203ff384
data/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [1.5.0] - 2024-05-23
4
+ ### Added
5
+ - New Feature: **Log sampling** - configurable log sampling based on `weight` algorithm (where the weight is a percentage);
6
+
3
7
  ## [1.4.0] - 2024-05-13
4
8
  ### Added
5
9
  - `#lock`/`#lock!`: reduced memory allocaiton during `:meta` attribute type checking;
data/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  Each lock request is put into the request queue (each lock is hosted by it's own queue separately from other queues) and processed in order of their priority (FIFO). Each lock request lives some period of time (RTTL) (with requeue capabilities) which guarantees the request queue will never be stacked.
6
6
 
7
- Provides flexible invocation flow, parametrized limits (lock request ttl, lock ttls, queue ttls, lock attempts limit, fast failing, etc), logging and instrumentation.
7
+ Provides flexible invocation flow, parametrized limits (lock request ttl, lock ttl, queue ttl, lock attempts limit, fast failing, etc), logging and instrumentation.
8
8
 
9
9
  ---
10
10
 
@@ -182,10 +182,10 @@ clinet = RedisQueuedLocks::Client.new(redis_client) do |config|
182
182
 
183
183
  # (default: RedisQueuedLocks::Instrument::VoidNotifier)
184
184
  # - instrumentation layer;
185
- # - you can provde your own instrumenter with `#notify(event, payload = {})`
185
+ # - you can provide your own instrumenter that should realize `#notify(event, payload = {})` interface:
186
186
  # - event: <string> requried;
187
187
  # - payload: <hash> requried;
188
- # - disabled by default via VoidNotifier;
188
+ # - disabled by default via `VoidNotifier`;
189
189
  config.instrumenter = RedisQueuedLocks::Instrument::ActiveSupport
190
190
 
191
191
  # (default: -> { RedisQueuedLocks::Resource.calc_uniq_identity })
@@ -232,6 +232,27 @@ clinet = RedisQueuedLocks::Client.new(redis_client) do |config|
232
232
  # - "[redis_queued_locks.try_lock.exit__lock_still_obtained]" (logs "lock_key", "queue_ttl", "acq_id", "first_acq_id_in_queue", "locked_by_acq_id", "<current_lock_data>");
233
233
  # - "[redis_queued_locks.try_lock.obtain__free_to_acquire]" (logs "lock_key", "queue_ttl", "acq_id");
234
234
  config.log_lock_try = false
235
+
236
+ # (default: false)
237
+ # - enables <log sampling>: only the configured percent of RQL cases will be logged;
238
+ # - disabled by default;
239
+ # - works in tandem with <config.log_sampling_percent> and <log.sampler> configs;
240
+ config.log_sampling_enabled = false
241
+
242
+ # (default: 15)
243
+ # - the percent of cases that should be logged;
244
+ # - take an effect when <config.log_sampling_enalbed> is true;
245
+ # - works in tandem with <config.log_sampling_enabled> and <config.log_sampler> configs;
246
+ config.log_sampling_percent = 15
247
+
248
+ # (default: RedisQueuedLocks::Logging::Sampler)
249
+ # - percent-based log sampler that decides should be RQL case logged or not;
250
+ # - works in tandem with <config.log_sampling_enabled> and <config.log_sampling_percent> configs;
251
+ # - based on the ultra simple percent-based (weight-based) algorithm that uses SecureRandom.rand
252
+ # method so the algorithm error is ~(0%..13%);
253
+ # - you can provide your own log sampler with bettter algorithm that should realize
254
+ # `sampling_happened?(percent) => boolean` interface (see `RedisQueuedLocks::Logging::Sampler` for example);
255
+ config.log_sampler = RedisQueuedLocks::Logging::Sampler
235
256
  end
236
257
  ```
237
258
 
@@ -290,6 +311,9 @@ def lock(
290
311
  instrument: nil,
291
312
  logger: config[:logger],
292
313
  log_lock_try: config[:log_lock_try],
314
+ log_sampling_enabled: config[:log_sampling_enabled],
315
+ log_sampling_percent: config[:log_sampling_percent],
316
+ log_sampler: config[:log_sampler],
293
317
  &block
294
318
  )
295
319
  ```
@@ -363,6 +387,24 @@ def lock(
363
387
  - should be logged the each try of lock acquiring (a lot of logs can be generated depending on your retry configurations);
364
388
  - pre-configured in `config[:log_lock_try]`;
365
389
  - `false` by default;
390
+ - `log_sampling_enabled` - (optional) `[Boolean]`
391
+ - enables **log sampling**: only the configured percent of RQL cases will be logged;
392
+ - disabled by default;
393
+ - works in tandem with `log_sampling_percent` and `log_sampler` options;
394
+ - pre-configured in `config[:log_sampling_enabled]`;
395
+ - `log_sampling_percent` - (optional) `[Integer]`
396
+ - the percent of cases that should be logged;
397
+ - take an effect when `log_sampling_enalbed` is true;
398
+ - works in tandem with `log_sampling_enabled` and `log_sampler` options;
399
+ - pre-configured in `config[:log_sampling_percent]`;
400
+ - `log_sampler` - (optional) `[#sampling_happened?,Module<RedisQueuedLocks::Logging::Sampler>]`
401
+ - percent-based log sampler that decides should be RQL case logged or not;
402
+ - works in tandem with `log_sampling_enabled` and `log_sampling_percent` options;
403
+ - based on the ultra simple percent-based (weight-based) algorithm that uses SecureRandom.rand
404
+ method so the algorithm error is ~(0%..13%);
405
+ - you can provide your own log sampler with bettter algorithm that should realize
406
+ `sampling_happened?(percent) => boolean` interface (see `RedisQueuedLocks::Logging::Sampler` for example);
407
+ - pre-configured in `config[:log_sampler]`;
366
408
  - `block` - (optional) `[Block]`
367
409
  - A block of code that should be executed after the successfully acquired lock.
368
410
  - If block is **passed** the obtained lock will be released after the block execution or it's ttl (what will happen first);
@@ -572,6 +614,9 @@ def lock!(
572
614
  log_lock_try: config[:log_lock_try],
573
615
  instrument: nil,
574
616
  conflict_strategy: config[:default_conflict_strategy],
617
+ log_sampling_enabled: config[:log_sampling_enabled],
618
+ log_sampling_percent: config[:log_sampling_percent],
619
+ log_sampler: config[:log_sampler],
575
620
  &block
576
621
  )
577
622
  ```
@@ -743,6 +788,15 @@ rql.queued?("your_lock_name") # => true/false
743
788
  - `:instrument` - (optional) `[NilClass,Any]`;
744
789
  - custom instrumentation data wich will be passed to the instrumenter's payload with :instrument key;
745
790
  - `nil` by default (no additional data);
791
+ - `:log_sampling_enabled` - (optional) `[Boolean]`
792
+ - enables **log sampling**;
793
+ - pre-configured in `config[:log_sampling_enabled]`;
794
+ - `:log_sampling_percent` - (optional) `[Integer]`
795
+ - **log sampling**:the percent of cases that should be logged;
796
+ - pre-configured in `config[:log_sampling_percent]`;
797
+ - `:log_sampler` - (optional) `[#sampling_happened?,Module<RedisQueuedLocks::Logging::Sampler>]`
798
+ - **log sampling**: percent-based log sampler that decides should be RQL case logged or not;
799
+ - pre-configured in `config[:log_sampler]`;
746
800
  - if you try to unlock non-existent lock you will receive `ok: true` result with operation timings
747
801
  and `:nothing_to_release` result factor inside;
748
802
 
@@ -793,6 +847,15 @@ rql.unlock("your_lock_name")
793
847
  - pre-configured value in `config[:isntrumenter]`;
794
848
  - `:instrument` - (optional) `[NilClass,Any]`
795
849
  - custom instrumentation data wich will be passed to the instrumenter's payload with `:instrument` key;
850
+ - `:log_sampling_enabled` - (optional) `[Boolean]`
851
+ - enables **log sampling**;
852
+ - pre-configured in `config[:log_sampling_enabled]`;
853
+ - `:log_sampling_percent` - (optional) `[Integer]`
854
+ - **log sampling**:the percent of cases that should be logged;
855
+ - pre-configured in `config[:log_sampling_percent]`;
856
+ - `:log_sampler` - (optional) `[#sampling_happened?,Module<RedisQueuedLocks::Logging::Sampler>]`
857
+ - **log sampling**: percent-based log sampler that decides should be RQL case logged or not;
858
+ - pre-configured in `config[:log_sampler]`;
796
859
  - returns:
797
860
  - `[Hash<Symbol,Numeric>]` - Format: `{ ok: true, result: Hash<Symbol,Numeric> }`;
798
861
  - result data:
@@ -828,6 +891,15 @@ rql.clear_locks
828
891
  - `:logger` - (optional) `[::Logger,#debug]`
829
892
  - custom logger object;
830
893
  - pre-configured in `config[:logger]`;
894
+ - `:log_sampling_enabled` - (optional) `[Boolean]`
895
+ - enables **log sampling**;
896
+ - pre-configured in `config[:log_sampling_enabled]`;
897
+ - `:log_sampling_percent` - (optional) `[Integer]`
898
+ - **log sampling**:the percent of cases that should be logged;
899
+ - pre-configured in `config[:log_sampling_percent]`;
900
+ - `:log_sampler` - (optional) `[#sampling_happened?,Module<RedisQueuedLocks::Logging::Sampler>]`
901
+ - **log sampling**: percent-based log sampler that decides should be RQL case logged or not;
902
+ - pre-configured in `config[:log_sampler]`;
831
903
  - returns `{ ok: true, result: :ttl_extended }` when ttl is extended;
832
904
  - returns `{ ok: false, result: :async_expire_or_no_lock }` when a lock not found or a lock is already expired during
833
905
  some steps of invocation (see **Important** section below);
@@ -1056,6 +1128,15 @@ Accepts:
1056
1128
  - `:instrument` - (optional) `[NilClass,Any]`
1057
1129
  - custom instrumentation data wich will be passed to the instrumenter's payload with :instrument key;
1058
1130
  - `nil` by default (no additional data);
1131
+ - `:log_sampling_enabled` - (optional) `[Boolean]`
1132
+ - enables **log sampling**;
1133
+ - pre-configured in `config[:log_sampling_enabled]`;
1134
+ - `:log_sampling_percent` - (optional) `[Integer]`
1135
+ - **log sampling**:the percent of cases that should be logged;
1136
+ - pre-configured in `config[:log_sampling_percent]`;
1137
+ - `:log_sampler` - (optional) `[#sampling_happened?,Module<RedisQueuedLocks::Logging::Sampler>]`
1138
+ - **log sampling**: percent-based log sampler that decides should be RQL case logged or not;
1139
+ - pre-configured in `config[:log_sampler]`;
1059
1140
 
1060
1141
  Returns: `{ ok: true, processed_queues: Set<String> }` returns the list of processed lock queues;
1061
1142
 
@@ -1087,7 +1168,7 @@ rql.clear_dead_requests(dead_ttl: 60 * 60 * 1000) # 1 hour in milliseconds
1087
1168
  - by default behavior (`:wait_for_lock`) your lock obtaining process will work in a classic way (limits, retries, etc);
1088
1169
  - `:work_through`, `:extendable_work_through` works with limits too (timeouts, delays, etc), but the decision of
1089
1170
  "is your lock are obtained or not" is made as you work with **reentrant locks** (your process continues to use the lock without/with
1090
- lock's TTL accordingly);
1171
+ lock's TTL extension accordingly);
1091
1172
  - for current implementation details check:
1092
1173
  - [Configuration](#configuration) documentation: see `config.default_conflict_strategy` config docs;
1093
1174
  - [#lock](#lock---obtain-a-lock) method documentation: see `conflict_strategy` attribute docs and the method result data;
@@ -1254,6 +1335,7 @@ Detalized event semantics and payload structure:
1254
1335
  <sup>\[[back to top](#table-of-contents)\]</sup>
1255
1336
 
1256
1337
  - **Major**:
1338
+ - precent-based instrumentation sampling (instrument the concrete % of cases via "sampling" strategy);
1257
1339
  - support for Random Access strategy (non-queued behavior);
1258
1340
  - lock request prioritization;
1259
1341
  - **strict redlock algorithm support** (support for many `RedisClient` instances);
@@ -1264,8 +1346,6 @@ Detalized event semantics and payload structure:
1264
1346
  - support for `Dragonfly` database backend (https://github.com/dragonflydb/dragonfly) (https://www.dragonflydb.io/);
1265
1347
  - **Minor**:
1266
1348
  - Semantic error objects for unexpected Redis errors;
1267
- - change all `::Process.clock_gettime(::Process::CLOCK_MONOTONIC)` milliseconds-related invocations to
1268
- `::Process.clock_gettime(::Process::CLOCK_MONOTONIC, :millisecond)` in order to reduce time-related math operations count;
1269
1349
  - **Experimental feature**: (non-`timed` locks): per-ruby-block-holding-the-lock sidecar `Ractor` and `in progress queue` in RedisDB that will extend
1270
1350
  the acquired lock for long-running blocks of code (that invoked "under" the lock
1271
1351
  whose ttl may expire before the block execution completes). It makes sense for non-`timed` locks *only*;
@@ -28,11 +28,12 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
28
28
  # @param fail_fast [Boolean]
29
29
  # @param conflict_strategy [Symbol]
30
30
  # @param meta [NilClass,Hash<String|Symbol,Any>]
31
+ # @param log_sampled [Boolean]
31
32
  # @return [Hash<Symbol,Any>] Format: { ok: true/false, result: Symbol|Hash<Symbol,Any> }
32
33
  #
33
34
  # @api private
34
35
  # @since 1.0.0
35
- # @version 1.3.0
36
+ # @version 1.5.0
36
37
  # rubocop:disable Metrics/MethodLength
37
38
  def try_to_lock(
38
39
  redis,
@@ -46,14 +47,15 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
46
47
  queue_ttl,
47
48
  fail_fast,
48
49
  conflict_strategy,
49
- meta
50
+ meta,
51
+ log_sampled
50
52
  )
51
53
  # Step X: intermediate invocation results
52
54
  inter_result = nil
53
55
  timestamp = nil
54
56
  spc_processed_timestamp = nil
55
57
 
56
- if log_lock_try
58
+ if log_sampled && log_lock_try
57
59
  run_non_critical do
58
60
  logger.debug do
59
61
  "[redis_queued_locks.try_lock.start] " \
@@ -66,7 +68,7 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
66
68
 
67
69
  # Step X: start to work with lock acquiring
68
70
  result = redis.with do |rconn|
69
- if log_lock_try
71
+ if log_sampled && log_lock_try
70
72
  run_non_critical do
71
73
  logger.debug do
72
74
  "[redis_queued_locks.try_lock.rconn_fetched] " \
@@ -88,7 +90,7 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
88
90
 
89
91
  # SP-Conflict Step X1: calculate the current deadlock status
90
92
  if current_lock_obtainer != nil && acquier_id == current_lock_obtainer
91
- if log_lock_try
93
+ if log_sampled && log_lock_try
92
94
  run_non_critical do
93
95
  logger.debug do
94
96
  "[redis_queued_locks.try_lock.same_process_conflict_detected] " \
@@ -124,7 +126,7 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
124
126
  end
125
127
  # rubocop:enable Lint/DuplicateBranch
126
128
 
127
- if log_lock_try
129
+ if log_sampled && log_lock_try
128
130
  run_non_critical do
129
131
  logger.debug do
130
132
  "[redis_queued_locks.try_lock.same_process_conflict_analyzed] " \
@@ -172,7 +174,7 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
172
174
  )
173
175
  inter_result = :extendable_conflict_work_through
174
176
 
175
- if log_lock_try
177
+ if log_sampled && log_lock_try
176
178
  run_non_critical do
177
179
  logger.debug do
178
180
  "[redis_queued_locks.try_lock.reentrant_lock__extend_and_work_through] " \
@@ -205,7 +207,7 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
205
207
  'l_spc_ts', (spc_processed_timestamp = Time.now.to_f)
206
208
  )
207
209
 
208
- if log_lock_try
210
+ if log_sampled && log_lock_try
209
211
  run_non_critical do
210
212
  logger.debug do
211
213
  "[redis_queued_locks.try_lock.reentrant_lock__work_through] " \
@@ -222,7 +224,7 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
222
224
  inter_result = :conflict_dead_lock
223
225
  spc_processed_timestamp = Time.now.to_f
224
226
 
225
- if log_lock_try
227
+ if log_sampled && log_lock_try
226
228
  logger.debug do
227
229
  "[redis_queued_locks.try_lock.single_process_lock_conflict__dead_lock] " \
228
230
  "lock_key => '#{lock_key}' " \
@@ -242,7 +244,7 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
242
244
  # Step 1: add an acquier to the lock acquirement queue
243
245
  rconn.call('ZADD', lock_key_queue, 'NX', acquier_position, acquier_id)
244
246
 
245
- if log_lock_try
247
+ if log_sampled && log_lock_try
246
248
  run_non_critical do
247
249
  logger.debug do
248
250
  "[redis_queued_locks.try_lock.acq_added_to_queue] " \
@@ -261,7 +263,7 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
261
263
  RedisQueuedLocks::Resource.acquier_dead_score(queue_ttl)
262
264
  )
263
265
 
264
- if log_lock_try
266
+ if log_sampled && log_lock_try
265
267
  run_non_critical do
266
268
  logger.debug do
267
269
  "[redis_queued_locks.try_lock.remove_expired_acqs] " \
@@ -275,7 +277,7 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
275
277
  # Step 3: get the actual acquier waiting in the queue
276
278
  waiting_acquier = Array(rconn.call('ZRANGE', lock_key_queue, '0', '0')).first
277
279
 
278
- if log_lock_try
280
+ if log_sampled && log_lock_try
279
281
  run_non_critical do
280
282
  logger.debug do
281
283
  "[redis_queued_locks.try_lock.get_first_from_queue] " \
@@ -290,7 +292,7 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
290
292
  # Step PRE-4.x: check if the request time limit is reached
291
293
  # (when the current try self-removes itself from queue (queue ttl has come))
292
294
  if waiting_acquier == nil
293
- if log_lock_try
295
+ if log_sampled && log_lock_try
294
296
  run_non_critical do
295
297
  logger.debug do
296
298
  "[redis_queued_locks.try_lock.exit__queue_ttl_reached] " \
@@ -306,7 +308,7 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
306
308
  elsif waiting_acquier != acquier_id
307
309
  # Step ROLLBACK 1.1: our time hasn't come yet. retry!
308
310
 
309
- if log_lock_try
311
+ if log_sampled && log_lock_try
310
312
  run_non_critical do
311
313
  logger.debug do
312
314
  "[redis_queued_locks.try_lock.exit__no_first] " \
@@ -329,7 +331,7 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
329
331
  if locked_by_acquier
330
332
  # Step ROLLBACK 2: required lock is stil acquired. retry!
331
333
 
332
- if log_lock_try
334
+ if log_sampled && log_lock_try
333
335
  run_non_critical do
334
336
  logger.debug do
335
337
  "[redis_queued_locks.try_lock.exit__lock_still_obtained] " \
@@ -363,7 +365,7 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
363
365
  # Step 6.3: set the lock expiration time in order to prevent "infinite locks"
364
366
  transact.call('PEXPIRE', lock_key, ttl) # NOTE: in milliseconds
365
367
 
366
- if log_lock_try
368
+ if log_sampled && log_lock_try
367
369
  run_non_critical do
368
370
  logger.debug do
369
371
  "[redis_queued_locks.try_lock.obtain__free_to_acquire] " \
@@ -482,12 +484,21 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
482
484
  # @param lock_key_queue [String]
483
485
  # @param queue_ttl [Integer]
484
486
  # @param acquier_id [String]
487
+ # @param log_sampled [Boolean]
485
488
  # @return [Hash<Symbol,Any>] Format: { ok: true/false, result: Any }
486
489
  #
487
490
  # @api private
488
491
  # @since 1.0.0
489
- # @version 1.3.0
490
- def dequeue_from_lock_queue(redis, logger, lock_key, lock_key_queue, queue_ttl, acquier_id)
492
+ # @version 1.5.0
493
+ def dequeue_from_lock_queue(
494
+ redis,
495
+ logger,
496
+ lock_key,
497
+ lock_key_queue,
498
+ queue_ttl,
499
+ acquier_id,
500
+ log_sampled
501
+ )
491
502
  result = redis.call('ZREM', lock_key_queue, acquier_id)
492
503
 
493
504
  run_non_critical do
@@ -497,7 +508,7 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
497
508
  "queue_ttl => '#{queue_ttl}' " \
498
509
  "acq_id => '#{acquier_id}'"
499
510
  end
500
- end
511
+ end if log_sampled
501
512
 
502
513
  RedisQueuedLocks::Data[ok: true, result: result]
503
514
  end
@@ -24,6 +24,7 @@ module RedisQueuedLocks::Acquier::AcquireLock::YieldExpire
24
24
  # @param ttl [Integer,NilClass] Lock's time to live (in ms). Nil means "without timeout".
25
25
  # @param queue_ttl [Integer] Lock request lifetime.
26
26
  # @param block [Block] Custom logic that should be invoked unter the obtained lock.
27
+ # @param log_sampled [Boolean] Should the logic be logged or not (is log sample happened?).
27
28
  # @param should_expire [Boolean] Should the lock be expired after the block invocation.
28
29
  # @param should_decrease [Boolean]
29
30
  # - Should decrease the lock TTL after the lock invocation;
@@ -32,6 +33,7 @@ module RedisQueuedLocks::Acquier::AcquireLock::YieldExpire
32
33
  #
33
34
  # @api private
34
35
  # @since 1.3.0
36
+ # @version 1.5.0
35
37
  # rubocop:disable Metrics/MethodLength
36
38
  def yield_expire(
37
39
  redis,
@@ -42,6 +44,7 @@ module RedisQueuedLocks::Acquier::AcquireLock::YieldExpire
42
44
  ttl_shift,
43
45
  ttl,
44
46
  queue_ttl,
47
+ log_sampled,
45
48
  should_expire,
46
49
  should_decrease,
47
50
  &block
@@ -69,7 +72,7 @@ module RedisQueuedLocks::Acquier::AcquireLock::YieldExpire
69
72
  "queue_ttl => #{queue_ttl} " \
70
73
  "acq_id => '#{acquier_id}'"
71
74
  end
72
- end
75
+ end if log_sampled
73
76
  redis.call('EXPIRE', lock_key, '0')
74
77
  elsif should_decrease
75
78
  finish_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC, :millisecond)
@@ -84,7 +87,7 @@ module RedisQueuedLocks::Acquier::AcquireLock::YieldExpire
84
87
  "queue_ttl => #{queue_ttl} " \
85
88
  "acq_id => '#{acquier_id}' " \
86
89
  end
87
- end
90
+ end if log_sampled
88
91
  # NOTE:# NOTE: EVAL signature -> <lua script>, (number of keys), *(keys), *(arguments)
89
92
  redis.call('EVAL', DECREASE_LOCK_PTTL, 1, lock_key, decreased_ttl)
90
93
  # TODO: upload scripts to the redis
@@ -86,6 +86,20 @@ module RedisQueuedLocks::Acquier::AcquireLock
86
86
  # - `:extendable_work_through`;
87
87
  # - `:wait_for_lock`;
88
88
  # - `:dead_locking`;
89
+ # @option log_sampling_enabled [Boolean]
90
+ # - The percent of cases that should be logged;
91
+ # - Sampling algorithm is super simple and works via SecureRandom.rand method
92
+ # on the base of "weight" algorithm;
93
+ # - You can provide your own sampler via config[:log_sampler] config and :sampler option
94
+ # (see `RedisQueuedLocks::Logging::Sampler` for examples);
95
+ # - The spread of guaranteed percent is approximately +13% (rand method spread);
96
+ # - Take an effect when <log_sampling_enabled> parameter has <true> value
97
+ # (when log sampling is enabled);
98
+ # @option log_sampling_percent [Integer]
99
+ # - The percent of cases that should be logged;
100
+ # - Take an effect when <log_sampling_enabled> parameter has <true> value
101
+ # (when log sampling is enabled);
102
+ # @option log_sampler [#sampling_happened?,Module<RedisQueuedLocks::Logging::Sampler>]
89
103
  # @param [Block]
90
104
  # A block of code that should be executed after the successfully acquired lock.
91
105
  # @return [RedisQueuedLocks::Data,Hash<Symbol,Any>,yield]
@@ -118,6 +132,9 @@ module RedisQueuedLocks::Acquier::AcquireLock
118
132
  logger:,
119
133
  log_lock_try:,
120
134
  conflict_strategy:,
135
+ log_sampling_enabled:,
136
+ log_sampling_percent:,
137
+ log_sampler:,
121
138
  &block
122
139
  )
123
140
  # Step 0: Prevent argument type incompatabilities
@@ -165,6 +182,12 @@ module RedisQueuedLocks::Acquier::AcquireLock
165
182
  lock_key_queue = RedisQueuedLocks::Resource.prepare_lock_queue(lock_name)
166
183
  acquier_position = RedisQueuedLocks::Resource.calc_initial_acquier_position
167
184
 
185
+ log_sampled = RedisQueuedLocks::Logging.should_log?(
186
+ log_sampling_enabled,
187
+ log_sampling_percent,
188
+ log_sampler
189
+ )
190
+
168
191
  # Step X: intermediate result observer
169
192
  acq_process = {
170
193
  lock_info: {},
@@ -183,7 +206,8 @@ module RedisQueuedLocks::Acquier::AcquireLock
183
206
  lock_key,
184
207
  lock_key_queue,
185
208
  queue_ttl,
186
- acquier_id
209
+ acquier_id,
210
+ log_sampled
187
211
  )
188
212
  end
189
213
 
@@ -194,7 +218,7 @@ module RedisQueuedLocks::Acquier::AcquireLock
194
218
  "queue_ttl => #{queue_ttl} " \
195
219
  "acq_id => '#{acquier_id}'"
196
220
  end
197
- end
221
+ end if log_sampled
198
222
 
199
223
  # Step 2: try to lock with timeout
200
224
  with_acq_timeout(timeout, lock_key, raise_errors, on_timeout: acq_dequeue) do
@@ -209,7 +233,7 @@ module RedisQueuedLocks::Acquier::AcquireLock
209
233
  "queue_ttl => #{queue_ttl} " \
210
234
  "acq_id => '{#{acquier_id}'"
211
235
  end
212
- end
236
+ end if log_sampled
213
237
 
214
238
  # Step 2.X: check the actual score: is it in queue ttl limit or not?
215
239
  if RedisQueuedLocks::Resource.dead_score_reached?(acquier_position, queue_ttl)
@@ -223,7 +247,7 @@ module RedisQueuedLocks::Acquier::AcquireLock
223
247
  "queue_ttl => #{queue_ttl} " \
224
248
  "acq_id => '#{acquier_id}'"
225
249
  end
226
- end
250
+ end if log_sampled
227
251
  end
228
252
 
229
253
  try_to_lock(
@@ -238,7 +262,8 @@ module RedisQueuedLocks::Acquier::AcquireLock
238
262
  queue_ttl,
239
263
  fail_fast,
240
264
  conflict_strategy,
241
- meta
265
+ meta,
266
+ log_sampled
242
267
  ) => { ok:, result: }
243
268
 
244
269
  acq_end_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC, :microsecond)
@@ -261,7 +286,7 @@ module RedisQueuedLocks::Acquier::AcquireLock
261
286
  "acq_id => '#{acquier_id}' " \
262
287
  "acq_time => #{acq_time} (ms)"
263
288
  end
264
- end
289
+ end if log_sampled
265
290
 
266
291
  run_non_critical do
267
292
  instrumenter.notify('redis_queued_locks.extendable_reentrant_lock_obtained', {
@@ -283,7 +308,7 @@ module RedisQueuedLocks::Acquier::AcquireLock
283
308
  "acq_id => '#{acquier_id}' " \
284
309
  "acq_time => #{acq_time} (ms)"
285
310
  end
286
- end
311
+ end if log_sampled
287
312
 
288
313
  run_non_critical do
289
314
  instrumenter.notify('redis_queued_locks.reentrant_lock_obtained', {
@@ -306,7 +331,7 @@ module RedisQueuedLocks::Acquier::AcquireLock
306
331
  "acq_id => '#{acquier_id}' " \
307
332
  "acq_time => #{acq_time} (ms)"
308
333
  end
309
- end
334
+ end if log_sampled
310
335
 
311
336
  # Step X (instrumentation): lock obtained
312
337
  run_non_critical do
@@ -426,6 +451,7 @@ module RedisQueuedLocks::Acquier::AcquireLock
426
451
  ttl_shift,
427
452
  ttl,
428
453
  queue_ttl,
454
+ log_sampled,
429
455
  should_expire, # NOTE: should expire the lock after the block execution
430
456
  should_decrease, # NOTE: should decrease the lock ttl in reentrant locks?
431
457
  &block
@@ -10,11 +10,24 @@ module RedisQueuedLocks::Acquier::ClearDeadRequests
10
10
  # @param logger [::Logger,#debug]
11
11
  # @param instrumenter [#notify]
12
12
  # @param instrument [NilClass,Any]
13
+ # @param log_sampling_enabled [Boolean]
14
+ # @param log_sampling_percent [Integer]
15
+ # @param log_sampler [#sampling_happened?,Module<RedisQueuedLocks::Logging::Sampler>]
13
16
  # @return [Hash<Symbol,Boolean|Hash<Symbol,Set<String>>>]
14
17
  #
15
18
  # @api private
16
19
  # @since 1.0.0
17
- def clear_dead_requests(redis_client, scan_size, dead_ttl, logger, instrumenter, instrument)
20
+ def clear_dead_requests(
21
+ redis_client,
22
+ scan_size,
23
+ dead_ttl,
24
+ logger,
25
+ instrumenter,
26
+ instrument,
27
+ log_sampling_enabled,
28
+ log_sampling_percent,
29
+ log_sampler
30
+ )
18
31
  dead_score = RedisQueuedLocks::Resource.acquier_dead_score(dead_ttl / 1_000.0)
19
32
 
20
33
  result = Set.new.tap do |processed_queues|
@@ -17,11 +17,23 @@ module RedisQueuedLocks::Acquier::ExtendLockTTL
17
17
  # @param lock_name [String]
18
18
  # @param milliseconds [Integer]
19
19
  # @param logger [::Logger,#debug]
20
+ # @param log_sampling_enabled [Boolean]
21
+ # @param log_sampling_percent [Integer]
22
+ # @param log_sampler [#sampling_happened?,Module<RedisQueuedLocks::Logging::Sampler>]
20
23
  # @return [Hash<Symbol,Boolean|Symbol>]
21
24
  #
22
25
  # @api private
23
26
  # @since 1.0.0
24
- def extend_lock_ttl(redis_client, lock_name, milliseconds, logger)
27
+ # @version 1.5.0
28
+ def extend_lock_ttl(
29
+ redis_client,
30
+ lock_name,
31
+ milliseconds,
32
+ logger,
33
+ log_sampling_enabled,
34
+ log_sampling_percent,
35
+ log_sampler
36
+ )
25
37
  lock_key = RedisQueuedLocks::Resource.prepare_lock_key(lock_name)
26
38
 
27
39
  # NOTE: EVAL signature -> <lua script>, (number of keys), *(keys), *(arguments)
@@ -20,16 +20,33 @@ module RedisQueuedLocks::Acquier::ReleaseAllLocks
20
20
  # - See RedisQueuedLocks::Logging::VoidLogger for example;
21
21
  # @param isntrumenter [#notify]
22
22
  # See RedisQueuedLocks::Instrument::ActiveSupport for example.
23
- # @option instrument [NilClass,Any]
23
+ # @param instrument [NilClass,Any]
24
24
  # - Custom instrumentation data wich will be passed to the instrumenter's payload
25
25
  # with :instrument key;
26
+ # @param log_sampling_enabled [Boolean]
27
+ # - Enables <log sampling>: only the configured percent of cases will be logged;
28
+ # - Works in tandem with <log_samplng_percent> option;
29
+ # @param log_sampling_percent [Integer]
30
+ # - The percent of cases that should be logged;
31
+ # - Take an effect when <log_sampling_enabled> parameter has <true> value
32
+ # (when log sampling is enabled);
33
+ # @param log_sampler [#sampling_happened?,Module<RedisQueuedLocks::Logging::Sampler>]
26
34
  # @return [RedisQueuedLocks::Data,Hash<Symbol,Any>]
27
35
  # Format: { ok: true, result: Hash<Symbol,Numeric> }
28
36
  #
29
37
  # @api private
30
38
  # @since 1.0.0
31
- # @version 1.4.0
32
- def release_all_locks(redis, batch_size, logger, instrumenter, instrument)
39
+ # @version 1.5.0
40
+ def release_all_locks(
41
+ redis,
42
+ batch_size,
43
+ logger,
44
+ instrumenter,
45
+ instrument,
46
+ log_sampling_enabled,
47
+ log_sampling_percent,
48
+ log_sampler
49
+ )
33
50
  rel_start_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC, :microsecond)
34
51
  fully_release_all_locks(redis, batch_size) => { ok:, result: }
35
52
  time_at = Time.now.to_f
@@ -23,13 +23,35 @@ module RedisQueuedLocks::Acquier::ReleaseLock
23
23
  # @param logger [::Logger,#debug]
24
24
  # - Logger object used from `configuration` layer (see config[:logger]);
25
25
  # - See RedisQueuedLocks::Logging::VoidLogger for example;
26
+ # @param log_sampling_enabled [Boolean]
27
+ # - The percent of cases that should be logged;
28
+ # - Sampling algorithm is super simple and works via SecureRandom.rand method
29
+ # on the base of "weight" algorithm;
30
+ # - You can provide your own sampler via config[:log_sampler] config and :sampler option
31
+ # (see `RedisQueuedLocks::Logging::Sampler` for examples);
32
+ # - The spread of guaranteed percent is approximately +13% (rand method spread);
33
+ # - Take an effect when <log_sampling_enabled> parameter has <true> value
34
+ # (when log sampling is enabled);
35
+ # @param log_sampling_percent [Integer]
36
+ # - The percent of cases that should be logged;
37
+ # - Take an effect when <log_sampling_enabled> parameter has <true> value
38
+ # (when log sampling is enabled);
39
+ # @param log_sampler [#sampling_happened?,Module<RedisQueuedLocks::Logging::Sampler>]
26
40
  # @return [RedisQueuedLocks::Data,Hash<Symbol,Boolean<Hash<Symbol,Numeric|String|Symbol>>]
27
41
  # Format: { ok: true/false, result: Hash<Symbol,Numeric|String|Symbol> }
28
42
  #
29
43
  # @api private
30
44
  # @since 1.0.0
31
- # @version 1.4.0
32
- def release_lock(redis, lock_name, instrumenter, logger)
45
+ # @version 1.5.0
46
+ def release_lock(
47
+ redis,
48
+ lock_name,
49
+ instrumenter,
50
+ logger,
51
+ log_sampling_enabled,
52
+ log_sampling_percent,
53
+ log_sampler
54
+ )
33
55
  lock_key = RedisQueuedLocks::Resource.prepare_lock_key(lock_name)
34
56
  lock_key_queue = RedisQueuedLocks::Resource.prepare_lock_queue(lock_name)
35
57
 
@@ -23,6 +23,9 @@ class RedisQueuedLocks::Client
23
23
  setting :dead_request_ttl, (1 * 24 * 60 * 60 * 1000) # NOTE: 1 day in milliseconds
24
24
  setting :is_timed_by_default, false
25
25
  setting :default_conflict_strategy, :wait_for_lock
26
+ setting :log_sampling_enabled, false
27
+ setting :log_sampling_percent, 15
28
+ setting :log_sampler, RedisQueuedLocks::Logging::Sampler
26
29
 
27
30
  validate('retry_count') { |val| val == nil || (val.is_a?(::Integer) && val >= 0) }
28
31
  validate('retry_delay') { |val| val.is_a?(::Integer) && val >= 0 }
@@ -37,6 +40,9 @@ class RedisQueuedLocks::Client
37
40
  validate('log_lock_try', :boolean)
38
41
  validate('dead_request_ttl') { |val| val.is_a?(::Integer) && val > 0 }
39
42
  validate('is_timed_by_default', :boolean)
43
+ validate('log_sampler') { |val| RedisQueuedLocks::Logging.valid_sampler?(val) }
44
+ validate('log_sampling_enabled', :boolean)
45
+ validate('log_sampling_percent') { |val| val.is_a?(::Integer) && val >= 0 && val <= 100 }
40
46
  validate('default_conflict_strategy') do |val|
41
47
  # rubocop:disable Layout/MultilineOperationIndentation
42
48
  val == :work_through ||
@@ -130,6 +136,23 @@ class RedisQueuedLocks::Client
130
136
  # @option instrument [NilClass,Any]
131
137
  # - Custom instrumentation data wich will be passed to the instrumenter's payload
132
138
  # with :instrument key;
139
+ # @option log_sampling_enabled [Boolean]
140
+ # - enables <log sampling>: only the configured percent of RQL cases will be logged;
141
+ # - disabled by default;
142
+ # - works in tandem with <config.log_sampling_percent and <log.sampler>;
143
+ # @option log_sampling_percent [Integer]
144
+ # - the percent of cases that should be logged;
145
+ # - take an effect when <config.log_sampling_enalbed> is true;
146
+ # - works in tandem with <config.log_sampling_enabled> and <config.log_sampler> configs;
147
+ # @option log_sampler [#sampling_happened?,Module<RedisQueuedLocks::Logging::Sampler>]
148
+ # - percent-based log sampler that decides should be RQL case logged or not;
149
+ # - works in tandem with <config.log_sampling_enabled> and
150
+ # <config.log_sampling_percent> configs;
151
+ # - based on the ultra simple percent-based (weight-based) algorithm that uses SecureRandom.rand
152
+ # method so the algorithm error is ~(0%..13%);
153
+ # - you can provide your own log sampler with bettter algorithm that should realize
154
+ # `sampling_happened?(percent) => boolean` interface
155
+ # (see `RedisQueuedLocks::Logging::Sampler` for example);
133
156
  # @param block [Block]
134
157
  # A block of code that should be executed after the successfully acquired lock.
135
158
  # @return [RedisQueuedLocks::Data,Hash<Symbol,Any>,yield]
@@ -138,7 +161,8 @@ class RedisQueuedLocks::Client
138
161
  #
139
162
  # @api public
140
163
  # @since 1.0.0
141
- # @version 1.3.0
164
+ # @version 1.5.0
165
+ # rubocop:disable Metrics/MethodLength
142
166
  def lock(
143
167
  lock_name,
144
168
  ttl: config[:default_lock_ttl],
@@ -156,6 +180,9 @@ class RedisQueuedLocks::Client
156
180
  logger: config[:logger],
157
181
  log_lock_try: config[:log_lock_try],
158
182
  instrument: nil,
183
+ log_sampling_enabled: config[:log_sampling_enabled],
184
+ log_sampling_percent: config[:log_sampling_percent],
185
+ log_sampler: config[:log_sampler],
159
186
  &block
160
187
  )
161
188
  RedisQueuedLocks::Acquier::AcquireLock.acquire_lock(
@@ -178,18 +205,22 @@ class RedisQueuedLocks::Client
178
205
  fail_fast:,
179
206
  conflict_strategy:,
180
207
  meta:,
181
- logger: config[:logger],
182
- log_lock_try: config[:log_lock_try],
208
+ logger:,
209
+ log_lock_try:,
183
210
  instrument:,
211
+ log_sampling_enabled:,
212
+ log_sampling_percent:,
213
+ log_sampler:,
184
214
  &block
185
215
  )
186
216
  end
217
+ # rubocop:enable Metrics/MethodLength
187
218
 
188
219
  # @note See #lock method signature.
189
220
  #
190
221
  # @api public
191
222
  # @since 1.0.0
192
- # @version 1.3.0
223
+ # @version 1.5.0
193
224
  def lock!(
194
225
  lock_name,
195
226
  ttl: config[:default_lock_ttl],
@@ -206,6 +237,9 @@ class RedisQueuedLocks::Client
206
237
  logger: config[:logger],
207
238
  log_lock_try: config[:log_lock_try],
208
239
  instrument: nil,
240
+ log_sampling_enabled: config[:log_sampling_enabled],
241
+ log_sampling_percent: config[:log_sampling_percent],
242
+ log_sampler: config[:log_sampler],
209
243
  &block
210
244
  )
211
245
  lock(
@@ -225,6 +259,9 @@ class RedisQueuedLocks::Client
225
259
  meta:,
226
260
  instrument:,
227
261
  conflict_strategy:,
262
+ log_sampling_enabled:,
263
+ log_sampling_percent:,
264
+ log_sampler:,
228
265
  &block
229
266
  )
230
267
  end
@@ -233,6 +270,9 @@ class RedisQueuedLocks::Client
233
270
  # @option logger [::Logger,#debug]
234
271
  # @option instrumenter [#notify]
235
272
  # @option instrument [NilClass,Any]
273
+ # @option log_sampling_enabled [Boolean]
274
+ # @option log_sampling_percent [Integer]
275
+ # @option log_sampler [#sampling_happened?,Module<RedisQueuedLocks::Logging::Sampler>]
236
276
  # @return [RedisQueuedLocks::Data, Hash<Symbol,Any>]
237
277
  # Format: {
238
278
  # ok: true/false,
@@ -247,17 +287,24 @@ class RedisQueuedLocks::Client
247
287
  #
248
288
  # @api public
249
289
  # @since 1.0.0
290
+ # @version 1.5.0
250
291
  def unlock(
251
292
  lock_name,
252
293
  logger: config[:logger],
253
294
  instrumenter: config[:instrumenter],
254
- instrument: nil
295
+ instrument: nil,
296
+ log_sampling_enabled: config[:log_sampling_enabled],
297
+ log_sampling_percent: config[:log_sampling_percent],
298
+ log_sampler: config[:log_sampler]
255
299
  )
256
300
  RedisQueuedLocks::Acquier::ReleaseLock.release_lock(
257
301
  redis_client,
258
302
  lock_name,
259
303
  config[:instrumenter],
260
- config[:logger]
304
+ config[:logger],
305
+ log_sampling_enabled,
306
+ log_sampling_percent,
307
+ log_sampler
261
308
  )
262
309
  end
263
310
 
@@ -310,18 +357,44 @@ class RedisQueuedLocks::Client
310
357
  #
311
358
  # @param lock_name [String]
312
359
  # @param milliseconds [Integer] How many milliseconds should be added.
360
+ # @option logger [::Logger,#debug]
361
+ # @option log_sampling_enabled [Boolean]
362
+ # - The percent of cases that should be logged;
363
+ # - Sampling algorithm is super simple and works via SecureRandom.rand method
364
+ # on the base of "weight" algorithm;
365
+ # - You can provide your own sampler via config[:log_sampler] config and :sampler option
366
+ # (see `RedisQueuedLocks::Logging::Sampler` for examples);
367
+ # - The spread of guaranteed percent is approximately +13% (rand method spread);
368
+ # - Take an effect when <log_sampling_enabled> parameter has <true> value
369
+ # (when log sampling is enabled);
370
+ # @option log_sampling_percent [Integer]
371
+ # - The percent of cases that should be logged;
372
+ # - Take an effect when <log_sampling_enabled> parameter has <true> value
373
+ # (when log sampling is enabled);
374
+ # @option log_sampler [#sampling_happened?,Module<RedisQueuedLocks::Logging::Sampler>]
313
375
  # @return [Hash<Symbol,Boolean|Symbol>]
314
376
  # - { ok: true, result: :ttl_extended }
315
377
  # - { ok: false, result: :async_expire_or_no_lock }
316
378
  #
317
379
  # @api public
318
380
  # @since 1.0.0
319
- def extend_lock_ttl(lock_name, milliseconds, logger: config[:logger])
381
+ # @version 1.5.0
382
+ def extend_lock_ttl(
383
+ lock_name,
384
+ milliseconds,
385
+ logger: config[:logger],
386
+ log_sampling_enabled: config[:log_sampling_enabled],
387
+ log_sampling_percent: config[:log_sampling_percent],
388
+ log_sampler: config[:log_sampler]
389
+ )
320
390
  RedisQueuedLocks::Acquier::ExtendLockTTL.extend_lock_ttl(
321
391
  redis_client,
322
392
  lock_name,
323
393
  milliseconds,
324
- logger
394
+ logger,
395
+ log_sampling_enabled,
396
+ log_sampling_percent,
397
+ log_sampler
325
398
  )
326
399
  end
327
400
 
@@ -334,23 +407,33 @@ class RedisQueuedLocks::Client
334
407
  # @option logger [::Logger,#debug]
335
408
  # @option instrumenter [#notify]
336
409
  # @option instrument [NilClass,Any]
410
+ # @option log_sampling_enabled [Boolean]
411
+ # @option log_sampling_percent [Integer]
412
+ # @option log_sampler [#sampling_happened?,Module<RedisQueuedLocks::Logging::Sampler>]
337
413
  # @return [RedisQueuedLocks::Data,Hash<Symbol,Boolean|Hash<Symbol,Numeric>>]
338
414
  # Example: { ok: true, result { rel_key_cnt: 100, rel_time: 0.01 } }
339
415
  #
340
416
  # @api public
341
417
  # @since 1.0.0
418
+ # @version 1.5.0
342
419
  def clear_locks(
343
420
  batch_size: config[:lock_release_batch_size],
344
421
  logger: config[:logger],
345
422
  instrumenter: config[:instrumenter],
346
- instrument: nil
423
+ instrument: nil,
424
+ log_sampling_enabled: config[:log_sampling_enabled],
425
+ log_sampling_percent: config[:log_sampling_percent],
426
+ log_sampler: config[:log_sampler]
347
427
  )
348
428
  RedisQueuedLocks::Acquier::ReleaseAllLocks.release_all_locks(
349
429
  redis_client,
350
430
  batch_size,
351
431
  logger,
352
432
  instrumenter,
353
- instrument
433
+ instrument,
434
+ log_sampling_enabled,
435
+ log_sampling_percent,
436
+ log_sampler
354
437
  )
355
438
  end
356
439
 
@@ -438,18 +521,24 @@ class RedisQueuedLocks::Client
438
521
  # @option logger [::Logger,#debug]
439
522
  # @option instrumenter [#notify]
440
523
  # @option instrument [NilClass,Any]
524
+ # @option log_sampling_enabled [Boolean]
525
+ # @option log_sampling_percent [Integer]
526
+ # @option log_sampler [#sampling_happened?,Module<RedisQueuedLocks::Logging::Sampler>]
441
527
  # @return [Hash<Symbol,Boolean|Hash<Symbol,Set<String>>>]
442
528
  # Format: { ok: true, result: { processed_queus: Set<String> } }
443
529
  #
444
530
  # @api public
445
531
  # @since 1.0.0
446
- # @version 1.1.0
532
+ # @version 1.5.0
447
533
  def clear_dead_requests(
448
534
  dead_ttl: config[:dead_request_ttl],
449
535
  scan_size: config[:lock_release_batch_size],
450
536
  logger: config[:logger],
451
537
  instrumenter: config[:instrumenter],
452
- instrument: nil
538
+ instrument: nil,
539
+ log_sampling_enabled: config[:log_sampling_enabled],
540
+ log_sampling_percent: config[:log_sampling_percent],
541
+ log_sampler: config[:log_sampler]
453
542
  )
454
543
  RedisQueuedLocks::Acquier::ClearDeadRequests.clear_dead_requests(
455
544
  redis_client,
@@ -457,7 +546,10 @@ class RedisQueuedLocks::Client
457
546
  dead_ttl,
458
547
  logger,
459
548
  instrumenter,
460
- instrument
549
+ instrument,
550
+ log_sampling_enabled,
551
+ log_sampling_percent,
552
+ log_sampler
461
553
  )
462
554
  end
463
555
  end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api private
4
+ # @since 1.5.0
5
+ module RedisQueuedLocks::Logging::Sampler
6
+ class << self
7
+ # Super simple probalistic function based on the `weight` of <true>/<false> values.
8
+ # Requires the <percent> parameter as the required percent of <true> values sampled.
9
+ #
10
+ # @param sampling_percent [Integer]
11
+ # - percent of <true> values in range of 0..100;
12
+ # @return [Boolean]
13
+ # - <true> for <sampling_percent>% of invocations (and <false> for the rest invocations)
14
+ #
15
+ # @api private
16
+ # @since 1.5.0
17
+ def sampling_happened?(sampling_percent)
18
+ sampling_percent >= SecureRandom.rand(0..100)
19
+ end
20
+ end
21
+ end
@@ -4,8 +4,54 @@
4
4
  # @since 1.0.0
5
5
  module RedisQueuedLocks::Logging
6
6
  require_relative 'logging/void_logger'
7
+ require_relative 'logging/sampler'
7
8
 
8
9
  class << self
10
+ # @param log_sampling_enabled [Boolean]
11
+ # @param log_sampling_percent [Integer]
12
+ # @param log_sampler [#sampling_happened?,Module<RedisQueuedLocks::Logging::Sampler>]
13
+ # @return [Boolean]
14
+ #
15
+ # @api private
16
+ # @since 1.5.0
17
+ def should_log?(log_sampling_enabled, log_sampling_percent, log_sampler)
18
+ return true unless log_sampling_enabled
19
+ log_sampler.sampling_happened?(log_sampling_percent)
20
+ end
21
+
22
+ # @param sampler [#sampling_happened?,Module<RedisQueuedLocks::Logging::Sampler>]
23
+ # @return [Boolean]
24
+ #
25
+ # @api private
26
+ # @since 1.5.0
27
+ def valid_sampler?(sampler)
28
+ return false unless sampler.respond_to?(:sampling_happened?)
29
+
30
+ m_obj = sampler.method(:sampling_happened?)
31
+ m_sig = m_obj.parameters
32
+
33
+ # NOTE:
34
+ # Required method signature (sampling_percent)
35
+ # => [[:req, :sampling_percent]]
36
+ # => [[:opt, :sampling_percent]]
37
+ # => [[:req, :sampling_percent], [:block, :block]]
38
+ # => [[:opt, :sampling_percent], [:block, :block]]
39
+ if m_sig.size == 1
40
+ prm = m_sig[0][0]
41
+ prm == :req || prm == :opt
42
+ elsif m_sig.size == 2
43
+ f_prm = m_sig[0][0]
44
+ s_prm = m_sign[1][0]
45
+
46
+ # rubocop:disable Layout/MultilineOperationIndentation
47
+ f_prm == :req && s_prm == :block ||
48
+ f_prm == :opt && s_prm == :block
49
+ # rubocop:enable Layout/MultilineOperationIndentation
50
+ else
51
+ false
52
+ end
53
+ end
54
+
9
55
  # @param logger [::Logger,#debug,::SemanticLogger::Logger]
10
56
  # @return [Boolean]
11
57
  #
@@ -2,6 +2,7 @@
2
2
 
3
3
  # @api private
4
4
  # @since 1.0.0
5
+ # @version 1.5.0
5
6
  module RedisQueuedLocks::Utilities
6
7
  module_function
7
8
 
@@ -5,6 +5,6 @@ module RedisQueuedLocks
5
5
  #
6
6
  # @api public
7
7
  # @since 0.0.1
8
- # @version 1.4.0
9
- VERSION = '1.4.0'
8
+ # @version 1.5.0
9
+ VERSION = '1.5.0'
10
10
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: redis_queued_locks
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.0
4
+ version: 1.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rustam Ibragimov
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-05-13 00:00:00.000000000 Z
11
+ date: 2024-05-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis-client
@@ -81,6 +81,7 @@ files:
81
81
  - lib/redis_queued_locks/instrument/active_support.rb
82
82
  - lib/redis_queued_locks/instrument/void_notifier.rb
83
83
  - lib/redis_queued_locks/logging.rb
84
+ - lib/redis_queued_locks/logging/sampler.rb
84
85
  - lib/redis_queued_locks/logging/void_logger.rb
85
86
  - lib/redis_queued_locks/resource.rb
86
87
  - lib/redis_queued_locks/utilities.rb