Skip to content

Commit 93e239a

Browse files
author
Manohar
committed
Initial commit
0 parents  commit 93e239a

16 files changed

Lines changed: 1281 additions & 0 deletions

File tree

.env.example

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
AWS_REGION=us-east-1
2+
AWS_ACCESS_KEY_ID=your_access_key_here
3+
AWS_SECRET_ACCESS_KEY=your_secret_key_here
4+
SQS_QUEUE_URL=https://sqs.us-east-1.amazonaws.com/YOUR_ACCOUNT_ID/task-queue
5+
SQS_DLQ_URL=https://sqs.us-east-1.amazonaws.com/YOUR_ACCOUNT_ID/task-queue-dlq
6+
DYNAMODB_TABLE=tasks
7+
WORKER_COUNT=5
8+
PORT=8080

.github/workflows/ci.yml

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
name: CI/CD Pipeline
2+
3+
on:
4+
push:
5+
branches: [main, develop]
6+
pull_request:
7+
branches: [main]
8+
9+
jobs:
10+
# ---- Lint & Test ----
11+
test:
12+
name: Test
13+
runs-on: ubuntu-latest
14+
15+
steps:
16+
- name: Checkout code
17+
uses: actions/checkout@v4
18+
19+
- name: Set up Go
20+
uses: actions/setup-go@v5
21+
with:
22+
go-version: '1.22'
23+
24+
- name: Cache Go modules
25+
uses: actions/cache@v4
26+
with:
27+
path: ~/go/pkg/mod
28+
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
29+
restore-keys: ${{ runner.os }}-go-
30+
31+
- name: Download dependencies
32+
run: go mod download
33+
34+
- name: Vet (static analysis)
35+
run: go vet ./...
36+
37+
- name: Build
38+
run: go build ./...
39+
40+
- name: Run tests with race detector
41+
run: go test ./... -v -race -coverprofile=coverage.out
42+
43+
- name: Upload coverage report
44+
uses: codecov/codecov-action@v4
45+
with:
46+
file: ./coverage.out
47+
fail_ci_if_error: false
48+
49+
# ---- Docker Build ----
50+
docker:
51+
name: Docker Build
52+
needs: test
53+
runs-on: ubuntu-latest
54+
55+
steps:
56+
- name: Checkout code
57+
uses: actions/checkout@v4
58+
59+
- name: Build Docker image
60+
run: |
61+
docker build -t task-queue-engine:${{ github.sha }} .
62+
docker build -t task-queue-engine:latest .
63+
64+
- name: Smoke test — container starts and responds to health check
65+
run: |
66+
docker run -d --name tqe-test \
67+
-e SQS_QUEUE_URL=mock \
68+
-e SQS_DLQ_URL=mock \
69+
-p 8080:8080 \
70+
task-queue-engine:latest || true
71+
sleep 3
72+
docker logs tqe-test || true

.gitignore

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Binaries
2+
task-queue-api
3+
*.exe
4+
5+
# Environment variables — NEVER commit this
6+
.env
7+
8+
# Go build cache
9+
/vendor/
10+
11+
# IDE
12+
.vscode/
13+
.idea/
14+
*.swp
15+
16+
# OS
17+
.DS_Store
18+
Thumbs.db

Dockerfile

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# ---- Build Stage ----
2+
FROM golang:1.22-alpine AS builder
3+
4+
WORKDIR /app
5+
6+
# Download dependencies first (cached layer — only re-runs if go.mod changes)
7+
COPY go.mod go.sum ./
8+
RUN go mod download
9+
10+
# Copy source and build a statically linked binary
11+
COPY . .
12+
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-w -s" -o /task-queue-api ./cmd/api
13+
14+
# ---- Runtime Stage ----
15+
# Use minimal alpine image — final image is ~15MB instead of ~800MB
16+
FROM alpine:3.19
17+
18+
RUN apk --no-cache add ca-certificates tzdata
19+
20+
WORKDIR /app
21+
COPY --from=builder /task-queue-api .
22+
23+
EXPOSE 8080
24+
25+
# Run as non-root user for security
26+
RUN adduser -D -g '' appuser
27+
USER appuser
28+
29+
CMD ["./task-queue-api"]

