<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: OJASVI SWAMI</title>
    <description>The latest articles on DEV Community by OJASVI SWAMI (@ojasviswami22).</description>
    <link>https://dev.to/ojasviswami22</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4207002%2Fb248f994-39ed-4d59-a555-d4954e8d34f5.jpg</url>
      <title>DEV Community: OJASVI SWAMI</title>
      <link>https://dev.to/ojasviswami22</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ojasviswami22"/>
    <language>en</language>
    <item>
      <title>Building a Priority-Based Task Scheduler with FastAPI, Redis, and C++</title>
      <dc:creator>OJASVI SWAMI</dc:creator>
      <pubDate>Sun, 25 Oct 2026 17:58:10 +0000</pubDate>
      <link>https://dev.to/ojasviswami22/building-a-priority-based-task-scheduler-with-fastapi-redis-and-c-1c93</link>
      <guid>https://dev.to/ojasviswami22/building-a-priority-based-task-scheduler-with-fastapi-redis-and-c-1c93</guid>
      <description>&lt;p&gt;&lt;strong&gt;Introduction :-&lt;/strong&gt;&lt;br&gt;
Not every backend operation should be executed as part of an HTTP request.&lt;br&gt;
Some operations require additional computation, may take unpredictable amounts of time, or need to run independently of the API layer. Executing all of them synchronously can tie request handling to processing time and make it harder to control how work is scheduled.&lt;br&gt;
A common solution is to separate task submission from task execution.&lt;br&gt;
In this article, I will walk through a task-processing system I built using Python, FastAPI, Redis, PostgreSQL, and a C++ processing service powered by OpenSSL. The system accepts operations through a REST API, organizes tasks into priority queues, and processes them asynchronously.&lt;/p&gt;

&lt;p&gt;The goal is not to introduce another queue implementation. Instead, I want to explore the engineering decisions behind the system: why the components are separated, how tasks move through the architecture, and what trade-offs arise when building an asynchronous processing pipeline.&lt;br&gt;
The complete source code is available on GitHub.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Problem Statement:-&lt;/strong&gt; &lt;br&gt;
Consider an API that accepts computational tasks such as encryption, decryption, hashing, and compression.&lt;/p&gt;

&lt;p&gt;If the API performs each operation before responding, the request remains coupled to the execution time of that operation. This becomes inconvenient when processing needs to happen independently or when different tasks have different priorities.&lt;/p&gt;

&lt;p&gt;I wanted to build a system having following capablities:-&lt;br&gt;
Asynchronous processing: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Accept a task without waiting for its computation to finish.&lt;/li&gt;
&lt;li&gt;Priority-based scheduling: Route tasks into high, normal, and low-priority queues.&lt;/li&gt;
&lt;li&gt;Resource control: Apply a per-user request rate limit.&lt;/li&gt;
&lt;li&gt;Separation of responsibilities: Keep API handling separate from the C++ processing service.&lt;/li&gt;
&lt;li&gt;Status tracking: Allow clients to check whether a task is pending, running, completed, or failed.&lt;/li&gt;
&lt;li&gt;Persistent results: Store task-related results in PostgreSQL.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are the requirements for building architecture of the project.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Architectural overview :-&lt;/strong&gt;&lt;br&gt;
The system has four main services: FastAPI, Redis, a C++ processing service, and PostgreSQL.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvhj18zlbqvdqju5mcqfv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvhj18zlbqvdqju5mcqfv.png" alt=" " width="800" height="617"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgqpa54lrsot8kt5pznvu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgqpa54lrsot8kt5pznvu.png" alt=" " width="800" height="390"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The responsibilities are deliberately separated:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;FastAPI handles incoming requests, validates task parameters, and exposes endpoints for task submission and status checks.&lt;/li&gt;
&lt;li&gt;Redis provides priority queues for pending tasks and supports request rate limiting.&lt;/li&gt;
&lt;li&gt;The C++ service executes the supported processing operations using OpenSSL and the project's RLE implementation.&lt;/li&gt;
&lt;li&gt;PostgreSQL stores task-related state and results that need to remain available for subsequent status requests.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;3. Following a task through the system :-&lt;/strong&gt; &lt;br&gt;
Here is a brief walk through the lifecycle of task.&lt;/p&gt;

