DungBeetle Documentation

repository·master·Indexed 23 days ago

https://github.com/zerodha/dungbeetle

A lightweight, single-binary distributed job server for queuing and asynchronously executing large SQL read jobs. DungBeetle offloads heavy reporting queries from primary databases to a worker system and stores results in ephemeral, dedicated tables in a cache database (MySQL or PostgreSQL) for fast retrieval. It features a Go HTTP API client, support for goyesql formatted SQL tasks, and scalable worker configurations using Redis for job queuing and state management.

Tokens
4.6K
Snippets
12
Records
38
Agent score
79%

What's inside DungBeetle

  1. What is DungBeetle?

    master

    DungBeetle is a lightweight, single-binary distributed job server designed for queuing and asynchronously executing large numbers of SQL read jobs (such as reports) against SQL databases.

    When jobs are executed, the results are written to separate ephemeral results databases where each job's results are stored in its own dedicated table. This architecture enables faster retrieval and prevents heavy report generation from overloading primary source databases.

  2. How Tasks, Jobs, and Results work together

    master

    DungBeetle operates using three core abstractions:

    1. Task: A named SQL query loaded from .sql files using the goyesql format. Tasks are registered on server startup and can be configured with specific databases, queues, or result backends using special tags.
    2. Job: An instance of a Task that has been queued for execution. Each job has a unique (or user-provided) job_id used to track its status via HTTP APIs.
    3. Results: The output of a completed job. Results are written to a result backend (MySQL or PostgreSQL) into a new table named after the job_id. The schema of this table is automatically generated from the query results, mapping fields to types like BIGINT, DECIMAL, TIMESTAMP, DATE, BOOLEAN, or TEXT.
    -- Example Task in queries.sql
    -- name: get_profit_entries
    -- db: db0, other_db
    -- queue: myqueue
    -- results: my_res_db
    SELECT * FROM entries WHERE user_id = ?;
  3. Scale DungBeetle with multiple queues and workers

    master

    You can scale DungBeetle by running multiple workers across different machines using different queues and concurrency levels. This allows you to isolate jobs by priority (e.g., a high-priority queue with many workers and a low-priority queue with fewer workers).

    Key behaviors:

    • Queue Routing: A job posted to any instance will be routed to the correct worker based on the queue parameter.
    • Worker-only mode: Instances do not need to expose the HTTP service if they are only processing jobs; use the --worker-only flag for these instances.
    • Shared Backend: All instances must connect to the same broker backend to function as a single distributed system.
    # Run a high-priority worker with 30 concurrency
    dungbeetle --config /path/to/config.toml --sql-directory /path/to/sql/dir \
    	--queue "high_priority" \
        --worker-name "high_priority_worker" \
        --worker-concurrency 30
    
    # Run a low-priority worker with 5 concurrency in worker-only mode
    dungbeetle --config /path/to/config.toml --sql-directory /path/to/sql/dir \
    	--queue "low_priority" \
        --worker-name "low_priority_worker" \
        --worker-concurrency 5 \
        --worker-only
  4. Define SQL Tasks using goyesql format

    master

    Tasks are defined in .sql files. Each task must have a -- name: <name> header. You can use special tags to configure task behavior:

    • -- db: <db_names>: Specifies which databases to use. If multiple are listed, the task runs against a random one unless a specific db is provided in the API request.
    • -- queue: <queue_name>: Routes the task to a specific queue.
    • -- results: <results_db_name>: Specifies the results backend.
    • -- raw: 1: Prevents the server from preparing the statement (useful for complex queries).

    Placeholders: Use ? for MySQL or $1, $2 ... for PostgreSQL.

    -- name: get_profit_summary
    SELECT SUM(amount) AS total, entry_date FROM entries WHERE user_id = ? GROUP BY entry_date;
    
    -- name: get_profit_entries_by_date
    -- raw: 1
    SELECT * FROM entries WHERE user_id = ? AND timestamp > ? and timestamp < ?;
  5. Start the DungBeetle server

    master

    To start the server, provide the path to your configuration file and the directory containing your .sql task files.

    By default, this starts a set of workers listening on a default queue and an HTTP service on http://127.0.0.1:6060. To run as a worker only (without the HTTP control interface), use the --worker-only flag.

    dungbeetle --config /path/to/config.toml --sql-directory /path/to/your/sql/queries
  6. Understand the HTTP response format

    master

    All successful and error responses from the Dungbeetle HTTP server follow a standard JSON envelope structure defined by models.HTTPResp.

    Success Response:

    {
      "status": "success",
      "data": <response_data>
    }

    Error Response:

    {
      "status": "error",
      "message": "error message details"
    }
  7. Generate a new sample config.toml

    master

    If you are setting up DungBeetle for the first time, you can generate a template configuration file using the --new-config flag. This will create a config.toml file based on the internal config.sample.toml.

    Note: If a config.toml already exists in the current directory, the command will fail to prevent overwriting your existing configuration. You must remove the existing file before regenerating.

  8. Check a job's status

    master

    Poll the /jobs/{jobID} endpoint to check if a job is finished and to retrieve its results.

    In the response, the Results field indicates the number of rows generated by the query.

    $ curl localhost:6060/jobs/myjob
    {"status":"success","data":{"job_id":"myjob","status":"SUCCESS","results":[{"Type":"int64","Value":2}],"error":""}}
  9. Schedule a single job

    master

    Send a POST request to /tasks/{taskName}/jobs with a JSON body to queue a task.

    Job Parameters:

    • job_id (string, optional): Alphanumeric ID. If omitted, one is generated. Non-unique IDs allow preventing duplicate concurrent requests for the same report.
    • queue (string, optional): Specific queue to route the job to.
    • eta (string, optional): Start timestamp (yyyy-mm-dd hh:mm:ss).
    • retries (int, optional): Number of retries on failure (default 0).
    • args[] (array of strings, optional): Positional arguments for the SQL query.
    $ curl localhost:6060/tasks/get_profit_entries_by_date/jobs -H "Content-Type: application/json" -X POST --data '{"job_id": "myjob", "args": ["USER1", "2017-12-01", "2017-01-01"]}'
  10. Schedule a group of jobs

    master

    Use the /groups endpoint to schedule multiple jobs that run concurrently. You can poll the group status to determine when all jobs in the group have finished.

    Group Parameters:

    • group_id (string, optional): Alphanumeric ID for the group.
    • concurrency (int, optional): Number of jobs to run concurrently in the group.
    • jobs (array, required): A list of job objects, each containing task, job_id, and args.
    $ curl localhost:6060/groups -H "Content-Type: application/json" -X POST --data '{"group_id": "mygroup", "concurrency": 3, "jobs": [{"job_id": "myjob", "task": "get_profit_entries_by_date", "args": ["USER1", "2017-12-01", "2017-01-01"]}, {"job_id": "myjob2", "task": "get_profit_entries_by_date", "args": ["USER1", "2017-12-01", "2017-01-01"]}]'
  11. Submit jobs to specific queues via HTTP API

    master

    You can route jobs to specific queues by including the queue key in the JSON payload when hitting the task endpoint.

    Endpoint pattern: POST /tasks/{task_name}/jobs

    Example commands:

    # Send a job to the high priority queue with arguments
    curl localhost:6060/tasks/get_profit_entries_by_date/jobs -H "Content-Type: application/json" --data '{"job_id": "myjob", "queue": "high_priority", "args": ["USER1", "2017-12-01", "2017-01-01"]}'
    
    # Send a job to the low priority queue
    curl localhost:6060/tasks/get_profit_entries_by_date/jobs -H "Content-Type: application/json" --data '{"job_id": "myjob", "queue": "low_priority"}'
    # Send a job to the high priority queue.
    $ curl localhost:6060/tasks/get_profit_entries_by_date/jobs -H "Content-Type: application/json" --data '{"job_id": "myjob", "queue": "high_priority", "args": ["USER1", "2017-12-01", "2017-01-01"]}'
    
    # Send another job to the low priority queue.
    $ curl localhost:6060/tasks/get_profit_entries_by_date/jobs -H "Content-Type: application/json" --data '{"job_id": "myjob", "queue": "low_priority"}'