cache_sweeper 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 392da6714d4f37e50e0defdcde8bb441ea64409dc61e05bb041086048d317146
4
+ data.tar.gz: ef6299ad81c1828237b3c3b66c4d6b331df3af6f86d54d2d71fa066a7e5c5fde
5
+ SHA512:
6
+ metadata.gz: 8d8aa522bf13d117ec228c7c4028fef58953d2dc7b2e002a5b51f273938df6e9ea7f09c8422dee558efb239e01187b63eaa3064a1f8d4623450d1cf94d07123d
7
+ data.tar.gz: 8f230984e6fcdf49af9aaa33cac2714d88b6ab1111d8881b0031801db5a53977649fed23123060cbee08545eb4b3634a03996460011cd9a76415544a8f0432cc
@@ -0,0 +1,74 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as
6
+ contributors and maintainers pledge to making participation in our project and
7
+ our community a harassment-free experience for everyone, regardless of age, body
8
+ size, disability, ethnicity, gender identity and expression, level of experience,
9
+ nationality, personal appearance, race, religion, or sexual identity and
10
+ orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to creating a positive environment
15
+ include:
16
+
17
+ * Using welcoming and inclusive language
18
+ * Being respectful of differing viewpoints and experiences
19
+ * Gracefully accepting constructive criticism
20
+ * Focusing on what is best for the community
21
+ * Showing empathy towards other community members
22
+
23
+ Examples of unacceptable behavior by participants include:
24
+
25
+ * The use of sexualized language or imagery and unwelcome sexual attention or
26
+ advances
27
+ * Trolling, insulting/derogatory comments, and personal or political attacks
28
+ * Public or private harassment
29
+ * Publishing others' private information, such as a physical or electronic
30
+ address, without explicit permission
31
+ * Other conduct which could reasonably be considered inappropriate in a
32
+ professional setting
33
+
34
+ ## Our Responsibilities
35
+
36
+ Project maintainers are responsible for clarifying the standards of acceptable
37
+ behavior and are expected to take appropriate and fair corrective action in
38
+ response to any instances of unacceptable behavior.
39
+
40
+ Project maintainers have the right and responsibility to remove, edit, or
41
+ reject comments, commits, code, wiki edits, issues, and other contributions
42
+ that are not aligned to this Code of Conduct, or to ban temporarily or
43
+ permanently any contributor for other behaviors that they deem inappropriate,
44
+ threatening, offensive, or harmful.
45
+
46
+ ## Scope
47
+
48
+ This Code of Conduct applies both within project spaces and in public spaces
49
+ when an individual is representing the project or its community. Examples of
50
+ representing a project or community include using an official project e-mail
51
+ address, posting via an official social media account, or acting as an appointed
52
+ representative at an online or offline event. Representation of a project may be
53
+ further defined and clarified by project maintainers.
54
+
55
+ ## Enforcement
56
+
57
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
+ reported by contacting the project team at rafayqayyum786@gmail.com. All
59
+ complaints will be reviewed and investigated and will result in a response that
60
+ is deemed necessary and appropriate to the circumstances. The project team is
61
+ obligated to maintain confidentiality with regard to the reporter of an incident.
62
+ Further details of specific enforcement policies may be posted separately.
63
+
64
+ Project maintainers who do not follow or enforce the Code of Conduct in good
65
+ faith may face temporary or permanent repercussions as determined by other
66
+ members of the project's leadership.
67
+
68
+ ## Attribution
69
+
70
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
+ available at [https://contributor-covenant.org/version/1/4][version]
72
+
73
+ [homepage]: https://contributor-covenant.org
74
+ [version]: https://contributor-covenant.org/version/1/4/
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2025 Rafay Qayyum
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,586 @@
1
+ # CacheSweeper
2
+
3
+ A flexible, rule-based cache invalidation gem for Rails applications. CacheSweeper enables you to define cache invalidation logic in dedicated sweeper classes, keeping your models clean and your cache logic organized. It supports batching, async jobs via Sidekiq, and association-aware cache sweeping with comprehensive logging.
4
+
5
+ ## Table of Contents
6
+
7
+ - [Features](#features)
8
+ - [Installation](#installation)
9
+ - [Quick Start](#quick-start)
10
+ - [Configuration](#configuration)
11
+ - [Usage Examples](#usage-examples)
12
+ - [API Reference](#api-reference)
13
+ - [Logging](#logging)
14
+ - [Middleware](#middleware)
15
+ - [Testing](#testing)
16
+ - [Troubleshooting](#troubleshooting)
17
+ - [Contributing](#contributing)
18
+ - [License](#license)
19
+
20
+ ## Features
21
+
22
+ - **Rule-based cache invalidation**: Define what changes should trigger cache clearing using a simple DSL
23
+ - **Flexible triggering**: Choose between instant cache deletion or request-level batching
24
+ - **Async processing**: Offload cache deletion to Sidekiq for scalability
25
+ - **Association support**: Invalidate cache for associated models when their attributes change
26
+ - **Multi-level configuration**: Control behavior globally, per-sweeper, or per-rule
27
+ - **Comprehensive logging**: Detailed logging for debugging with configurable levels
28
+ - **Performance monitoring**: Built-in performance logging for cache operations and timing
29
+ - **Error tracking**: Comprehensive error logging with context and stack traces
30
+ - **Clean model code**: Keep cache logic out of your models
31
+ - **Thread-safe**: Uses RequestStore for reliable multi-threaded operation
32
+ - **Efficient batch deletion**: Uses `Rails.cache.delete_multi` for optimal performance
33
+ - **Configurable batch sizes**: Control how many keys are deleted in each batch
34
+
35
+ ## Installation
36
+
37
+ Add this line to your application's Gemfile:
38
+
39
+ ```ruby
40
+ gem 'cache_sweeper', path: 'path/to/cache_sweeper'
41
+ ```
42
+
43
+ Then run:
44
+
45
+ ```sh
46
+ bundle install
47
+ ```
48
+
49
+ ### Dependencies
50
+
51
+ - **Rails**: 5.0+ (tested with Rails 6.x and 7.x)
52
+ - **Sidekiq**: Required for async cache deletion
53
+ - **RequestStore**: For thread-safe request-level storage
54
+
55
+ Add Sidekiq to your Gemfile:
56
+
57
+ ```ruby
58
+ gem 'sidekiq'
59
+ gem 'request_store'
60
+ ```
61
+
62
+ ## Quick Start
63
+
64
+ ### 1. Configure the Gem
65
+
66
+ Create `config/initializers/cache_sweeper.rb`:
67
+
68
+ ```ruby
69
+ # Logging configuration
70
+ CacheSweeper.logger = Rails.logger
71
+ CacheSweeper.log_level = :info # :debug, :info, :warn, :error
72
+
73
+ # Global cache invalidation configuration (optional)
74
+ CacheSweeper.trigger = :request # :instant or :request
75
+ CacheSweeper.mode = :async # :async or :inline
76
+ CacheSweeper.queue = :low # Sidekiq queue name
77
+ CacheSweeper.sidekiq_options = { retry: false }
78
+ CacheSweeper.delete_multi_batch_size = 100 # Batch size for efficient cache deletion
79
+ ```
80
+
81
+ ### 2. Add Middleware (if using request-level batching)
82
+
83
+ Add to `config/application.rb`:
84
+
85
+ ```ruby
86
+ config.middleware.use CacheSweeperFlushMiddleware
87
+ ```
88
+
89
+ ### 3. Create Your First Sweeper
90
+
91
+ Create `app/cache_sweepers/product_sweeper.rb`:
92
+
93
+ ```ruby
94
+ class ProductSweeper < CacheSweeper::Base
95
+ # Configure this sweeper's behavior
96
+ sweeper_options trigger: :request, mode: :async, queue: :products
97
+
98
+ # Clear cache when name or price changes
99
+ watch attributes: [:name, :price], keys: ->(product) { ["product:#{product.id}"] }
100
+
101
+ # Clear cache when product is created, updated, or destroyed
102
+ watch attributes: [:name, :price], keys: ->(product) { ["products:index", "products:featured"] }
103
+ end
104
+ ```
105
+
106
+ ### 4. Start Sidekiq
107
+
108
+ ```sh
109
+ bundle exec sidekiq
110
+ ```
111
+
112
+ That's it! Your cache will now be automatically invalidated when products change.
113
+
114
+ ## Configuration
115
+
116
+ ### Global Configuration
117
+
118
+ Configure in `config/initializers/cache_sweeper.rb`:
119
+
120
+ ```ruby
121
+ # Logging configuration
122
+ CacheSweeper.logger = Rails.logger
123
+ CacheSweeper.log_level = :info # :debug, :info, :warn, :error
124
+
125
+ # Cache invalidation configuration (optional - has sensible defaults)
126
+ CacheSweeper.trigger = :request # :instant or :request
127
+ CacheSweeper.mode = :async # :async or :inline
128
+ CacheSweeper.queue = :low # Sidekiq queue name
129
+ CacheSweeper.sidekiq_options = { retry: false }
130
+ CacheSweeper.delete_multi_batch_size = 100 # Batch size for efficient cache deletion
131
+ ```
132
+
133
+ ### Sweeper-Level Configuration
134
+
135
+ Use the `sweeper_options` DSL for clean sweeper configuration:
136
+
137
+ ```ruby
138
+ class OrderSweeper < CacheSweeper::Base
139
+ sweeper_options trigger: :request, mode: :async, queue: :orders
140
+ # ... watch rules
141
+ end
142
+ ```
143
+
144
+ ### Rule-Level Configuration
145
+
146
+ Override configuration for specific rules:
147
+
148
+ ```ruby
149
+ class MixedSweeper < CacheSweeper::Base
150
+ # Instant deletion for critical data
151
+ watch attributes: [:name], keys: ->(obj) { ["instant:#{obj.id}"] },
152
+ trigger: :instant, mode: :inline
153
+
154
+ # Async processing for less critical data
155
+ watch attributes: [:description], keys: ->(obj) { ["async:#{obj.id}"] },
156
+ trigger: :request, mode: :async, queue: :background
157
+ end
158
+ ```
159
+
160
+ ### Configuration Precedence
161
+
162
+ Configuration is resolved in this order (highest to lowest priority):
163
+
164
+ 1. **Rule-level** - Options passed to individual `watch` calls
165
+ 2. **Sweeper-level** - Configuration set on the sweeper class
166
+ 3. **Global-level** - Default configuration set globally
167
+
168
+ ### Configuration Options
169
+
170
+ - **`trigger`**: `:instant` (delete immediately) or `:request` (batch until end of request)
171
+ - **`mode`**: `:async` (use Sidekiq) or `:inline` (synchronous)
172
+ - **`queue`**: Sidekiq queue name (e.g., `:low`, `:high`, `:background`)
173
+ - **`sidekiq_options`**: Hash of Sidekiq options (e.g., `{ retry: false, backtrace: true }`)
174
+ - **`delete_multi_batch_size`**: Number of keys to delete in each batch (default: 100)
175
+
176
+ ## Usage Examples
177
+
178
+ ### Basic Sweeper
179
+
180
+ ```ruby
181
+ # app/cache_sweepers/product_sweeper.rb
182
+ class ProductSweeper < CacheSweeper::Base
183
+ watch attributes: [:name, :price], keys: ->(product) { ["product:#{product.id}"] }
184
+ end
185
+ ```
186
+
187
+ ### Association Sweeper
188
+
189
+ ```ruby
190
+ # app/cache_sweepers/package_sweeper.rb
191
+ class PackageSweeper < CacheSweeper::Base
192
+ # Clear cache when package name changes
193
+ watch attributes: [:name], keys: ->(package) { ["package:#{package.id}"] }
194
+
195
+ # Clear cache when associated products change
196
+ watch :products, attributes: [:name], keys: ->(product) {
197
+ product.packages.map { |pkg| "package:#{pkg.id}" }
198
+ }
199
+ end
200
+ ```
201
+
202
+ ### Conditional Cache Invalidation
203
+
204
+ ```ruby
205
+ # app/cache_sweepers/user_sweeper.rb
206
+ class UserSweeper < CacheSweeper::Base
207
+ # Clear cache when user profile changes
208
+ watch attributes: [:name, :email], keys: ->(user) { ["user:#{user.id}", "user:#{user.id}:profile"] }
209
+
210
+ # Clear cache with custom condition
211
+ watch attributes: [:last_login_at], keys: ->(user) { ["users:active"] },
212
+ if: ->(user) { user.last_login_at_changed? && user.last_login_at > 1.day.ago }
213
+ end
214
+ ```
215
+
216
+ ### Mixed Configuration Sweeper
217
+
218
+ ```ruby
219
+ # app/cache_sweepers/order_sweeper.rb
220
+ class OrderSweeper < CacheSweeper::Base
221
+ # Default configuration for this sweeper
222
+ sweeper_options trigger: :request, mode: :async, queue: :orders
223
+
224
+ # Critical data - instant deletion
225
+ watch attributes: [:status], keys: ->(order) { ["order:#{order.id}"] },
226
+ trigger: :instant, mode: :inline
227
+
228
+ # Less critical data - async processing
229
+ watch attributes: [:notes], keys: ->(order) { ["order:#{order.id}:notes"] },
230
+ trigger: :request, mode: :async, queue: :background
231
+
232
+ # Association changes
233
+ watch :order_items, attributes: [:quantity, :price], keys: ->(order_item) {
234
+ ["order:#{order_item.order_id}", "order:#{order_item.order_id}:total"]
235
+ }
236
+ end
237
+ ```
238
+
239
+ ### Custom Callback Events
240
+
241
+ ```ruby
242
+ # app/cache_sweepers/notification_sweeper.rb
243
+ class NotificationSweeper < CacheSweeper::Base
244
+ # Only clear cache on create and destroy, not update
245
+ watch attributes: [:message], keys: ->(notification) { ["notifications:count"] },
246
+ on: [:create, :destroy]
247
+
248
+ # Use before_commit instead of after_commit
249
+ watch attributes: [:read_at], keys: ->(notification) { ["user:#{notification.user_id}:unread_count"] },
250
+ callback: :before_commit
251
+ end
252
+ ```
253
+
254
+ ## API Reference
255
+
256
+ ### Sweeper DSL
257
+
258
+ #### `watch(association = nil, **options)`
259
+
260
+ Define cache invalidation rules.
261
+
262
+ **Parameters:**
263
+
264
+ - **`association`** (optional): Association name to watch (e.g., `:products`, `:order_items`)
265
+ - **`attributes`**: Array of attributes to watch for changes (e.g., `[:name, :price]`)
266
+ - **`keys`**: Proc or array of cache keys to invalidate
267
+ - **`if`**: Proc or method name for conditional invalidation
268
+ - **`trigger`**: `:instant` or `:request` (per rule)
269
+ - **`mode`**: `:async` or `:inline` (per rule)
270
+ - **`queue`**: Sidekiq queue name (per rule)
271
+ - **`sidekiq_options`**: Hash of Sidekiq options (per rule)
272
+ - **`callback`**: Callback type (`:after_commit`, `:before_commit`, etc.)
273
+ - **`on`**: Events to watch (`[:create, :update, :destroy]`)
274
+
275
+ **Examples:**
276
+
277
+ ```ruby
278
+ # Basic usage
279
+ watch attributes: [:name], keys: ->(obj) { ["key:#{obj.id}"] }
280
+
281
+ # Association watching
282
+ watch :products, attributes: [:name], keys: ->(product) { ["product:#{product.id}"] }
283
+
284
+ # Conditional invalidation
285
+ watch attributes: [:status], keys: ->(obj) { ["key"] },
286
+ if: ->(obj) { obj.status == 'published' }
287
+
288
+ # Custom events
289
+ watch attributes: [:name], keys: ->(obj) { ["key"] },
290
+ on: [:create, :destroy]
291
+ ```
292
+
293
+ #### `sweeper_options(**options)`
294
+
295
+ Configure sweeper-level behavior.
296
+
297
+ **Parameters:**
298
+
299
+ - **`trigger`**: `:instant` or `:request`
300
+ - **`mode`**: `:async` or `:inline`
301
+ - **`queue`**: Sidekiq queue name
302
+ - **`sidekiq_options`**: Hash of Sidekiq options
303
+
304
+ **Example:**
305
+
306
+ ```ruby
307
+ class MySweeper < CacheSweeper::Base
308
+ sweeper_options trigger: :request, mode: :async, queue: :low
309
+ end
310
+ ```
311
+
312
+ ### Global Configuration
313
+
314
+ #### `CacheSweeper.logger = logger`
315
+
316
+ Set the logger for cache actions.
317
+
318
+ #### `CacheSweeper.log_level = level`
319
+
320
+ Set minimum log level (`:debug`, `:info`, `:warn`, `:error`).
321
+
322
+ #### Global Configuration Attributes
323
+
324
+ Configure global cache invalidation behavior using direct attribute assignment:
325
+
326
+ **Example:**
327
+
328
+ ```ruby
329
+ CacheSweeper.trigger = :request
330
+ CacheSweeper.mode = :async
331
+ CacheSweeper.queue = :low
332
+ CacheSweeper.sidekiq_options = { retry: false }
333
+ ```
334
+
335
+ ## Logging
336
+
337
+ The gem provides comprehensive logging to help debug cache invalidation issues.
338
+
339
+ ### Log Levels
340
+
341
+ - **`:debug`** - All logging enabled (initialization, rule execution, performance, cache operations, async jobs, middleware)
342
+ - **`:info`** - Important events (initialization, cache operations, async jobs, middleware)
343
+ - **`:warn`** - Warnings and errors only
344
+ - **`:error`** - Errors only
345
+
346
+ ### Default Log Levels
347
+
348
+ - **Development**: `:debug` (all logging enabled)
349
+ - **Production**: `:warn` (warnings and errors only)
350
+ - **Other environments**: `:info`
351
+
352
+ ### Log Output
353
+
354
+ Log output includes:
355
+
356
+ - **Initialization**: Sweeper loading and model attachment
357
+ - **Rule execution**: Which rules are triggered, condition evaluation, cache key generation
358
+ - **Performance**: Timing for cache operations and rule execution
359
+ - **Cache operations**: Cache key invalidation details
360
+ - **Async jobs**: Job scheduling and execution status
361
+ - **Middleware**: Request-level batching and flushing
362
+ - **Errors**: Detailed error information with context and stack traces
363
+
364
+ ### Example Log Output
365
+
366
+ ```
367
+ [CacheSweeper] [2024-01-15 10:30:45.123] [INFO] Initialization: Processing sweeper: ProductSweeper
368
+ [CacheSweeper] [2024-01-15 10:30:45.124] [DEBUG] Rule execution: ProductSweeper -> Product#123
369
+ [CacheSweeper] [2024-01-15 10:30:45.125] [INFO] Cache operations: Deleted instantly: product:123
370
+ [CacheSweeper] [2024-01-15 10:30:45.126] [DEBUG] Performance: cache_invalidation took 2.456ms
371
+ ```
372
+
373
+ ### Debugging Configuration
374
+
375
+ ```ruby
376
+ # Enable all logging for debugging
377
+ CacheSweeper.logger = Rails.logger
378
+ CacheSweeper.log_level = :debug # Shows everything
379
+
380
+ # Or use different levels
381
+ CacheSweeper.log_level = :info # Shows important events only
382
+ CacheSweeper.log_level = :warn # Shows warnings and errors only
383
+ CacheSweeper.log_level = :error # Shows errors only
384
+ ```
385
+
386
+ ## Middleware
387
+
388
+ The `CacheSweeperFlushMiddleware` handles request-level batching. It automatically flushes all pending cache keys at the end of each request.
389
+
390
+ ### Setup
391
+
392
+ Add to `config/application.rb`:
393
+
394
+ ```ruby
395
+ config.middleware.use CacheSweeperFlushMiddleware
396
+ ```
397
+
398
+ ### How It Works
399
+
400
+ 1. When `trigger: :request` is used, cache keys are batched during the request
401
+ 2. At the end of the request, the middleware flushes all pending keys
402
+ 3. Keys are processed according to their `mode` setting (`:async` or `:inline`)
403
+
404
+ ### Middleware Order
405
+
406
+ Place the middleware after other middleware that might affect caching:
407
+
408
+ ```ruby
409
+ # config/application.rb
410
+ config.middleware.use SomeOtherMiddleware
411
+ config.middleware.use CacheSweeperFlushMiddleware
412
+ ```
413
+
414
+ ## Testing
415
+
416
+ ### Basic Testing
417
+
418
+ You can test sweepers using standard Rails/ActiveRecord test frameworks:
419
+
420
+ ```ruby
421
+ # test/sweepers/product_sweeper_test.rb
422
+ class ProductSweeperTest < ActiveSupport::TestCase
423
+ test "clears cache when product name changes" do
424
+ product = Product.create!(name: "Original Name")
425
+
426
+ # Mock cache
427
+ Rails.cache.expects(:delete).with("product:#{product.id}")
428
+
429
+ product.update!(name: "New Name")
430
+ end
431
+ end
432
+ ```
433
+
434
+ ### Testing Async Jobs
435
+
436
+ For async jobs, ensure Sidekiq is running or stub the worker:
437
+
438
+ ```ruby
439
+ # test/sweepers/async_sweeper_test.rb
440
+ class AsyncSweeperTest < ActiveSupport::TestCase
441
+ test "schedules async job for cache deletion" do
442
+ # Stub Sidekiq worker
443
+ CacheSweeper::AsyncWorker.expects(:perform_async).with(["key1", "key2"])
444
+
445
+ # Trigger the change
446
+ product = Product.create!(name: "Test Product")
447
+ end
448
+ end
449
+ ```
450
+
451
+ ### Testing with Sidekiq
452
+
453
+ For integration tests with Sidekiq:
454
+
455
+ ```ruby
456
+ # test/integration/cache_sweeper_integration_test.rb
457
+ class CacheSweeperIntegrationTest < ActionDispatch::IntegrationTest
458
+ test "async cache deletion works end-to-end" do
459
+ # Ensure Sidekiq is running
460
+ Sidekiq::Testing.inline! do
461
+ product = Product.create!(name: "Test Product")
462
+ # Cache should be cleared synchronously
463
+ end
464
+ end
465
+ end
466
+ ```
467
+
468
+ ## Troubleshooting
469
+
470
+ ### Common Issues
471
+
472
+ #### Sidekiq Not Running
473
+
474
+ **Problem**: Async cache deletion doesn't work.
475
+
476
+ **Solution**: Ensure Sidekiq is running:
477
+
478
+ ```sh
479
+ bundle exec sidekiq
480
+ ```
481
+
482
+ #### Sweepers Not Loading
483
+
484
+ **Problem**: Sweepers aren't being loaded or attached to models.
485
+
486
+ **Solution**:
487
+ 1. Ensure sweepers are in `app/cache_sweepers/` directory
488
+ 2. Ensure sweepers inherit from `CacheSweeper::Base`
489
+ 3. Check that the sweeper files are named `*_sweeper.rb`
490
+
491
+ #### Cache Keys Not Being Cleared
492
+
493
+ **Problem**: Cache keys aren't being invalidated when models change.
494
+
495
+ **Solution**:
496
+ 1. Enable debug logging: `CacheSweeper.log_level = :debug`
497
+ 2. Check that the correct attributes are being watched
498
+ 3. Verify cache key generation logic
499
+ 4. Ensure the model callbacks are being triggered
500
+
501
+ #### Middleware Not Flushing
502
+
503
+ **Problem**: Request-level batching isn't flushing at the end of requests.
504
+
505
+ **Solution**:
506
+ 1. Ensure `CacheSweeperFlushMiddleware` is added to the middleware stack
507
+ 2. Check middleware order in `config/application.rb`
508
+ 3. Verify that `trigger: :request` is being used
509
+
510
+ ### Debugging Steps
511
+
512
+ 1. **Enable comprehensive logging**:
513
+ ```ruby
514
+ CacheSweeper.logger = Rails.logger
515
+ CacheSweeper.log_level = :debug
516
+ ```
517
+
518
+ 2. **Check sweeper loading**:
519
+ ```ruby
520
+ # In Rails console
521
+ CacheSweeper::Base.descendants
522
+ ```
523
+
524
+ 3. **Verify model callbacks**:
525
+ ```ruby
526
+ # In Rails console
527
+ Product._commit_callbacks.map(&:filter)
528
+ ```
529
+
530
+ 4. **Test cache key generation**:
531
+ ```ruby
532
+ # In Rails console
533
+ product = Product.first
534
+ sweeper = ProductSweeper.new
535
+ # Test your key generation logic
536
+ ```
537
+
538
+ ### Performance Considerations
539
+
540
+ - **Use `:instant` trigger** for critical cache that must be cleared immediately
541
+ - **Use `:request` trigger** for less critical cache to reduce database load
542
+ - **Use `:async` mode** for high-volume applications to avoid blocking requests
543
+ - **Use `:inline` mode** for low-volume applications or when immediate consistency is required
544
+ - **Monitor Sidekiq queue sizes** to ensure async jobs are being processed
545
+ - **Optimize batch sizes**: Adjust `delete_multi_batch_size` based on your cache store's performance
546
+ - **Redis**: 100-500 keys per batch works well
547
+ - **Memcached**: 50-200 keys per batch is optimal
548
+ - **File store**: 10-50 keys per batch to avoid I/O bottlenecks
549
+
550
+ ### Memory Usage
551
+
552
+ - Request-level batching stores cache keys in memory during the request
553
+ - For high-volume applications, consider using `:instant` trigger to avoid memory buildup
554
+ - Monitor RequestStore memory usage in production
555
+
556
+ ## Contributing
557
+
558
+ 1. Fork the repository
559
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
560
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
561
+ 4. Push to the branch (`git push origin my-new-feature`)
562
+ 5. Create a new Pull Request
563
+
564
+ ### Development Setup
565
+
566
+ 1. Clone the repository
567
+ 2. Run `bundle install`
568
+ 3. Run tests with `bundle exec rspec`
569
+ 4. Run linting with `bundle exec rubocop`
570
+
571
+ ### Code Style
572
+
573
+ - Follow Ruby style guidelines
574
+ - Write tests for new features
575
+ - Update documentation for API changes
576
+ - Use meaningful commit messages
577
+
578
+ ## License
579
+
580
+ MIT License. See [LICENSE.txt](LICENSE.txt) for details.
581
+
582
+ ## Links
583
+
584
+ - [GitHub Repository](https://github.com/rafayqayyum/cache_sweeper)
585
+ - [MIT License](https://opensource.org/licenses/MIT)
586
+ - [RubyGems](https://rubygems.org/gems/cache_sweeper) (when published)