&lt;p&gt;Step 1: Task submission&lt;/p&gt;

&lt;p&gt;A client submits a request to POST /process.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyjqgr86uy0bslc87pk3y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyjqgr86uy0bslc87pk3y.png" alt=" " width="673" height="340"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;FastAPI validates the request and applies the rate-limiting middleware. The system allows up to 10 requests per minute for a given x-user-id header, according to the configured rate limit.&lt;/p&gt;

&lt;p&gt;Once the task is accepted, the API returns a request ID and a PENDING status rather than waiting for the processing operation to finish.&lt;/p&gt;

&lt;p&gt;Step 2: Priority-based scheduling&lt;br&gt;
The scheduler routes tasks into one of three Redis queues:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fomwh3oteb1jfewv8evcf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fomwh3oteb1jfewv8evcf.png" alt=" " width="800" height="289"&gt;&lt;/a&gt;&lt;br&gt;
This makes priority an explicit part of the scheduling model.&lt;/p&gt;

&lt;p&gt;A worker can check higher-priority queues before lower-priority queues, allowing urgent tasks to be selected first. &lt;em&gt;However, this also raises an important design question: what happens to low-priority tasks if high-priority work arrives continuously?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Strict priority can cause starvation. A more advanced scheduler might introduce weighted scheduling or aging, where a task's effective priority increases as it waits.&lt;/p&gt;

&lt;p&gt;The current architecture establishes separate priority queues; starvation prevention would be a further scheduling enhancement.&lt;/p&gt;

&lt;p&gt;Step 3: Asynchronous task execution&lt;/p&gt;

&lt;p&gt;A background Python worker retrieves queued tasks using Redis blocking queue operations and dispatches them to the C++ processing service through HTTP.&lt;/p&gt;

&lt;p&gt;The processing service supports five operations:&lt;/p&gt;

&lt;p&gt;ENCRYPT and DECRYPT using AES-256-CBC.&lt;/p&gt;

&lt;p&gt;HASH using SHA-256.&lt;/p&gt;

&lt;p&gt;TRANSFORM and DECOMPRESS using run-length encoding (RLE).&lt;/p&gt;

&lt;p&gt;The Python API and worker do not need to implement these operations themselves. They coordinate task execution while the C++ service owns the processing logic.&lt;/p&gt;

&lt;p&gt;This boundary also creates an independent place to evolve and test the processing implementation.&lt;/p&gt;

&lt;p&gt;Step 4: Persisting results and checking status&lt;/p&gt;

&lt;p&gt;As processing progresses, the task moves through the following states:&lt;/p&gt;

&lt;p&gt;PENDING → IN_PROGRESS → DONE / FAILED&lt;/p&gt;

&lt;p&gt;The result and relevant task information are stored in PostgreSQL. Clients can subsequently call GET /status/{request_id} to retrieve the current state, output data, and processing time when available.&lt;/p&gt;

&lt;p&gt;This polling model is straightforward to implement and integrate with ordinary HTTP clients.Its trade-off is that clients make repeated requests even when a task's state has not changed. _Webhooks or server-sent events _could be explored if clients later need push-based updates.&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;4. Why Use Redis for Priority Queues? *&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Redis provides a lightweight way to maintain pending tasks without forcing the API to execute them immediately.&lt;/p&gt;

&lt;p&gt;In this design, the task payload is placed directly into the Redis queue. The worker receives the information it needs to dispatch the task without making an additional database query just to reconstruct the queued request.&lt;/p&gt;

&lt;p&gt;This is a deliberate trade-off: it reduces the need for an extra database read during dispatch, but duplicates some task information between Redis and PostgreSQL.&lt;/p&gt;

&lt;p&gt;The design also raises reliability questions.&lt;/p&gt;

&lt;p&gt;The worker uses Redis &lt;code&gt;BLPOP&lt;/code&gt;, which removes a task from the queue as soon as it retrieves it. If the worker crashes before completing the task, Redis does not automatically put it back into the queue. The task may therefore remain stuck in &lt;code&gt;PENDING&lt;/code&gt; or &lt;code&gt;IN_PROGRESS&lt;/code&gt; in PostgreSQL.&lt;/p&gt;

