MRQ Documentation

repository·master·Indexed 19 days ago

https://github.com/pricingassistant/mrq

MRQ is an opinionated, distributed task queue for Python that uses MongoDB for job and log storage and Redis for queue management. It provides tools for asynchronous task execution via mrq-worker, synchronous execution via mrq-run, and a monitoring interface via mrq-dashboard. The library supports scheduled tasks, timed set queues, and high-throughput IO-bound processing using Gevent greenlets.

Tokens
15.3K
Snippets
53
Records
77
Agent score
67%

What's inside MRQ

  1. What is MRQ?

    master
    MRQ is a distributed task queue for Python designed to be simple and easy to extend, similar to RQ, while maintaining performance levels close to Celery. It is built on top of MongoDB, Redis, and Gevent. It is specifically optimized for heterogeneous workloads containing both IO-bound and CPU-bound tasks.
  2. Key features of MRQ

    master

    MRQ provides several features for managing distributed tasks:

    • Gevent worker: Enables parallel execution of IO-bound tasks within a single UNIX process for high throughput.
    • Dashboard: Provides visibility and control over queued jobs, current jobs, and worker status.
    • Per-job logs: Allows viewing the log output of individual tasks via the dashboard.
    • Job management: Supports retrying, requeueing, and cancelling jobs via code or the dashboard.
    • Job routing: Supports default queues, timeouts, and TTL (Time To Live) values per job.
    • Builtin scheduler: Allows scheduling tasks by interval or specific time of day.
    • Strategies: Supports sequential or parallel dequeue orders, and a 'burst mode' for batch jobs.
    • Subqueues: Supports dequeuing multiple sub-queues using auto-discovery from the worker side.
    • Debugging tools: Includes Greenlet tracing for CPU-intensive jobs and an integrated memory leak debugger using objgraph.
  3. What is a Worker in MRQ

    master

    A Worker is the unit of processing in MRQ responsible for dequeuing jobs and executing them.

    Key characteristics:

    • Queue Subscription: A worker is started with a specific list of queues to listen to, processed in the order they are provided.
    • Concurrency: A single worker can be configured with concurrency options to handle multiple jobs in parallel using multiple processes and/or multiple greenlets. Even when using multiple processes/greenlets, the entire group is referred to as a single 'worker'.
    • Dispatch Mechanism: When concurrency is enabled, the worker polls for waiting jobs and dispatches them to its internal pool of processes or greenlets (e.g., a single Python process managing a pool of greenlets).
  4. Understand Timed Set Execution Behavior

    master

    When using timed set queues, the execution of tasks is tied to their scheduled timestamp.

    Important Timing Note: If you enqueue tasks for the future but delay starting your worker, the worker will immediately execute any tasks whose scheduled execution time has already passed.

    For example, if you enqueue tasks every 10 seconds but wait 20 seconds before starting the worker, the first two tasks will be executed immediately because their scheduled time is in the past. Subsequent tasks will then follow the intended interval relative to their scheduled times.

  5. Use raw queues for high performance

    master

    Raw queues prioritize performance by storing only serialized task parameters in Redis. Tasks are only inserted into MongoDB after being dequeued by a worker. This reduces visibility for individual queued jobs but increases throughput.

    There are four types of raw queues determined by their name suffix:

    • _raw: Simplest type, stored in a Redis LIST.
    • _set: Stored in a Redis SET. Supports "unique" tasks (only one instance of a specific task/parameter pair can be queued at a time).
    • _sorted_set: Stored in a Redis ZSET. Allows ordering/re-ordering tasks. Like _set, task parameters are unique.
    • _timed_set: A special _sorted_set where tasks are sorted by a UNIX timestamp, allowing you to schedule tasks for a specific time in the future.
  6. Understand the difference between Tasks and Jobs

    master

    MRQ distinguishes between the definition of work and its execution:

    • Task: A Python class (subclassing mrq.task.Task) that wraps a unit of processing. It defines what to do.
    • Job: An instance of a Task being executed. A Job is linked to a specific Task via its path, has specific parameters, and is queued in a Queue to be processed by a Worker. It tracks execution metadata like status and tracebacks.

    A Task can invoke other tasks either synchronously or asynchronously by queuing them as new Jobs.

  7. Design tasks to be reentrant

    master
    Because MRQ workers can be interrupted at any time (e.g., due to server restarts or process termination), all tasks should be designed to be reentrant. A reentrant task is one that can be interrupted mid-execution and safely called again before the previous invocation completes. MRQ handles interruptions by automatically requeueing jobs, but your task logic must be able to handle being restarted without causing side effects or data corruption.
  8. Use the MRQ scheduler for recurring tasks

    master

    MRQ includes a built-in scheduler that allows you to run tasks at regular intervals (e.g., every X units of time), similar to how crontab works.

    If you need to schedule a job to run at a specific, precise time in the future rather than on a recurring interval, use timed_set available via raw queues.

  9. Use regular queues in MRQ

    master

    Regular queues store tasks directly in MongoDB. By default, they follow a FIFO (First-In-First-Out) pattern. You can transform a regular queue into a LIFO (Last-In-First-Out) 'pile' by appending _reverse to the queue name when starting a worker.

    # To dequeue the last jobs added to the queue "default" (LIFO)
    $ mrq-worker default_reverse
  10. Job statuses and lifecycle

    master

    Jobs transition through various statuses. Understanding these is critical for monitoring and error handling:

    Standard Lifecycle:

    • queued: Created and waiting for a Worker.
    • started: A Worker has begun execution.
    • success: Execution completed successfully.

    Error and Interruption States:

    • failed: A Python Exception was raised during execution.
    • cancel: The job was cancelled (usually via the Dashboard). Note: Cancelling a started job does not interrupt the currently running code.
    • abort: The job was stopped via abort_current_job(). Used for unrecoverable errors you want to log without retrying.
    • interrupt: The worker process received a SIGTERM or two SIGINTs. (Note: SIGKILL or power loss will leave the job in started state).
    • timeout: The job exceeded its configured timeout.
    • retry: task.retry() was called to schedule a retry.
    • maxretries: The task reached its maximum allowed retry attempts (default is 3).
  11. Compare MRQ Queue Types: Regular, Raw, and Redis-only

    master

    Choosing a queue type involves balancing performance, dashboard visibility, and job safety.

    Queue typeRegularRawRaw with no_storage config
    Storage for queued jobsMongoDBRedisRedis
    Storage for started & success jobsMongoDBMongoDBNone
    Performance++++++
    Visibility in the dashboardFullAfter startJob counts & failed jobs
    Safety++++++

    Safety Considerations

    • Regular Queue: Highest safety; jobs are guaranteed not to be lost once inserted in MongoDB.
    • Raw Queue: Risk of job loss if the worker exits abruptly between dequeuing from Redis and inserting into MongoDB.
    • Redis-only Raw Queue: Lowest safety; cannot guarantee a job is finished if the worker exits abruptly after dequeuing.
  12. Send metrics to Graphite using METRIC_HOOK

    master

    MRQ does not support Graphite out of the box, but you can integrate it by implementing a METRIC_HOOK in your mrq-config file. This hook is called by MRQ whenever a metric is recorded.

    To use Graphite, you should install the graphiteudp package via pip, initialize a GraphiteUDPClient, and then define the METRIC_HOOK function to send the metric name and increment value to your Graphite instance. You can use logic within the hook (such as a whitelist) to filter which metrics are sent to avoid overwhelming your monitoring system.

    import graphiteudp  # Install this via pip
    
    # Initialize the Graphite UDP Client
    _graphite_client = graphiteudp.GraphiteUDPClient(host, port, prefix, debug=False)
    _graphite_client.init()
    
    def METRIC_HOOK(name, incr=1, **kwargs):
    
      # You can use this to avoid sending too many different metrics
      whitelisted_metrics = ["queues.all.", "queues.default.", "jobs."]
    
      if any([name.startswith(m) for m in whitelisted_metrics]):
        _graphite_client.send(name, incr)