pgbus 0.0.1 → 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.
Files changed (83) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +37 -3
  3. data/Rakefile +98 -1
  4. data/app/controllers/pgbus/application_controller.rb +8 -0
  5. data/app/controllers/pgbus/recurring_tasks_controller.rb +36 -0
  6. data/app/helpers/pgbus/application_helper.rb +41 -0
  7. data/app/models/pgbus/application_record.rb +7 -0
  8. data/app/models/pgbus/batch_entry.rb +31 -0
  9. data/app/models/pgbus/blocked_execution.rb +40 -0
  10. data/app/models/pgbus/process_entry.rb +9 -0
  11. data/app/models/pgbus/processed_event.rb +9 -0
  12. data/app/models/pgbus/recurring_execution.rb +33 -0
  13. data/app/models/pgbus/recurring_task.rb +42 -0
  14. data/app/models/pgbus/semaphore.rb +29 -0
  15. data/app/views/layouts/pgbus/application.html.erb +1 -0
  16. data/app/views/pgbus/dashboard/_stats_cards.html.erb +9 -1
  17. data/app/views/pgbus/dead_letter/_messages_table.html.erb +55 -18
  18. data/app/views/pgbus/jobs/_enqueued_table.html.erb +46 -8
  19. data/app/views/pgbus/recurring_tasks/_tasks_table.html.erb +79 -0
  20. data/app/views/pgbus/recurring_tasks/index.html.erb +6 -0
  21. data/app/views/pgbus/recurring_tasks/show.html.erb +122 -0
  22. data/config/routes.rb +7 -0
  23. data/lib/active_job/queue_adapters/pgbus_adapter.rb +29 -0
  24. data/lib/generators/pgbus/add_recurring_generator.rb +56 -0
  25. data/lib/generators/pgbus/install_generator.rb +76 -2
  26. data/lib/generators/pgbus/templates/add_recurring_tables.rb.erb +31 -0
  27. data/lib/generators/pgbus/templates/migration.rb.erb +72 -4
  28. data/lib/generators/pgbus/templates/recurring.yml.erb +40 -0
  29. data/lib/generators/pgbus/templates/upgrade_pgmq.rb.erb +30 -0
  30. data/lib/generators/pgbus/upgrade_pgmq_generator.rb +60 -0
  31. data/lib/pgbus/active_job/adapter.rb +0 -3
  32. data/lib/pgbus/active_job/executor.rb +27 -12
  33. data/lib/pgbus/batch.rb +60 -69
  34. data/lib/pgbus/cli.rb +11 -16
  35. data/lib/pgbus/client.rb +25 -7
  36. data/lib/pgbus/concurrency/blocked_execution.rb +32 -37
  37. data/lib/pgbus/concurrency/semaphore.rb +11 -39
  38. data/lib/pgbus/concurrency.rb +10 -2
  39. data/lib/pgbus/configuration.rb +33 -0
  40. data/lib/pgbus/engine.rb +19 -1
  41. data/lib/pgbus/event_bus/handler.rb +4 -14
  42. data/lib/pgbus/instrumentation.rb +29 -0
  43. data/lib/pgbus/pgmq_schema/pgmq_v1.11.0.sql +2123 -0
  44. data/lib/pgbus/pgmq_schema.rb +159 -0
  45. data/lib/pgbus/process/consumer.rb +8 -9
  46. data/lib/pgbus/process/dispatcher.rb +26 -24
  47. data/lib/pgbus/process/heartbeat.rb +15 -23
  48. data/lib/pgbus/process/signal_handler.rb +23 -1
  49. data/lib/pgbus/process/supervisor.rb +51 -2
  50. data/lib/pgbus/process/worker.rb +37 -9
  51. data/lib/pgbus/recurring/already_recorded.rb +7 -0
  52. data/lib/pgbus/recurring/command_job.rb +16 -0
  53. data/lib/pgbus/recurring/config_loader.rb +35 -0
  54. data/lib/pgbus/recurring/schedule.rb +102 -0
  55. data/lib/pgbus/recurring/scheduler.rb +102 -0
  56. data/lib/pgbus/recurring/task.rb +111 -0
  57. data/lib/pgbus/serializer.rb +10 -6
  58. data/lib/pgbus/version.rb +1 -1
  59. data/lib/pgbus/web/data_source.rb +187 -22
  60. data/lib/pgbus.rb +8 -0
  61. data/lib/tasks/pgbus_pgmq.rake +62 -0
  62. metadata +51 -24
  63. data/.bun-version +0 -1
  64. data/.claude/commands/architect.md +0 -100
  65. data/.claude/commands/github-review-comments.md +0 -237
  66. data/.claude/commands/lfg.md +0 -271
  67. data/.claude/commands/review-pr.md +0 -69
  68. data/.claude/commands/security.md +0 -122
  69. data/.claude/commands/tdd.md +0 -148
  70. data/.claude/rules/agents.md +0 -49
  71. data/.claude/rules/coding-style.md +0 -91
  72. data/.claude/rules/git-workflow.md +0 -56
  73. data/.claude/rules/performance.md +0 -73
  74. data/.claude/rules/testing.md +0 -67
  75. data/CLAUDE.md +0 -80
  76. data/CODE_OF_CONDUCT.md +0 -10
  77. data/bun.lock +0 -18
  78. data/docs/README.md +0 -28
  79. data/docs/switch_from_good_job.md +0 -279
  80. data/docs/switch_from_sidekiq.md +0 -226
  81. data/docs/switch_from_solid_queue.md +0 -247
  82. data/package.json +0 -9
  83. data/sig/pgbus.rbs +0 -4