&lt;p&gt;A possible improvement would be to introduce an in-flight queue, acknowledgements, and a recovery mechanism for unfinished tasks. The current implementation does not yet provide this recovery behavior.&lt;/p&gt;

&lt;p&gt;The right choice depends on the delivery guarantees the application actually needs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Why Separate the C++ Processing Service?&lt;/strong&gt;&lt;br&gt;
The project uses Python for API orchestration and C++ for the processing operations.This offers several benefits including-&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The API does not have to execute the processing logic directly inside its request handler.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The processing implementation can be tested independently.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The C++ service can be scaled separately if workload measurements justify doing so.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Each component has a narrower responsibility.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is also a cost. Calling the C++ service over HTTP introduces serialization, network communication, and additional failure points.&lt;br&gt;
It is therefore important not to assume that a separate C++ service is automatically faster than implementing the same operation in Python. The benefit depends on the workload, data size, execution cost, and communication overhead.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Rate Limiting at the API Layer&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The application limits each x-user-id to 10 requests per minute using Redis-backed keys with expiration.&lt;/p&gt;

&lt;p&gt;This protects the API from an individual identifier submitting requests too quickly and provides a simple mechanism for controlling admission.&lt;/p&gt;

&lt;p&gt;When the limit is exceeded, the API returns HTTP 429 Too Many Requests, along with a retry-after value.&lt;/p&gt;

&lt;p&gt;One important distinction is that rate limiting controls the rate at which tasks are submitted. It does not, by itself, control how many tasks a worker can execute concurrently or how large the queue can grow.&lt;/p&gt;

&lt;p&gt;For a more complete overload-control strategy, I would also evaluate queue-depth limits, bounded worker concurrency, and backpressure when processing capacity falls behind incoming demand.&lt;/p&gt;

&lt;p&gt;The current implementation uses separate Redis operations to read, increment, and expire the counter. Concurrent requests can race, and the initial increment and expiration are not atomic. A Redis Lua script could combine these operations to provide more consistent rate-limit enforcement under concurrency.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7. Testing the Components&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The project includes Python tests using pytest and C++ tests using GoogleTest.&lt;/p&gt;

&lt;p&gt;The C++ tests cover operations such as encryption/decryption round trips, SHA-256, and RLE compression/decompression. Python tests cover API behavior, rate limiting, and processor integration.&lt;/p&gt;

&lt;p&gt;These tests provide a foundation for validating the individual components. Additional tests would be valuable for system-level failure scenarios:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A worker crashes after retrieving a task.&lt;/li&gt;
&lt;li&gt;The C++ service becomes unavailable.&lt;/li&gt;
&lt;li&gt;PostgreSQL temporarily rejects writes.&lt;/li&gt;
&lt;li&gt;The same task is submitted or processed more than once.&lt;/li&gt;
&lt;li&gt;A high-priority workload continuously occupies the workers.&lt;/li&gt;
&lt;li&gt;Incoming tasks exceed the processing capacity.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These cases matter because successful execution tests alone do not establish reliable delivery under failure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;8. What I Would Improve Next&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The current implementation is a useful starting point, but several areas deserve further investigation.&lt;/p&gt;

&lt;p&gt;Reliable task delivery: Introduce an explicit recovery strategy for tasks removed from Redis but not completed.&lt;/p&gt;

&lt;p&gt;Idempotent execution: Give tasks stable identities and define how duplicate execution is handled, particularly when a worker fails after performing the operation but before recording completion.&lt;/p&gt;

&lt;p&gt;Bounded concurrency and backpressure: Limit in-flight work and define what happens when the queue grows faster than workers can drain it.&lt;/p&gt;

&lt;p&gt;Observability: Record queue wait time, processing duration, task failure rate, queue depth, and end-to-end latency. These measurements would help distinguish slow computation from scheduling or persistence overhead.&lt;/p&gt;

&lt;p&gt;Security: AES-CBC provides confidentiality but does not authenticate ciphertext. For applications that need authenticated encryption, I would evaluate AES-GCM or another authenticated encryption construction, with appropriate nonce management and key handling.&lt;/p&gt;

&lt;p&gt;Performance evaluation: Benchmark realistic payload sizes and concurrent workloads before claiming that one architecture or implementation is faster than another.&lt;/p&gt;

&lt;p&gt;These improvements would move the system toward stronger delivery guarantees and more measurable operational behavior.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Building the task scheduler was an exercise in separating concerns across an asynchronous backend pipeline.&lt;/p&gt;

&lt;p&gt;FastAPI handles the API, Redis organizes pending work, a Python worker coordinates execution, C++ performs the supported operations, and PostgreSQL retains task information and results.&lt;/p&gt;

&lt;p&gt;The interesting engineering questions go beyond choosing a technology. They involve deciding what happens when a worker fails, how priority affects fairness, how to control overload, and how to measure the real cost of communication between services.&lt;/p&gt;

&lt;p&gt;Those trade-offs are what make a task scheduler a useful system-design case study—and what I would investigate next as the system evolves.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Source Code&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/OJASVISWAMI22" rel="noopener noreferrer"&gt;
        OJASVISWAMI22
      &lt;/a&gt; / &lt;a href="https://github.com/OJASVISWAMI22/task-scheduler" rel="noopener noreferrer"&gt;
        task-scheduler
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Async task processing system built with FastAPI, Redis rate limiting, and a C++ processing service backed by PostgreSQL
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;p&gt;Task Scheduler&lt;/p&gt;
&lt;p&gt;An asynchronous task-processing system built with FastAPI, C++ (OpenSSL), Redis and PostgreSQL, fully containerized with Docker Compose.&lt;/p&gt;
&lt;p&gt;Clients send tasks (encrypt, hash, compress etc.) through REST API. Queues are prioritized in Redis and handled by a C++ service. Results go to PostgreSQL.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Architecture&lt;/h2&gt;
&lt;/div&gt;

  &lt;div class="js-render-enrichment-target"&gt;
    &lt;div class="render-plaintext-hidden"&gt;
      &lt;pre&gt;flowchart TD
    Client([Client])

    subgraph FastAPI["FastAPI Service"]
        RL["Rate Limiter\n(Redis TTL · 10 req/min)"]
        PP["POST /process"]
        GS["GET /status/{request_id}"]
        WK["Worker\n(asyncio · blpop)"]
    end

    R[(Redis\nqueue:high / normal / low)]
    CPP["C++ Service\n(AES-256-CBC · SHA-256 · RLE)"]
    PG[(PostgreSQL\nrequests · results)]

    Client --&amp;gt;|POST /process| RL
    Client --&amp;gt;|GET /status| GS
    RL --&amp;gt; PP
    PP --&amp;gt;|enqueue| R
    WK --&amp;gt;|blpop| R
    WK --&amp;gt;|HTTP POST /process| CPP
    WK --&amp;gt;|save result| PG
    GS --&amp;gt;|read| PG
&lt;/pre&gt;
    &lt;/div&gt;
  &lt;/div&gt;
  &lt;span class="js-render-enrichment-loader d-flex flex-justify-center flex-items-center width-full"&gt;
    &lt;span&gt;
      &lt;span class="sr-only"&gt;Loading&lt;/span&gt;
&lt;/span&gt;
  &lt;/span&gt;


&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Features&lt;/h2&gt;

&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Priority queues — tasks routed to &lt;code&gt;queue:high&lt;/code&gt;, &lt;code&gt;queue:normal&lt;/code&gt;, or &lt;code&gt;queue:low&lt;/code&gt; based on request priority (3 / 2 / 1)&lt;/li&gt;
&lt;li&gt;Rate limiting — 10 requests/minute per &lt;code&gt;x-user-id&lt;/code&gt; header, enforced in middleware via Redis TTL keys&lt;/li&gt;
&lt;li&gt;Async processing — background…&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/OJASVISWAMI22/task-scheduler" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


</description>
      <category>fastapi</category>
      <category>python</category>
      <category>cpp</category>
      <category>systemdesign</category>
    </item>
  </channel>
</rss>
