sidekiq_solid_fetch 0.1.1
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 +7 -0
- data/.release-please-manifest.json +3 -0
- data/.rspec +2 -0
- data/.standard.yml +3 -0
- data/CHANGELOG.md +10 -0
- data/CLAUDE.md +45 -0
- data/Makefile +18 -0
- data/README.md +75 -0
- data/Rakefile +8 -0
- data/docker-compose.yml +16 -0
- data/lib/sidekiq_solid_fetch/fetcher.rb +57 -0
- data/lib/sidekiq_solid_fetch/unit_of_work.rb +29 -0
- data/lib/sidekiq_solid_fetch/version.rb +3 -0
- data/lib/sidekiq_solid_fetch.rb +39 -0
- data/release-please-config.json +16 -0
- metadata +73 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 3f9d49a1edc5ffd25c20302adac6f5fea21eb3aacab3c441d7a8722bb42faeca
|
|
4
|
+
data.tar.gz: 8c137210e8a8febf7409379c7c207270f0c3964ae378898122cc7e1a6f7ae88d
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 2df35c34eed28f7f1c3ed78e853da85b80262a21702e4334699a0c7dadc5ba2f62059dba9dea116882ee71edbfa5c503de1f682b4371e021d580409cd8640322
|
|
7
|
+
data.tar.gz: a3ffb8214437b7f4b579465b03268b3a4a4a393d481036f5ac4e90b510174ef7220f5d88a93f54038712f6e6731882889536396c7eed71b08e528163da0670e4
|
data/.rspec
ADDED
data/.standard.yml
ADDED
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [0.1.1](https://github.com/k0va1/sidekiq_solid_fetch/compare/v0.1.0...v0.1.1) (2025-12-04)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* basic implementation ([08809c1](https://github.com/k0va1/sidekiq_solid_fetch/commit/08809c11ff15a623b592721785f76706bb62557d))
|
|
9
|
+
|
|
10
|
+
## Changelog
|
data/CLAUDE.md
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Project Overview
|
|
6
|
+
|
|
7
|
+
SidekiqSolidFetch is an open-source Ruby gem that provides a reliable fetch strategy for Sidekiq, similar to Sidekiq Pro's `super_fetch`. It uses Redis LMOVE to atomically move jobs from the queue to a processing queue, ensuring jobs aren't lost if a worker crashes.
|
|
8
|
+
|
|
9
|
+
## Commands
|
|
10
|
+
|
|
11
|
+
- **Install dependencies**: `bundle install`
|
|
12
|
+
- **Run all tests**: `bundle exec rake spec`
|
|
13
|
+
- **Run a single test file**: `bundle exec rspec spec/path/to/file_spec.rb`
|
|
14
|
+
- **Run a single test by line**: `bundle exec rspec spec/path/to/file_spec.rb:LINE`
|
|
15
|
+
- **Lint code**: `bundle exec rake standard`
|
|
16
|
+
- **Auto-fix lint issues**: `bundle exec standardrb --fix`
|
|
17
|
+
- **Run all checks (tests + lint)**: `bundle exec rake`
|
|
18
|
+
- **Interactive console**: `bin/console`
|
|
19
|
+
|
|
20
|
+
## Architecture
|
|
21
|
+
|
|
22
|
+
### Core Components
|
|
23
|
+
|
|
24
|
+
- `SidekiqSolidFetch` (lib/sidekiq_solid_fetch.rb) - Main module with `enable!(config)` method to activate the custom fetcher
|
|
25
|
+
- `SidekiqSolidFetch::Fetcher` (lib/sidekiq_solid_fetch/fetcher.rb) - Custom Sidekiq fetcher implementing reliable queue processing
|
|
26
|
+
|
|
27
|
+
### Fetcher Design
|
|
28
|
+
|
|
29
|
+
The Fetcher uses Redis LMOVE to atomically move jobs from the source queue to a per-worker processing queue (`processing:queue:{name}:{identity}`). This ensures:
|
|
30
|
+
- Jobs are never lost if a worker crashes mid-execution
|
|
31
|
+
- Unfinished jobs can be requeued via `bulk_requeue`
|
|
32
|
+
|
|
33
|
+
Key classes:
|
|
34
|
+
- `Fetcher` - Implements Sidekiq's fetch interface with `retrieve_work` and `bulk_requeue`
|
|
35
|
+
- `UnitOfWork` - Struct wrapping a job with `acknowledge` and `requeue` methods
|
|
36
|
+
|
|
37
|
+
### Queue Modes
|
|
38
|
+
|
|
39
|
+
- **Strict mode** (`cap.mode == :strict`) - Processes queues in defined order
|
|
40
|
+
- **Weighted mode** (default) - Shuffles queues to distribute load
|
|
41
|
+
|
|
42
|
+
## Requirements
|
|
43
|
+
|
|
44
|
+
- Ruby >= 3.1.0
|
|
45
|
+
- Sidekiq >= 7.0
|
data/Makefile
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
REDIS_URL ?= redis://localhost:6379/0
|
|
2
|
+
|
|
3
|
+
.PHONY: test lint-fix console install
|
|
4
|
+
|
|
5
|
+
install:
|
|
6
|
+
bundle install
|
|
7
|
+
|
|
8
|
+
console:
|
|
9
|
+
bin/console
|
|
10
|
+
|
|
11
|
+
test:
|
|
12
|
+
REDIS_URL=$(REDIS_URL) bundle exec rspec $(filter-out $@,$(MAKECMDGOALS))
|
|
13
|
+
|
|
14
|
+
lint-fix:
|
|
15
|
+
bundle exec standardrb --fix
|
|
16
|
+
|
|
17
|
+
%:
|
|
18
|
+
@:
|
data/README.md
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# SidekiqSolidFetch
|
|
2
|
+
|
|
3
|
+
A reliable fetch strategy for Sidekiq that ensures jobs are never lost, even if a worker crashes mid-execution. This is an open-source implementation similar to Sidekiq Pro's `super_fetch`.
|
|
4
|
+
|
|
5
|
+
## How It Works
|
|
6
|
+
|
|
7
|
+
SidekiqSolidFetch uses Redis `LMOVE` command to atomically move jobs from the queue to a per-worker processing queue. This approach ensures:
|
|
8
|
+
|
|
9
|
+
- **No job loss**: Jobs are moved (not copied) to a processing queue before execution. If a worker crashes, the job remains in the processing queue.
|
|
10
|
+
- **Automatic recovery**: On startup, any unfinished jobs from previous runs are automatically requeued.
|
|
11
|
+
- **Graceful shutdown**: When Sidekiq shuts down, in-progress jobs are moved back to their original queues.
|
|
12
|
+
|
|
13
|
+
### Flow
|
|
14
|
+
|
|
15
|
+
1. Worker fetches a job → job is atomically moved from `queue:default` to `sidekiq_solid_fetch:processing:queue:default`
|
|
16
|
+
2. Worker processes the job successfully → job is removed from the processing queue
|
|
17
|
+
3. Worker crashes → job stays in the processing queue
|
|
18
|
+
4. On next startup → jobs in processing queues are moved back to their original queues
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
Add to your Gemfile:
|
|
23
|
+
|
|
24
|
+
```ruby
|
|
25
|
+
gem "sidekiq_solid_fetch"
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Then run:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
bundle install
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Usage
|
|
35
|
+
|
|
36
|
+
Enable SidekiqSolidFetch in your Sidekiq configuration:
|
|
37
|
+
|
|
38
|
+
```ruby
|
|
39
|
+
# config/initializers/sidekiq.rb (Rails)
|
|
40
|
+
# or wherever you configure Sidekiq
|
|
41
|
+
|
|
42
|
+
require "sidekiq_solid_fetch"
|
|
43
|
+
|
|
44
|
+
Sidekiq.configure_server do |config|
|
|
45
|
+
SidekiqSolidFetch.enable!(config)
|
|
46
|
+
end
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
That's it! SidekiqSolidFetch will now handle job fetching with crash recovery.
|
|
50
|
+
|
|
51
|
+
## Requirements
|
|
52
|
+
|
|
53
|
+
- Ruby >= 3.1.0
|
|
54
|
+
- Sidekiq >= 7.0
|
|
55
|
+
|
|
56
|
+
## Development
|
|
57
|
+
|
|
58
|
+
After checking out the repo, run `bin/setup` to install dependencies.
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
# Run tests
|
|
62
|
+
docker compose up -d
|
|
63
|
+
make test
|
|
64
|
+
|
|
65
|
+
# Run linter
|
|
66
|
+
make lint-fix
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Contributing
|
|
70
|
+
|
|
71
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/k0va1/sidekiq_solid_fetch.
|
|
72
|
+
|
|
73
|
+
## License
|
|
74
|
+
|
|
75
|
+
The gem is available as open source under the terms of the MIT License.
|
data/Rakefile
ADDED
data/docker-compose.yml
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
require "sidekiq"
|
|
2
|
+
require "sidekiq/component"
|
|
3
|
+
require "sidekiq/capsule"
|
|
4
|
+
|
|
5
|
+
module SidekiqSolidFetch
|
|
6
|
+
class Fetcher
|
|
7
|
+
include Sidekiq::Component
|
|
8
|
+
|
|
9
|
+
def initialize(cap)
|
|
10
|
+
raise ArgumentError, "missing queue list" unless cap.queues
|
|
11
|
+
@config = cap
|
|
12
|
+
@strictly_ordered_queues = cap.mode == :strict
|
|
13
|
+
@queues = config.queues.map { |q| "queue:#{q}" }
|
|
14
|
+
@queues.uniq! if @strictly_ordered_queues
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def retrieve_work
|
|
18
|
+
queues_cmd.each do |queue|
|
|
19
|
+
processing_queue_name = ::SidekiqSolidFetch.processing_queue_name(queue)
|
|
20
|
+
work = redis do |conn|
|
|
21
|
+
conn.lmove(queue, processing_queue_name, "RIGHT", "LEFT")
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
return ::SidekiqSolidFetch::UnitOfWork.new(queue, work, config, processing_queue_name) if work
|
|
25
|
+
end
|
|
26
|
+
nil
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def bulk_requeue(*)
|
|
30
|
+
logger.info("SidekiqSolidFetch: Re-queueing terminated jobs")
|
|
31
|
+
|
|
32
|
+
count = 0
|
|
33
|
+
redis do |conn|
|
|
34
|
+
queues_cmd.each do |queue|
|
|
35
|
+
processing_queue_name = ::SidekiqSolidFetch.processing_queue_name(queue)
|
|
36
|
+
while conn.lmove(processing_queue_name, queue, "RIGHT", "LEFT")
|
|
37
|
+
count += 1
|
|
38
|
+
logger.info { "SidekiqSolidFetch: Moving job from #{processing_queue_name} back to #{queue}" }
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
logger.info("SidekiqSolidFetch: Re-queued #{count} jobs")
|
|
43
|
+
rescue => ex
|
|
44
|
+
logger.warn("SidekiqSolidFetch: Failed to requeue jobs: #{ex.message}")
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def queues_cmd
|
|
48
|
+
if @strictly_ordered_queues
|
|
49
|
+
@queues
|
|
50
|
+
else
|
|
51
|
+
permute = @queues.shuffle
|
|
52
|
+
permute.uniq!
|
|
53
|
+
permute
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module SidekiqSolidFetch
|
|
2
|
+
class UnitOfWork
|
|
3
|
+
attr_accessor :queue, :job, :config, :processing_queue
|
|
4
|
+
|
|
5
|
+
def initialize(queue, job, config, processing_queue)
|
|
6
|
+
@queue = queue
|
|
7
|
+
@job = job
|
|
8
|
+
@config = config
|
|
9
|
+
@processing_queue = processing_queue
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def acknowledge
|
|
13
|
+
config.redis { |conn| conn.lrem(processing_queue, -1, job) }
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def queue_name
|
|
17
|
+
queue.delete_prefix("queue:")
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def requeue
|
|
21
|
+
config.redis do |conn|
|
|
22
|
+
conn.multi do |multi|
|
|
23
|
+
multi.lrem(processing_queue, -1, job)
|
|
24
|
+
multi.lpush(queue, job)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
require_relative "sidekiq_solid_fetch/version"
|
|
2
|
+
require_relative "sidekiq_solid_fetch/unit_of_work"
|
|
3
|
+
require_relative "sidekiq_solid_fetch/fetcher"
|
|
4
|
+
|
|
5
|
+
module SidekiqSolidFetch
|
|
6
|
+
class Error < StandardError; end
|
|
7
|
+
|
|
8
|
+
PROCESSING_QUEUE_PREFIX = "sidekiq_solid_fetch:processing"
|
|
9
|
+
|
|
10
|
+
def self.enable!(config)
|
|
11
|
+
config[:fetch_class] = SidekiqSolidFetch::Fetcher
|
|
12
|
+
|
|
13
|
+
config.on(:startup) do
|
|
14
|
+
Sidekiq.logger.info("SidekiqSolidFetch enabled")
|
|
15
|
+
requeue_not_finished_jobs(config)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def self.requeue_not_finished_jobs(config)
|
|
20
|
+
Sidekiq.logger.info("SidekiqSolidFetch: Re-queueing not finished jobs from previous runs")
|
|
21
|
+
|
|
22
|
+
count = 0
|
|
23
|
+
Sidekiq.redis do |conn|
|
|
24
|
+
config.queues.map { |q| "queue:#{q}" }.uniq.each do |queue|
|
|
25
|
+
processing_queue_name = ::SidekiqSolidFetch.processing_queue_name(queue)
|
|
26
|
+
while conn.lmove(processing_queue_name, queue, "RIGHT", "LEFT")
|
|
27
|
+
count += 1
|
|
28
|
+
Sidekiq.logger.info { "SidekiqSolidFetch: Moved job from #{processing_queue_name} back to #{queue}" }
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
Sidekiq.logger.info("SidekiqSolidFetch: Re-queued #{count} jobs from previous runs")
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def self.processing_queue_name(queue)
|
|
37
|
+
"#{PROCESSING_QUEUE_PREFIX}:#{queue}"
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"packages": {
|
|
3
|
+
".": {
|
|
4
|
+
"package-name": "sidekiq_solid_fetch",
|
|
5
|
+
"include-component-in-tag": false,
|
|
6
|
+
"changelog-path": "CHANGELOG.md",
|
|
7
|
+
"release-type": "ruby",
|
|
8
|
+
"bump-minor-pre-major": true,
|
|
9
|
+
"bump-patch-for-minor-pre-major": true,
|
|
10
|
+
"draft": false,
|
|
11
|
+
"prerelease": false,
|
|
12
|
+
"version-file": "lib/sidekiq_solid_fetch/version.rb"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json"
|
|
16
|
+
}
|
metadata
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: sidekiq_solid_fetch
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Alex Koval
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: exe
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2025-12-05 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: sidekiq
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '7.0'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ">="
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '7.0'
|
|
27
|
+
description: OSS implementation of Sidekiq Pro's `super_fetch`
|
|
28
|
+
email:
|
|
29
|
+
- al3xander.koval@gmail.com
|
|
30
|
+
executables: []
|
|
31
|
+
extensions: []
|
|
32
|
+
extra_rdoc_files: []
|
|
33
|
+
files:
|
|
34
|
+
- ".release-please-manifest.json"
|
|
35
|
+
- ".rspec"
|
|
36
|
+
- ".standard.yml"
|
|
37
|
+
- CHANGELOG.md
|
|
38
|
+
- CLAUDE.md
|
|
39
|
+
- Makefile
|
|
40
|
+
- README.md
|
|
41
|
+
- Rakefile
|
|
42
|
+
- docker-compose.yml
|
|
43
|
+
- lib/sidekiq_solid_fetch.rb
|
|
44
|
+
- lib/sidekiq_solid_fetch/fetcher.rb
|
|
45
|
+
- lib/sidekiq_solid_fetch/unit_of_work.rb
|
|
46
|
+
- lib/sidekiq_solid_fetch/version.rb
|
|
47
|
+
- release-please-config.json
|
|
48
|
+
homepage: https://github.com/k0va1/sidekiq_solid_fetch
|
|
49
|
+
licenses: []
|
|
50
|
+
metadata:
|
|
51
|
+
homepage_uri: https://github.com/k0va1/sidekiq_solid_fetch
|
|
52
|
+
source_code_uri: https://github.com/k0va1/sidekiq_solid_fetch
|
|
53
|
+
changelog_uri: https://github.com/k0va1/sidekiq_solid_fetch/blob/master/CHANGELOG.md
|
|
54
|
+
post_install_message:
|
|
55
|
+
rdoc_options: []
|
|
56
|
+
require_paths:
|
|
57
|
+
- lib
|
|
58
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
59
|
+
requirements:
|
|
60
|
+
- - ">="
|
|
61
|
+
- !ruby/object:Gem::Version
|
|
62
|
+
version: 3.1.0
|
|
63
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - ">="
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '0'
|
|
68
|
+
requirements: []
|
|
69
|
+
rubygems_version: 3.5.22
|
|
70
|
+
signing_key:
|
|
71
|
+
specification_version: 4
|
|
72
|
+
summary: OSS implementation of Sidekiq Pro's `super_fetch`
|
|
73
|
+
test_files: []
|