README.md

Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
# Task Queue Engine
2+
3+
A production-grade distributed task queue system built with **Go** and **AWS**, designed for reliable asynchronous job processing at scale.
4+
5+
[![CI/CD](https://github.com/manohar6317/task-queue-engine/actions/workflows/ci.yml/badge.svg)](https://github.com/manohar6317/task-queue-engine/actions)
6+
![Go Version](https://img.shields.io/badge/go-1.22-blue)
7+
![AWS](https://img.shields.io/badge/AWS-SQS%20%7C%20DynamoDB-orange)
8+
9+
---
10+
11+
## Architecture
12+
13+
```
14+
┌─────────────────────────────────────────┐
15+
│ Task Queue Engine │
16+
│ │
17+
Client │ ┌──────────────┐ │
18+
│ │ │ HTTP API │ │
19+
│ POST /tasks │ │ (Go server) │ │
20+
├────────────────────►│ └──────┬───────┘ │
21+
│ │ │ 1. Save task │
22+
│ GET /tasks/{id} │ ▼ │
23+
├────────────────────►│ ┌──────────────┐ 2. Enqueue │
24+
│ │ │ DynamoDB │◄──────────────────┐ │
25+
│ GET /metrics │ │ (task store)│ │ │
26+
└────────────────────►│ └──────────────┘ │ │
27+
│ ┌───────────┴──┴────┐
28+
│ │ AWS SQS │
29+
│ │ ┌─────────────┐ │
30+
│ │ │ Main Queue │ │
31+
│ │ └──────┬──────┘ │
32+
│ │ │ │
33+
│ │ (after 3 fails) │
34+
│ │ ┌──────▼──────┐ │
35+
│ │ │ Dead Letter│ │
36+
│ │ │ Queue │ │
37+
│ │ └─────────────┘ │
38+
│ └────────┬──────────┘
39+
│ │ 3. Poll (long-polling)
40+
│ ┌──────────────────────────────────────────┐
41+
│ │ Worker Pool (5 goroutines) │
42+
│ │ ┌────────┐ ┌────────┐ ┌────────┐ │
43+
│ │ │Worker 0│ │Worker 1│ │Worker N│ ... │
44+
│ │ └────────┘ └────────┘ └────────┘ │
45+
│ │ 4. Process task concurrently │
46+
│ │ 5. Update status in DynamoDB │
47+
│ │ 6. Delete message from SQS │
48+
│ └──────────────────────────────────────────┘
49+
└─────────────────────────────────────────────┘
50+
```
51+
52+
---
53+
54+
## Features
55+
56+
- **Concurrent Workers** — configurable pool of goroutines, each polling SQS independently
57+
- **Reliable Delivery** — SQS visibility timeout prevents duplicate processing during failures
58+
- **Auto Retry** — failed tasks are retried up to 3 times before moving to the Dead Letter Queue
59+
- **Dead Letter Queue** — unprocessable messages are isolated for investigation without blocking the main queue
60+
- **Task Persistence** — every task's full lifecycle is tracked in DynamoDB with timestamps
61+
- **Real-time Metrics**`/metrics` endpoint exposes queue depth, active workers, and task counts
62+
- **Graceful Shutdown** — handles SIGINT/SIGTERM by draining in-flight tasks before exiting
63+
- **Dockerized** — multi-stage build produces a ~15MB production image
64+
- **CI/CD** — GitHub Actions pipeline runs lint, tests (with race detector), and Docker build on every push
65+
66+
---
67+
68+
## Tech Stack
69+
70+
| Layer | Technology |
71+
|---|---|
72+
| Language | Go 1.22 |
73+
| Message Queue | AWS SQS |
74+
| Database | AWS DynamoDB |
75+
| Containerization | Docker (multi-stage) |
76+
| CI/CD | GitHub Actions |
77+
| Cloud | AWS (free tier compatible) |
78+
79+
---
80+
81+
## Project Structure
82+
83+
```
84+
task-queue-engine/
85+
├── cmd/
86+
│ └── api/
87+
│ └── main.go # Entry point — wires up all components
88+
├── internal/
89+
│ ├── api/
90+
│ │ └── handler.go # HTTP handlers (submit, status, metrics, health)
91+
│ ├── models/
92+
│ │ └── task.go # Task domain model and request/response types
93+
│ ├── queue/
94+
│ │ └── sqs.go # SQS enqueue / dequeue / delete operations
95+
│ ├── store/
96+
│ │ └── dynamodb.go # DynamoDB CRUD and status queries
97+
│ └── worker/
98+
│ └── worker.go # Concurrent worker pool with retry logic
99+
├── config/
100+
│ └── config.go # Environment-based configuration loader
101+
├── .github/
102+
│ └── workflows/
103+
│ └── ci.yml # CI/CD pipeline (lint → test → docker build)
104+
├── setup-aws.sh # One-time AWS resource provisioning script
105+
├── Dockerfile # Multi-stage Docker build
106+
├── docker-compose.yml # Local development environment
107+
└── go.mod
108+
```
109+
110+
---
111+
112+
## Getting Started
113+
114+
### Prerequisites
115+
116+
- [Go 1.22+](https://go.dev/dl/)
117+
- [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) configured (`aws configure`)
118+
- [Docker](https://docs.docker.com/get-docker/) (optional)
119+
- AWS account (free tier works)
120+
121+
### 1. Clone the repository
122+
123+
```bash
124+
git clone https://github.com/manohar6317/task-queue-engine.git
125+
cd task-queue-engine
126+
```
127+
128+
### 2. Provision AWS resources
129+
130+
```bash
131+
chmod +x setup-aws.sh
132+
./setup-aws.sh
133+
```
134+
135+
This creates:
136+
- SQS main queue with a 30s visibility timeout
137+
- SQS Dead Letter Queue with a 3-retry redrive policy
138+
- DynamoDB `tasks` table with a GSI on `status`
139+
140+
Copy the output values into a `.env` file.
141+
142+
### 3. Configure environment
143+
144+
```bash
145+
cp .env.example .env
146+
# Edit .env with the values from setup-aws.sh output
147+
```
148+
149+
```env
150+
AWS_REGION=us-east-1
151+
AWS_ACCESS_KEY_ID=your_access_key
152+
AWS_SECRET_ACCESS_KEY=your_secret_key
153+
SQS_QUEUE_URL=https://sqs.us-east-1.amazonaws.com/123456789/task-queue
154+
SQS_DLQ_URL=https://sqs.us-east-1.amazonaws.com/123456789/task-queue-dlq
155+
DYNAMODB_TABLE=tasks
156+
WORKER_COUNT=5
157+
PORT=8080
158+
```
159+
160+
### 4. Run locally
161+
162+
```bash
163+
# Option A: directly with Go
164+
source .env
165+
go run ./cmd/api
166+
167+
# Option B: with Docker
168+
docker-compose up --build
169+
```
170+
171+
---
172+
173+
## API Reference
174+
175+
### Submit a Task
176+
```http
177+
POST /tasks
178+
Content-Type: application/json
179+
180+
{
181+
"type": "email",
182+
"payload": "user@example.com"
183+
}
184+
```
185+
186+
**Response:**
187+
```json
188+
{
189+
"id": "550e8400-e29b-41d4-a716-446655440000",
190+
"type": "email",
191+
"payload": "user@example.com",
192+
"status": "PENDING",
193+
"retry_count": 0,
194+
"created_at": "2024-01-15T10:30:00Z",
195+
"updated_at": "2024-01-15T10:30:00Z"
196+
}
197+
```
198+
199+
### Check Task Status
200+
```http
201+
GET /tasks/{id}
202+
```
203+
204+
**Possible status values:** `PENDING``PROCESSING``COMPLETED` or `FAILED`
205+
206+
### Get System Metrics
207+
```http
208+
GET /metrics
209+
```
210+
211+
```json
212+
{
213+
"total_tasks": 1500,
214+
"pending_tasks": 23,
215+
"completed_tasks": 1460,
216+
"failed_tasks": 17,
217+
"active_workers": 4,
218+
"queue_depth": 23
219+
}
220+
```
221+
222+
### Health Check
223+
```http
224+
GET /health
225+
```
226+
227+
---
228+
229+
## Supported Task Types
230+
231+
| Type | Description |
232+
|---|---|
233+
| `email` | Sends an email notification |
234+
| `image-resize` | Resizes an uploaded image |
235+
| `data-export` | Exports a data set to CSV/JSON |
236+
237+
To add a new task type, add a case to `executeTask()` in `internal/worker/worker.go`.
238+
239+
---
240+
241+
## Key Design Decisions
242+
243+
**Why SQS over a database queue?**
244+
SQS provides at-least-once delivery guarantees, built-in visibility timeouts, and native dead-letter queue support — without managing a separate queue infrastructure.
245+
246+
**Why DynamoDB?**
247+
Serverless, pay-per-request pricing, and predictable single-digit millisecond latency make it ideal for task status lookups. The GSI on `status` enables O(1) metric aggregation.
248+
249+
**Why goroutines over threads?**
250+
Go goroutines are extremely lightweight (~2KB stack vs ~1MB per OS thread). Running 5–50 concurrent workers is trivial in Go.
251+
252+
**Why long polling?**
253+
SQS long polling (20s) reduces empty receive calls by ~99% compared to short polling, lowering both latency and AWS costs.
254+
255+
---
256+
257+
## License
258+
259+
MIT

0 commit comments

Comments
 (0)