@@ -1,91 +0,0 @@
1
- # Coding Style Rules
2
-
3
- ## File Organization
4
-
5
- **MANY SMALL FILES > FEW LARGE FILES**
6
-
7
- - High cohesion, low coupling
8
- - 200-400 lines typical
9
- - 800 lines maximum per file
10
- - Extract complex logic to dedicated classes
11
- - Organize by concern (client, adapter, event_bus, process, web)
12
-
13
- ## Ruby Style
14
-
15
- ### Classes & Methods
16
-
17
- ```ruby
18
- # Good: Small, focused methods
19
- def execute(message, queue_name)
20
- check_dead_letter(message, queue_name)
21
- deserialize_and_run(message)
22
- archive(message, queue_name)
23
- end
24
-
25
- # Bad: Giant methods doing everything
26
- def process_everything
27
- # 200 lines of code...
28
- end
29
- ```
30
-
31
- ### Error Handling
32
-
33
- ```ruby
34
- # Good: Specific error handling
35
- def execute(message, queue_name)
36
- job = deserialize(message)
37
- job.perform_now
38
- client.archive_message(queue_name, message.msg_id.to_i)
39
- :success
40
- rescue ActiveJob::DeserializationError => e
41
- Pgbus.logger.error { "[Pgbus] Deserialization failed: #{e.message}" }
42
- :failed
43
- rescue StandardError => e
44
- Pgbus.logger.error { "[Pgbus] Job failed: #{e.message}" }
45
- :failed
46
- end
47
-
48
- # Bad: Swallowing errors
49
- def execute(message, queue_name)
50
- deserialize(message).perform_now
51
- rescue StandardError
52
- nil
53
- end
54
- ```
55
-
56
- ### PGMQ Operations
57
-
58
- ```ruby
59
- # Good: All PGMQ access through Client
60
- Pgbus.client.send_message(queue, payload)
61
- Pgbus.client.read_batch(queue, qty: 5)
62
-
63
- # Bad: Direct PGMQ calls
64
- pgmq = PGMQ::Client.new(...)
65
- pgmq.produce("raw_queue_name", data)
66
- ```
67
-
68
- ### Thread Safety
69
-
70
- ```ruby
71
- # Good: Mutex for shared state
72
- @mutex.synchronize { @queues_created[name] = true }
73
-
74
- # Good: Concurrent primitives
75
- @pool = Concurrent::FixedThreadPool.new(threads)
76
-
77
- # Bad: Unsynchronized shared state
78
- @queues_created[name] = true # race condition
79
- ```
80
-
81
- ## Code Quality Checklist
82
-
83
- Before marking work complete:
84
- - [ ] Code is readable and well-named
85
- - [ ] Methods are small (<30 lines ideal, <50 max)
86
- - [ ] Files are focused (<800 lines)
87
- - [ ] No deep nesting (>4 levels)
88
- - [ ] Proper error handling with logging
89
- - [ ] All PGMQ operations through Client
90
- - [ ] Thread safety verified for shared state
91
- - [ ] Rubocop passes
@@ -1,56 +0,0 @@
1
- # Git Workflow Rules
2
-
3
- ## Commit Messages
4
-
5
- Use conventional commits:
6
- - `feat:` - New feature
7
- - `fix:` - Bug fix
8
- - `refactor:` - Code refactoring
9
- - `perf:` - Performance improvement
10
- - `docs:` - Documentation only
11
- - `test:` - Adding/updating tests
12
- - `chore:` - Maintenance tasks
13
- - `ci:` - CI/CD changes
14
-
15
- Format:
16
- ```
17
- feat(scope): brief description
18
-
19
- Longer explanation if needed. Focus on WHY, not WHAT.
20
-
21
- Refs #123
22
- ```
23
-
24
- ## Branch Naming
25
-
26
- - `feature/description` - New features
27
- - `fix/description` - Bug fixes
28
- - `refactor/description` - Refactoring
29
- - `ci/description` - CI changes
30
- - `chore/description` - Maintenance
31
-
32
- ## PR Workflow
33
-
34
- 1. Create branch from `main`
35
- 2. Make focused, atomic commits
36
- 3. Run all validators before pushing
37
- 4. Create PR with description and test plan
38
- 5. Request review
39
- 6. Squash merge when approved
40
-
41
- ## Pre-Commit Checklist
42
-
43
- Run before EVERY commit:
44
- ```bash
45
- bundle exec rubocop # Style
46
- bundle exec rspec <relevant_specs> # Tests
47
- ```
48
-
49
- ## Rules
50
-
51
- - **NEVER** commit directly to `main`
52
- - **NEVER** force push to shared branches
53
- - **ALWAYS** run validators before committing
54
- - **ALWAYS** write meaningful commit messages
55
- - Keep commits small and focused
56
- - One logical change per commit
@@ -1,73 +0,0 @@
1
- # Performance Rules
2
-
3
- ## Context Window Management
4
-
5
- **Critical**: Your context window can shrink significantly with many tools enabled.
6
-
7
- Guidelines:
8
- - Keep under 10 MCPs enabled per project
9
- - Avoid loading large files unnecessarily
10
- - Use targeted searches over broad exploration
11
-
12
- ## PGMQ Performance
13
-
14
- ### Polling vs LISTEN/NOTIFY
15
-
16
- ```ruby
17
- # Good: Use LISTEN/NOTIFY for instant wake-up
18
- pgmq.enable_notify_insert(queue_name, throttle_interval_ms: 250)
19
-
20
- # Polling as fallback only -- not primary mechanism
21
- sleep(config.polling_interval) # only when LISTEN/NOTIFY unavailable
22
- ```
23
-
24
- ### Batch Operations
25
-
26
- ```ruby
27
- # Good: Batch reads for throughput
28
- client.read_batch(queue, qty: idle_threads)
29
-
30
- # Bad: Single reads in a loop
31
- idle_threads.times { client.read_message(queue) }
32
- ```
33
-
34
- ### Connection Pool Sizing
35
-
36
- - Pool size should match worker thread count
37
- - Don't over-provision -- each connection holds PostgreSQL resources
38
- - Use `-> { ActiveRecord::Base.connection.raw_connection }` in Rails to share the pool
39
-
40
- ### Queue Table Performance
41
-
42
- - PGMQ's `q_` tables use `vt ASC` index for fast reads
43
- - `FOR UPDATE SKIP LOCKED` prevents contention between workers
44
- - Archive tables grow unbounded -- consider partitioning for high-volume queues
45
- - Purge processed events table periodically (idempotency dedup)
46
-
47
- ## Worker Performance
48
-
49
- ### Memory Management
50
-
51
- ```ruby
52
- # Good: Recycling prevents memory bloat
53
- max_jobs_per_worker: 10_000
54
- max_memory_mb: 512
55
- max_worker_lifetime: 3600
56
-
57
- # Bad: Workers running forever without limits
58
- # (this is solid_queue's problem)
59
- ```
60
-
61
- ### Thread Pool Sizing
62
-
63
- - Match threads to expected concurrency
64
- - More threads = more PGMQ connections needed
65
- - Monitor with `pool.queue_length` vs `pool.max_length`
66
-
67
- ## Performance Checklist
68
-
69
- - [ ] LISTEN/NOTIFY enabled for active queues
70
- - [ ] Batch reads used where possible
71
- - [ ] Connection pool sized appropriately
72
- - [ ] Worker recycling configured
73
- - [ ] No N+1 queries in dashboard DataSource
@@ -1,67 +0,0 @@
1
- # Testing Rules
2
-
3
- ## TDD Workflow
4
-
5
- Follow RED -> GREEN -> REFACTOR:
6
-
7
- 1. **RED**: Write a failing test first
8
- 2. **GREEN**: Write minimal code to pass
9
- 3. **REFACTOR**: Improve code while keeping tests green
10
-
11
- ## Coverage Requirements
12
-
13
- - **80% minimum** for all code
14
- - **100% required** for:
15
- - ActiveJob adapter (enqueue, execute, DLQ routing)
16
- - Event bus (handler, registry, publisher)
17
- - Client (PGMQ wrapper)
18
- - Configuration
19
- - Web::DataSource
20
- - Web::Authentication
21
-
22
- ## Test Type Preference
23
-
24
- | Feature involves | Use |
25
- |-----------------|-----|
26
- | Configuration / Event / Serializer | Unit spec |
27
- | Client / PGMQ interaction | Unit spec with mocked PGMQ |
28
- | ActiveJob adapter | Integration spec |
29
- | Worker / Supervisor | Unit spec (process behavior) |
30
- | Dashboard DataSource | Unit spec with mocked client |
31
- | Dashboard Authentication | Unit spec |
32
- | Helper formatting | Unit spec |
33
-
34
- ## RSpec Conventions
35
-
36
- ```ruby
37
- # Use let for setup
38
- let(:config) { Pgbus::Configuration.new }
39
-
40
- # Use subject for the thing being tested
41
- subject(:data_source) { described_class.new(client: mock_client) }
42
-
43
- # Use contexts for scenarios
44
- context "when queue exists" do
45
- before { allow(mock_client).to receive(:metrics).and_return(metrics) }
46
- it { expect(result).not_to be_nil }
47
- end
48
-
49
- # Use doubles for PGMQ (avoid DB dependency in unit tests)
50
- let(:mock_client) { double("Pgbus::Client", pgmq: double("pgmq")) }
51
- ```
52
-
53
- ## PGMQ in Tests
54
-
55
- - Mock `Pgbus::Client` in unit tests -- PGMQ requires a real PostgreSQL connection
56
- - Use doubles for PGMQ message objects (they're `Data.define` value objects)
57
- - Test configuration without PGMQ connection
58
- - Integration tests (when added) will need a real PostgreSQL with PGMQ extension
59
-
60
- ## Test Checklist
61
-
62
- - [ ] Tests written BEFORE implementation
63
- - [ ] All tests pass: `bundle exec rspec`
64
- - [ ] Coverage meets requirements
65
- - [ ] No skipped tests without reason
66
- - [ ] Edge cases covered (nil messages, expired VT, max retries exceeded)
67
- - [ ] Error paths tested (connection failures, deserialization errors)
data/CLAUDE.md DELETED
@@ -1,80 +0,0 @@
1
- # Pgbus
2
-
3
- PostgreSQL-native job processing and event bus for Rails, built on PGMQ.
4
-
5
- ## Tech Stack
6
-
7
- - **Ruby**: >= 3.3 | **Rails**: >= 7.1
8
- - **Transport**: pgmq-ruby (PGMQ PostgreSQL extension)
9
- - **Concurrency**: concurrent-ruby
10
- - **Autoloading**: zeitwerk
11
- - **Testing**: RSpec
12
- - **Linting**: RuboCop
13
-
14
- ## Critical Rules
15
-
16
- ### Never Do
17
- 1. **NO direct PGMQ calls** — always go through `Pgbus::Client`
18
- 2. **NO hardcoded queue names** — use `config.queue_name()`
19
- 3. **NO raw SQL in dashboard** — use `Web::DataSource`
20
- 4. **NO `Marshal.load`** — JSON serialization only
21
- 5. **NO unsynchronized shared state** — use Mutex or Concurrent primitives
22
- 6. **NO swallowing errors** — log via `Pgbus.logger`, track in `pgbus_failed_events`
23
-
24
- ### Always Do
25
- 1. **TDD**: Write tests BEFORE implementation
26
- 2. **Worker recycling**: Configure `max_jobs`, `max_memory_mb`, `max_lifetime`
27
- 3. **Dead letter routing**: Check `read_ct` > `max_retries`
28
- 4. **LISTEN/NOTIFY**: Use `enable_notify_insert` for instant wake-up
29
- 5. **Queue prefix**: All queues through `config.queue_name()`
30
- 6. **Visibility timeout**: Always pass `vt:` parameter on reads
31
-
32
- ## Commands
33
-
34
- ```bash
35
- bundle exec rspec # Run tests
36
- bundle exec rubocop # Lint
37
- bundle exec rake # Both
38
- ```
39
-
40
- ## Slash Commands
41
-
42
- | Command | Purpose |
43
- |---------|---------|
44
- | `/lfg` | Full autonomous workflow: branch → understand → explore → plan → TDD → verify → PR |
45
- | `/github-review-comments` | Process unresolved PR review comments |
46
- | `/review-pr` | Review a PR for pattern compliance |
47
- | `/tdd` | Enforce RED → GREEN → REFACTOR cycle |
48
- | `/security` | Security audit (PGMQ ops, connections, auth, deserialization) |
49
- | `/architect` | Coordinate multi-layer development |
50
-
51
- ## Architecture
52
-
53
- ```
54
- Layer 6: Dashboard app/controllers/pgbus/, app/views/pgbus/
55
- Layer 5: CLI lib/pgbus/cli.rb
56
- Layer 4: Process Model lib/pgbus/process/ (supervisor, worker, dispatcher, consumer)
57
- Layer 3: Event Bus lib/pgbus/event_bus/ (publisher, subscriber, registry, handler)
58
- Layer 2: ActiveJob lib/pgbus/active_job/ (adapter, executor)
59
- Layer 1: Client lib/pgbus/client.rb (PGMQ wrapper)
60
- Layer 0: Config lib/pgbus/configuration.rb, config_loader.rb
61
- ```
62
-
63
- ## Key Design Decisions
64
-
65
- - Worker recycling via `max_jobs_per_worker`, `max_memory_mb`, `max_worker_lifetime` — fixes solid_queue's memory leak problem
66
- - LISTEN/NOTIFY via PGMQ's `enable_notify_insert` for instant wake-up (polling as fallback only)
67
- - Dead letter queues: after `max_retries` failed reads (tracked by PGMQ's `read_ct`), move to `_dlq` queue
68
- - Idempotent events: `pgbus_processed_events` table with (event_id, handler_class) unique index
69
- - Dashboard via Tailwind CDN + Turbo CDN — zero npm dependency
70
-
71
- ## Queue Naming
72
-
73
- All PGMQ queues are prefixed: `{queue_prefix}_{name}` (default: `pgbus_default`).
74
- DLQ queues append `_dlq` suffix.
75
-
76
- ## More Documentation
77
-
78
- See `.claude/` directory:
79
- - `commands/` — Slash command definitions
80
- - `rules/` — Coding style, git workflow, testing, agents, performance, security
data/CODE_OF_CONDUCT.md DELETED
@@ -1,10 +0,0 @@
1
- # Code of Conduct
2
-
3
- "pgbus" follows [The Ruby Community Conduct Guideline](https://www.ruby-lang.org/en/conduct) in all "collaborative space", which is defined as community communications channels (such as mailing lists, submitted patches, commit comments, etc.):
4
-
5
- * Participants will be tolerant of opposing views.
6
- * Participants must ensure that their language and actions are free of personal attacks and disparaging personal remarks.
7
- * When interpreting the words and actions of others, participants should always assume good intentions.
8
- * Behaviour which can be reasonably considered harassment will not be tolerated.
9
-
10
- If you have any concerns about behaviour within this project, please contact us at ["mikael@mhenrixon.com"](mailto:"mikael@mhenrixon.com").
data/bun.lock DELETED
@@ -1,18 +0,0 @@
1
- {
2
- "lockfileVersion": 1,
3
- "configVersion": 0,
4
- "workspaces": {
5
- "": {
6
- "devDependencies": {
7
- "playwright": "^1.50.0",
8
- },
9
- },
10
- },
11
- "packages": {
12
- "fsevents": ["fsevents@2.3.2", "", { "os": "darwin" }, "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="],
13
-
14
- "playwright": ["playwright@1.58.2", "", { "dependencies": { "playwright-core": "1.58.2" }, "optionalDependencies": { "fsevents": "2.3.2" }, "bin": "cli.js" }, "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A=="],
15
-
16
- "playwright-core": ["playwright-core@1.58.2", "", { "bin": "cli.js" }, "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg=="],
17
- }
18
- }
data/docs/README.md DELETED
@@ -1,28 +0,0 @@
1
- # Pgbus Documentation
2
-
3
- ## Migration guides
4
-
5
- Switching to Pgbus from another job backend? These guides cover what changes, what stays the same, and what to watch out for.
6
-
7
- | Guide | Backend | Key differences |
8
- |-------|---------|-----------------|
9
- | [Switch from Sidekiq](switch_from_sidekiq.md) | Sidekiq (+ Pro/Enterprise) | Remove Redis, convert native workers to ActiveJob, replace middleware with callbacks |
10
- | [Switch from SolidQueue](switch_from_solid_queue.md) | SolidQueue | Similar architecture (PostgreSQL + `SKIP LOCKED`), swap config format, gain LISTEN/NOTIFY + worker recycling |
11
- | [Switch from GoodJob](switch_from_good_job.md) | GoodJob | Both PostgreSQL-native with LISTEN/NOTIFY, swap advisory locks for PGMQ visibility timeouts, gain worker recycling |
12
-
13
- ## Feature comparison
14
-
15
- | Feature | Sidekiq | SolidQueue | GoodJob | Pgbus |
16
- |---------|---------|------------|---------|-------|
17
- | Infrastructure | Redis | PostgreSQL | PostgreSQL | PostgreSQL (PGMQ) |
18
- | ActiveJob adapter | Yes | Yes | Yes | Yes |
19
- | Bulk enqueue | No | Yes | Yes | Yes |
20
- | LISTEN/NOTIFY | N/A | No (polling only) | Yes | Yes |
21
- | Dead letter queues | No (retries only) | No | No | Yes |
22
- | Worker recycling | No | No | No | Yes |
23
- | Event bus | No | No | No | Yes |
24
- | Idempotent events | No | No | No | Yes |
25
- | Concurrency controls | Enterprise | `limits_concurrency` | `good_job_control_concurrency_with` | `Pgbus::Concurrency` |
26
- | Recurring/cron jobs | `sidekiq-cron` gem | `config/recurring.yml` | `config.good_job.cron` | Planned |
27
- | Batches | Pro | No | `GoodJob::Batch` | `Pgbus::Batch` |
28
- | Web dashboard | `Sidekiq::Web` | Mission Control | `GoodJob::Engine` | `Pgbus::Engine` |