Skip to content

Latest commit

Β 

History

35 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Real-Time Bidding Engine Backend

A highly concurrent, event-driven backend architecture for a real-time auction system. This API handles secure user authentication, precise background task scheduling, row-level database locking to prevent bid race conditions, and real-time WebSockets to broadcast live price updates.

πŸš€ Architecture & Tech Stack

This project is built with a robust, production-ready microservices approach, fully containerized using Docker.

Layer Technology Role
Core Framework Django & Django Rest Framework REST API, ORM, Admin
Database PostgreSQL 15 Transactional storage with row-level locking
Authentication djangorestframework-simplejwt JWT access & refresh tokens
Task Queue Celery + Redis Deferred auction settlement
WebSockets / ASGI Django Channels + Daphne Real-time bid broadcasts
Message Broker Redis Channels layer & Celery broker
Reverse Proxy Nginx Route /api/ β†’ Gunicorn, /ws/ β†’ Daphne
Containerization Docker & Docker Compose 6-service orchestration

βš™οΈ System Architecture

Our backend adheres to a strictly decoupled, event-driven architecture. Service boundaries are enforced at the container level via Docker Compose, replicating the isolation guarantees of a production microservices deployment. Nginx acts as the single entry point, deterministically routing traffic to the appropriate application server based on protocol.

graph TD
    A[Client / Frontend] --> B(Nginx Reverse Proxy)
    B -->|/api/ /admin/| C(Gunicorn / Django REST API)
    B -->|/ws/| D(Daphne / Django Channels)
    C <--> E(PostgreSQL)
    C -->|Publish Events| F(Redis)
    C -->|Enqueue Tasks| F
    D <--> F
    D <-->|Read Models| E
    F --> G[Celery Worker]
    G <-->|Settle Auctions| E
    G -->|Broadcast auction_ended| F
    E -.->|SELECT ... FOR UPDATE| C
    F -.->|WebSocket Push| D
    D -.->|Live Price Broadcast| A
Loading

✨ Core Features

  • Custom User & Wallet System: Extended Django User model with wallet_balance. Every new user starts with β‚Ή1000.00 for immediate bidding.
  • Concurrency Control: Utilizes PostgreSQL's SELECT ... FOR UPDATE within transaction.atomic() to strictly serialize concurrent bids, ensuring zero race conditions when multiple users bid at the exact same millisecond.
  • Automatic Wallet Refunds: When a new highest bid is placed, the previous highest bidder's wallet balance is atomically refunded inside the same database transaction.
  • Automated Auction Settlement: Celery tasks are scheduled with apply_async(eta=end_time) at auction creation, automatically closing auctions at their exact expiry without polling.
  • Real-Time Price Broadcasts: Daphne and Redis publish instant WebSocket updates (new_bid and auction_ended events) to all connected clients the moment a bid clears or an auction concludes.
  • Admin Dashboard: Fully customized Django admin interface for managing users, balances, and active auctions.

πŸ—οΈ Project Structure

BidStream/
β”œβ”€β”€ docker-compose.yml          # 6-service orchestration
β”œβ”€β”€ stress_test.py              # Concurrency stress test (50 simultaneous bids)
β”œβ”€β”€ nginx/
β”‚   └── default.conf            # Reverse proxy routing rules
└── bid/                        # Django project root
    β”œβ”€β”€ Dockerfile
    β”œβ”€β”€ requirements.txt
    β”œβ”€β”€ manage.py
    β”œβ”€β”€ bid/                    # Project config
    β”‚   β”œβ”€β”€ settings.py
    β”‚   β”œβ”€β”€ urls.py
    β”‚   β”œβ”€β”€ wsgi.py             # Gunicorn entrypoint
    β”‚   β”œβ”€β”€ asgi.py             # Daphne entrypoint
    β”‚   └── celery.py           # Celery app config
    └── auctions/               # Core application
        β”œβ”€β”€ models.py           # User, Auction, Bid models
        β”œβ”€β”€ views.py            # API views with row-level locking
        β”œβ”€β”€ serializers.py      # DRF serializers
        β”œβ”€β”€ urls.py             # REST endpoint routing
        β”œβ”€β”€ consumers.py        # WebSocket consumers
        β”œβ”€β”€ routing.py          # WebSocket URL patterns
        β”œβ”€β”€ tasks.py            # Celery settlement task
        └── admin.py            # Admin customization

πŸ› οΈ Local Setup & Installation

