{"id":44502992,"url":"https://github.com/amkisko/good_job.ex","last_synced_at":"2026-02-13T07:58:16.554Z","repository":{"id":332062687,"uuid":"1128355482","full_name":"amkisko/good_job.ex","owner":"amkisko","description":"Concurrent, PostgreSQL-based job queue for Elixir. Port of Ruby GoodJob with full compatibility.","archived":false,"fork":false,"pushed_at":"2026-01-12T07:56:38.000Z","size":462,"stargazers_count":0,"open_issues_count":3,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-01-12T17:52:01.933Z","etag":null,"topics":["activejob","background-jobs","elixir","goodjob","job-queue","ruby-on-rails"],"latest_commit_sha":null,"homepage":"https://hex.pm/packages/good_job","language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/amkisko.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE.md","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":"GOVERNANCE.md","roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":"amkisko"}},"created_at":"2026-01-05T14:13:52.000Z","updated_at":"2026-01-12T07:56:42.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/amkisko/good_job.ex","commit_stats":null,"previous_names":["amkisko/good_job.ex"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/amkisko/good_job.ex","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/amkisko%2Fgood_job.ex","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/amkisko%2Fgood_job.ex/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/amkisko%2Fgood_job.ex/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/amkisko%2Fgood_job.ex/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/amkisko","download_url":"https://codeload.github.com/amkisko/good_job.ex/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/amkisko%2Fgood_job.ex/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29399426,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-13T06:24:03.484Z","status":"ssl_error","status_checked_at":"2026-02-13T06:23:12.830Z","response_time":78,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["activejob","background-jobs","elixir","goodjob","job-queue","ruby-on-rails"],"created_at":"2026-02-13T07:58:15.658Z","updated_at":"2026-02-13T07:58:16.534Z","avatar_url":"https://github.com/amkisko.png","language":"Elixir","funding_links":["https://github.com/sponsors/amkisko"],"categories":[],"sub_categories":[],"readme":"# good_job for elixir\n\n[![Hex Version](https://img.shields.io/hexpm/v/good_job.svg)](https://hex.pm/packages/good_job)\n[![Hex Docs](https://img.shields.io/badge/hex-docs-lightgreen.svg)](https://hexdocs.pm/good_job)\n[![Test Status](https://github.com/amkisko/good_job.ex/actions/workflows/test.yml/badge.svg)](https://github.com/amkisko/good_job.ex/actions/workflows/test.yml)\n[![codecov](https://codecov.io/gh/amkisko/good_job.ex/graph/badge.svg?token=Q0WMBFW7IU)](https://codecov.io/gh/amkisko/good_job.ex)\n\nConcurrent, Postgres-based job queue backend for Elixir. Provides attribute-based job execution with PostgreSQL advisory locks to ensure run-once safety. Works with Phoenix and can be used standalone in other Elixir frameworks or plain Elixir applications.\n\n**Port of [GoodJob](https://github.com/bensheldon/good_job)** - This Elixir implementation is a port of the excellent Ruby GoodJob gem by [Ben Sheldon](https://github.com/bensheldon), designed for maximum compatibility with the original, to make it possible running both Ruby and Elixir applications with the same database. It fully implements the protocol that respects GoodJob and ActiveJob conventions. This implementation allows moving forward to other languages and frameworks that implement the same protocol.\n\n**Need Ruby compatibility details?** See [COMPATIBILITY.md](COMPATIBILITY.md) for compatibility information.\n\n**Migrating from the Ruby version?** See [MIGRATION_FROM_RUBY.md](MIGRATION_FROM_RUBY.md) for a detailed guide.\n\n**Using without Phoenix?** See [STANDALONE.md](STANDALONE.md) for standalone usage.\n\n## Features\n\n- **PostgreSQL Backend** - Relies upon Postgres integrity, session-level Advisory Locks to provide run-once safety\n- **LISTEN/NOTIFY** - Uses PostgreSQL LISTEN/NOTIFY to reduce queuing latency\n- **Multiple Execution Modes** - Inline (testing), async (development), external (production)\n- **Queue Management** - Support for ordered queues, queue-specific concurrency, and semicolon-separated pools\n- **Cron Jobs** - Scheduled jobs with cron expressions\n- **Batch Operations** - Batch job tracking and callbacks\n- **Concurrency Controls** - Per-key concurrency limits and throttling\n- **Retry Mechanisms** - Automatic retries with exponential backoff\n- **Plugins System** - Extensible plugin architecture for custom functionality\n- **Labels/Tags** - Tag jobs for filtering and analytics\n- **Web Dashboard** - Phoenix LiveView dashboard for monitoring and management\n- **Ruby-Compatible** - Fully aligned with Ruby GoodJob configuration and database schema\n- **Comprehensive Instrumentation** - Telemetry events for monitoring and metrics\n- **Production Ready** - Designed for applications that enqueue 1-million jobs/day and more\n\n## Installation\n\nAdd `good_job` to your list of dependencies in `mix.exs`:\n\n```elixir\ndef deps do\n  [\n    {:good_job, \"~\u003e 0.1.1\"}\n  ]\nend\n```\n\nFor Phoenix LiveView dashboard support, also ensure you have:\n\n```elixir\n{:phoenix_live_view, \"~\u003e 0.20\"}\n```\n\n## Quick Start\n\n### 1. Install the Database Migrations\n\n```bash\nmix good_job.install\nmix ecto.migrate\n```\n\n### 2. Configure GoodJob\n\n```elixir\n# config/config.exs\nconfig :good_job,\n  repo: MyApp.Repo,\n  execution_mode: :external,  # :inline (test), :async (dev), :external (prod)\n  queues: \"*\",\n  max_processes: 5\n```\n\n### 3. Start GoodJob in Your Application\n\n```elixir\n# lib/my_app/application.ex\ndefmodule MyApp.Application do\n  use Application\n\n  def start(_type, _args) do\n    children = [\n      MyApp.Repo,\n      GoodJob.Application\n    ]\n\n    Supervisor.start_link(children, strategy: :one_for_one)\n  end\nend\n```\n\n### 4. Define and Enqueue a Job\n\n```elixir\ndefmodule MyApp.MyJob do\n  use GoodJob.Job\n\n  @impl GoodJob.Behaviour\n  def perform(%{data: data}) do\n    # Your job logic here\n    IO.puts(\"Processing: #{inspect(data)}\")\n    :ok\n  end\nend\n\n# Enqueue the job\nMyApp.MyJob.enqueue(%{data: \"hello\"})\n```\n\n## Usage\n\n### Basic Job\n\n```elixir\ndefmodule MyApp.EmailJob do\n  use GoodJob.Job, queue: \"emails\", priority: 1\n\n  @impl GoodJob.Behaviour\n  def perform(%{to: to, subject: subject, body: body}) do\n    MyApp.Mailer.send(to: to, subject: subject, body: body)\n    :ok\n  end\nend\n\nMyApp.EmailJob.enqueue(%{to: \"user@example.com\", subject: \"Hello\", body: \"World\"})\n```\n\n### Labeled Jobs (Tags)\n\n```elixir\ndefmodule MyApp.TaggedJob do\n  use GoodJob.Job, tags: [\"billing\", \"priority\"]\n\n  @impl GoodJob.Behaviour\n  def perform(_args), do: :ok\nend\n\nMyApp.TaggedJob.enqueue(%{user_id: 123}, tags: [\"vip\"])\n```\n\n### Job with Retries\n\n```elixir\ndefmodule MyApp.ApiJob do\n  use GoodJob.Job, max_attempts: 10\n\n  @impl GoodJob.Behaviour\n  def perform(%{url: url}) do\n    case HTTPoison.get(url) do\n      {:ok, response} -\u003e {:ok, response.body}\n      {:error, reason} -\u003e {:error, reason}  # Will retry\n    end\n  end\n\n  def backoff(attempt) do\n    GoodJob.Backoff.exponential(attempt, max: 300)\n  end\nend\n```\n\n### Cron Jobs\n\n```elixir\n# config/config.exs\nconfig :good_job,\n  enable_cron: true,\n  cron: %{\n    cleanup: %{\n      cron: \"0 2 * * *\",  # Every day at 2 AM\n      class: MyApp.CleanupJob,\n      args: %{},\n      queue: \"default\"\n    }\n  }\n```\n\n### Batch Jobs\n\n```elixir\nbatch = GoodJob.Batch.create(%{\n  description: \"Process users\",\n  on_finish: \"MyApp.BatchFinishedJob\"\n})\n\nUser\n|\u003e Repo.all()\n|\u003e Enum.each(fn user -\u003e\n  ProcessUserJob.enqueue(%{user_id: user.id}, batch_id: batch.id)\nend)\n```\n\n### Concurrency Controls\n\n```elixir\ndefmodule MyApp.UserJob do\n  use GoodJob.Job\n\n  @impl GoodJob.Behaviour\n  def perform(%{user_id: user_id}) do\n    # Process user\n  end\n\n  def good_job_concurrency_config do\n    [\n      key: fn %{user_id: user_id} -\u003e \"user_#{user_id}\" end,\n      limit: 5,\n      perform_throttle: {10, 60} # max 10 executions per 60s for the key\n    ]\n  end\nend\n```\n\n### Throttling Only (No Concurrency Limit)\n\n```elixir\ndefmodule MyApp.ThrottledJob do\n  use GoodJob.Job\n\n  @impl GoodJob.Behaviour\n  def perform(_args), do: :ok\n\n  def good_job_concurrency_config do\n    [\n      key: fn _args -\u003e \"global\" end,\n      enqueue_throttle: {100, 60}\n    ]\n  end\nend\n```\n\n## Queue Configuration\n\n```elixir\n# Process all queues\nqueues: \"*\"\n\n# Comma-separated queues (legacy format)\nqueues: \"queue1:5,queue2:10\"\n\n# Semicolon-separated pools (Ruby GoodJob format)\nqueues: \"queue1:2;queue2:1;*\"\n\n# Ordered queues (process in order)\nqueues: \"+queue1,queue2:5\"\n\n# Excluded queues\nqueues: \"-queue1,queue2:2\"\n```\n\n**Note:** Only `*` is supported as a wildcard (standalone, not in patterns like `queue*`).\n\n## Execution Modes\n\n- **`:inline`** - Execute immediately in current process (test/dev only)\n- **`:async`** - Execute in processes within web server process only\n- **`:external`** - Enqueue only, requires separate worker process (production default)\n\n## Configuration\n\n```elixir\n# config/config.exs\nconfig :good_job,\n  repo: MyApp.Repo,\n  execution_mode: :external,\n  queues: \"*\",\n  max_processes: 5,\n  poll_interval: 10,\n  enable_listen_notify: true,\n  enable_cron: false,\n  cleanup_discarded_jobs: true,\n  cleanup_preserved_jobs_before_seconds_ago: 1_209_600  # 14 days\n```\n\nSee [config/prod.exs.example](config/prod.exs.example) for a complete configuration example with all available options.\n\n## Web Dashboard\n\n### Phoenix LiveDashboard Integration (Recommended)\n\n```elixir\n# lib/my_app_web/router.ex\nimport Phoenix.LiveDashboard.Router\n\nlive_dashboard \"/dashboard\",\n  metrics: MyAppWeb.Telemetry,\n  additional_pages: [\n    good_job: GoodJob.Web.LiveDashboardPage\n  ]\n```\n\n### Standalone Dashboard\n\n```elixir\n# lib/my_app_web/router.ex\nscope \"/good_job\" do\n  pipe_through :browser\n  live \"/\", GoodJob.Web.LiveDashboard, :index\nend\n```\n\n**Note**: The web dashboard requires Phoenix. For monitoring without Phoenix, see [STANDALONE.md](STANDALONE.md).\n\n## Testing\n\n```elixir\n# config/test.exs\nconfig :good_job,\n  execution_mode: :inline\n\n# In your tests\nimport GoodJob.Testing\n\ntest \"job is enqueued\" do\n  MyApp.MyJob.enqueue(%{data: \"test\"})\n  assert_enqueued(MyApp.MyJob, %{data: \"test\"})\nend\n```\n\n## Requirements\n\n- Elixir \u003e= 1.18\n- PostgreSQL \u003e= 12\n- Ecto \u003e= 3.0\n- Phoenix \u003e= 1.7 (optional, for Phoenix integration)\n- Phoenix LiveView \u003e= 0.20 (optional, for LiveView dashboard)\n\n**Note**: GoodJob can be used without Phoenix! See [STANDALONE.md](STANDALONE.md).\n\n## Examples\n\nComplete working examples are available in the [`examples/`](https://github.com/amkisko/good_job.ex/tree/main/examples) directory:\n\n- **[habit_tracker](https://github.com/amkisko/good_job.ex/tree/main/examples/habit_tracker)** - A full Phoenix application demonstrating GoodJob integration with LiveView dashboard, cron jobs, and batch operations\n- **[monorepo_example](https://github.com/amkisko/good_job.ex/tree/main/examples/monorepo_example)** - A monorepo setup showing Ruby and Elixir applications sharing the same GoodJob database\n\nSee [examples/README.md](examples/README.md) for more details.\n\n## Contributing\n\nBug reports and pull requests are welcome on GitHub at https://github.com/amkisko/good_job.ex\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.\n\n## Credits\n\nThis Elixir implementation is a port of [GoodJob](https://github.com/bensheldon/good_job) by [Ben Sheldon](https://github.com/bensheldon). We are grateful for the excellent design and implementation of the original Ruby version, which served as the foundation for this port.\n\n## License\n\nThe library is available as open source under the terms of the [MIT License](LICENSE.md).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Famkisko%2Fgood_job.ex","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Famkisko%2Fgood_job.ex","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Famkisko%2Fgood_job.ex/lists"}