Ensure you have Docker and Docker Compose installed on your machine.

  1. Clone the repository:

    https://github.com/VIDIT45AGARWAL/BidStream.git
    cd BidStream
  2. Spin up the Docker containers:

    docker compose up --build

    This command will build the images and spin up all 6 services: PostgreSQL, Redis, Gunicorn (Django), Daphne (WebSockets), Celery Worker, and Nginx.

  3. Apply Database Migrations: Open a new terminal window and run:

    docker compose exec web python manage.py migrate
  4. Create a Superuser (Admin):

    docker compose exec web python manage.py createsuperuser
  5. Access the Application:

    Endpoint URL
    API Base http://localhost/api/
    Admin Panel http://localhost/admin/
    WebSocket ws://localhost/ws/auctions/<auction_id>/

πŸ“‘ API Endpoints

Authentication

Method Endpoint Description
POST /api/auth/register/ Register a new user account
POST /api/auth/login/ Obtain JWT access and refresh tokens
POST /api/auth/token/refresh/ Refresh an expired access token

Auctions

Method Endpoint Description
GET /api/auctions/ List all active auctions (sorted by end_time)
POST /api/auctions/ Create a new auction item (Auth Required)

Create Auction Payload:

{
    "title": "Vintage Watch",
    "description": "A rare 1960s timepiece in mint condition.",
    "starting_price": "100.00",
    "end_time": "2026-06-25T18:00:00Z"
}

Bidding

Method Endpoint Description
POST /api/bids/ Place a bid on an active auction (Auth Required)

Place Bid Payload:

{
    "auction": 1,
    "amount": "250.00"
}

Guardrails: Rejects bids lower than current price, self-bidding, bids on expired/closed auctions, and bids exceeding wallet balance.

Users (Admin Only)

Method Endpoint Description
DELETE /api/users/<id>/ Delete a user account (Requires is_staff=True)

πŸ”Œ WebSocket Integration

Connect your frontend to receive live updates when bids are placed or auctions conclude:

URL: ws://localhost/ws/auctions/<auction_id>/

Event: new_bid

Broadcast to all connected clients when a successful bid is placed:

{
    "event": "new_bid",
    "current_price": "250.00",
    "bidder_username": "buyer_bob"
}

Event: auction_ended

Broadcast when the Celery worker settles an expired auction:

{
    "event": "auction_ended",
    "winner": "buyer_bob",
    "final_price": "250.00",
    "message": "This auction has concluded."
}

πŸ§ͺ Concurrency Stress Test

This project includes a purpose-built stress test that proves the PostgreSQL row-level locking mechanism prevents race conditions. The script uses asyncio + aiohttp to fire 50 identical bid requests at the exact same millisecond from a single user, then verifies that exactly 1 bid is accepted and the remaining 49 are safely rejected.

How It Works

sequenceDiagram
    participant Script as stress_test.py
    participant API as Django API
    participant DB as PostgreSQL

    Script->>API: 50Γ— POST /api/bids/ (β‚Ή1000, same user)
    API->>DB: Thread 1: SELECT ... FOR UPDATE (acquires lock)
    API--xDB: Thread 2–50: BLOCKED (waiting for lock)
    DB-->>API: Thread 1: Validates & inserts bid
    API-->>Script: Thread 1: 201 Created βœ…
    DB-->>API: Thread 2: Acquires lock, reads updated price
    API-->>Script: Thread 2: 400 Rejected ❌ (bid ≀ current_price)
    API-->>Script: Thread 3–50: 400 Rejected ❌
Loading

Running the Test

  1. Start the stack:

    docker compose up --build
    docker compose exec web python manage.py migrate
  2. Setup test data:

    • Create a seller account and a buyer account via /api/auth/register/
    • Login as the seller via /api/auth/login/ and create an auction via /api/auctions/
    • Login as the buyer and copy the access token
  3. Configure and run:

    pip install aiohttp

    Update stress_test.py with your TOKEN and AUCTION_ID, then:

    python stress_test.py

Expected Output

Firing 50 bids for $1000.00 at the exact same millisecond
----------------------------------------
Completed in 0.45 seconds.
Successful bids (201 Created): 1
Rejected bids (400 Bad Request): 49
----------------------------------------
SUCCESS! Your database locks worked perfectly.
1 bid went through, and 49 were safely rejected to prevent a race condition.

Why this matters: Without SELECT ... FOR UPDATE, all 50 threads would read the same current_price, all 50 would pass validation, and the wallet would be debited 50Γ— β€” a classic race condition. The row-level lock serializes access so only one thread can validate and write at a time.

About

A realtime Bidding Engine

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages