تصميم طوابير المهام الخلفية لمعالجة التقارير والعمليات الثقيلة
بناء معمارية معالجة غير تزامنية للمهام الشاقة والتقارير المالية عبر طوابير الرسائل واستعلامات التدفق والحماية من انهيار الذاكرة.
🧾 Generate Report: كيف تصمم Background Job بشكل محترم؟
<img src="https://img.shields.io/badge/System%20Design-Background%20Jobs-blue" alt="System Design Background Jobs"/> <img src="https://img.shields.io/badge/Queues-Reliability-green" alt="Queues and Reliability"/> <img src="https://img.shields.io/badge/Idempotency-Production%20Ready-orange" alt="Idempotency Production Ready"/> <img src="https://img.shields.io/badge/Interview-System%20Design-purple" alt="System Design Interview"/>سؤال انترفيو بسيط شكله... لكن وراه System Design كامل.
</div>User ضغط Generate Report، والتقرير بياخد 5 دقائق. هتعمل إيه؟
📚 Table of Contents
- السؤال
- الإجابة القصيرة
- ليه مينفعش نستنى داخل HTTP Request؟
- أسوأ حل: Fire-and-Forget
- هل Task.Run غلط دائمًا؟
- التصميم الأفضل
- 202 Accepted
- Job Lifecycle
- ReportJobs Table
- Queue
- Worker
- Retries
- At-Least-Once Delivery
- Dead Letter Queue
- Idempotency
- Duplicate Requests
- Idempotency Key
- Request Hash
- Unique Constraints
- Status Endpoint
- Polling vs Push Notifications
- File Storage
- Security
- Authorization
- Report Generation Optimization
- Memory Management
- Concurrency Limits
- Priority Queues
- Timeouts
- Cancellation
- Observability
- Failure Scenarios
- Race Conditions
- Transactional Outbox
- Architecture Diagram
- Interview Answer
- Common Wrong Answers
- Production Checklist
- Decision Framework
- الخلاصة
- Official Sources
🎤 السؤال
تخيل الـinterviewer قال لك:
User ضغط على زرار Generate Report.
التقرير بياخد 5 دقايق، بيقرأ data كتير، وبيطلع PDF أو Excel. هتعمل إيه؟
هنا فيه developer ممكن يقول:
هخلي الـHTTP request مستني لحد ما التقرير يخلص.
ودي غالبًا بداية تصميم سيئ.
⚡ الإجابة القصيرة
أنا مش هخلي الـHTTP request مستني 5 دقائق.
هعمل:
POST /reports
↓
Create ReportJob
↓
Enqueue Job
↓
202 Accepted
↓
Worker
↓
Generate Report
↓
Upload File Storage
↓
Mark Job Completed
↓
Notify User / Poll Status
والـjob نفسه يكون:
Durable
Trackable
Retryable
Idempotent
Observable
❌ ليه مينفعش نستنى داخل HTTP Request؟
لو عملت:
POST /reports
↓
Generate report
↓
5 minutes
↓
Return PDF
فأنت رابط الـrequest بعملية طويلة جدًا.
وده ممكن يعمل مشاكل مثل:
- User waits for minutes
- Browser may close
- Mobile app may background
- Network connection may fail
- Reverse proxy timeout
- Load balancer timeout
- Server resources stay occupied
- Connection stays open
- Poor user experience
- Harder retry behavior
وفوق ده، الـHTTP request نفسه مش أفضل مكان لإدارة long-running business work.
⏳ مشكلة الـTimeout
تخيل:
Client
↓
Load Balancer
↓
API
↓
Report Generator
ممكن يكون عندك timeout في أي layer:
Browser
Proxy
Load Balancer
API Gateway
Web Server
Database
فحتى لو report generation محتاج:
5 minutes
ممكن connection تموت قبل ما توصل للـ5 دقائق.
🔥 أسوأ حل: Fire-and-Forget
ناس كتير تعمل:
_ = Task.Run(async () =>
{
await GenerateReportAsync(reportId);
});
وتقول:
"خلاص، طلعت الشغل من الـrequest."
لكن السؤال:
الشغل راح فين؟
لسه جوه نفس process غالبًا.
ولو الـprocess مات:
App crashed
↓
Task disappeared
ولو السيرفر اتعمل له restart:
Restart
↓
In-memory task gone
وممكن كمان مايبقاش عندك:
Retry
Job status
Durable queue
Dead-letter handling
Operational visibility
⚠️ هل Task.Run غلط دائمًا؟
لا.
دي نقطة مهمة.
مش معنى إن:
Task.Run(...)
ممنوع تمامًا.
المشكلة هي استخدامه كـdurable background job system.
في تطبيق ASP.NET Core، ممكن تستخدم hosted/background services لسيناريوهات مناسبة، وMicrosoft نفسها توفر patterns للـqueued background tasks وBackgroundService. لكن الـin-process queue تظل مرتبطة بعمر الـprocess، وبالتالي لا تعتبر بديلًا تلقائيًا عن durable distributed queue لو فقدان الـjob غير مقبول. citeturn0search0turn0search5
يعني:
In-process background task
ممكن تكون مناسبة لبعض الحالات.
لكن:
Critical 5-minute report
+
Must survive restart
+
Must retry
غالبًا تحتاج durable job infrastructure.
🏗️ التصميم الأفضل
خلينا نفصل العملية.
Step 1 — User requests report
POST /reports
الـrequest يحتوي مثلًا:
{
"type": "sales",
"from": "2026-08-01",
"to": "2026-08-17",
"format": "xlsx",
"filters": {
"region": "cairo"
}
}
💾 Step 2 — Create Job
الـAPI تعمل record:
ReportJobs
مثلاً:
jobId: rpt_123
userId: 42
type: sales
format: xlsx
status: Pending
createdAt: ...
ثم تحاول enqueue الـjob.
🚀 Step 3 — Return 202 Accepted
بدل:
200 + PDF
ترجع:
202 Accepted
مثلاً:
{
"jobId": "rpt_123",
"status": "Pending",
"statusUrl": "/reports/rpt_123/status"
}
202 Accepted معناها إن السيرفر قبل الطلب للمعالجة، لكن المعالجة لم تكتمل بعد، والـHTTP response لا يحمل نتيجة نهائية للعملية. MDN توضح أيضًا أن الـ202 لا يضمن وحده أن المهمة ستنجح لاحقًا، لذلك تحتاج آلية tracking للحالة. citeturn0search1
🔄 Job Lifecycle
ممكن تكون الحالات:
Pending
↓
Queued
↓
Processing
↓
Completed
أو:
Pending
↓
Queued
↓
Processing
├──→ Completed
├──→ Failed
└──→ Cancelled
وممكن تضيف:
Retrying
Expired
🗄️ ReportJobs Table
مثال:
CREATE TABLE ReportJobs (
Id VARCHAR(100) PRIMARY KEY,
UserId BIGINT NOT NULL,
ReportType VARCHAR(100) NOT NULL,
Format VARCHAR(20) NOT NULL,
ParametersJson TEXT NOT NULL,
Status VARCHAR(30) NOT NULL,
Progress INT NULL,
Attempts INT NOT NULL DEFAULT 0,
StorageKey VARCHAR(500) NULL,
ErrorCode VARCHAR(100) NULL,
ErrorMessage TEXT NULL,
CreatedAt TIMESTAMP NOT NULL,
StartedAt TIMESTAMP NULL,
CompletedAt TIMESTAMP NULL
);
لكن الـschema الحقيقي يعتمد على:
Database
Business requirements
Retention
Security
Reporting needs
📬 Queue
بعد إنشاء الـjob:
ReportJobs
↓
Queue
الـmessage ممكن تكون صغيرة:
{
"jobId": "rpt_123"
}
مش لازم تحط:
1 million rows
داخل queue message. 😂
الأفضل إن الـworker يأخذ:
jobId
ثم يقرأ الـjob data من مصدر موثوق.
🧰 أمثلة Queues / Job Systems
حسب الـarchitecture ممكن تستخدم:
AWS
Amazon SQS
Azure
Azure Service Bus
Self-hosted / Infrastructure
RabbitMQ
.NET Job Processing
Hangfire
Quartz
Kafka
ممكن يكون مناسب في بعض event/streaming workloads، لكنه ليس تلقائيًا الاختيار الأفضل لكل background job.
اختيار queue يعتمد على workload وdelivery guarantees وordering وoperations، مش على الترند.
👷 Worker
الـworker يقرأ:
ReportJob
ثم:
1. Load job
2. Mark Processing
3. Generate report
4. Upload file
5. Save storage reference
6. Mark Completed
7. Notify user
مثلاً:
Queue
↓
Worker 1
↓
Generate
↓
Storage
↓
DB status
وممكن يكون عندك:
Worker 1
Worker 2
Worker 3
Worker N
وبكده تقدر تعمل horizontal scaling للـworkers.
🔁 Retries
افترض:
Worker
↓
Generate
↓
Database timeout
هل خلاص؟
لا.
ممكن تعمل:
Retry #1
Retry #2
Retry #3
لكن مهم جدًا:
مش كل error يستحق retry.
مثلاً:
Temporary network error
غالبًا retryable.
لكن:
Invalid report parameters
ممكن تكون permanent failure.
⏱️ Exponential Backoff
بدل:
Retry
Retry
Retry
Retry
بسرعة، استخدم backoff:
1s
2s
4s
8s
16s
وممكن تضيف:
Jitter
عشان تمنع ملايين workers من إعادة المحاولة في نفس اللحظة.
♻️ At-Least-Once Delivery
دي نقطة مهمة جدًا في distributed systems.
الـqueue غالبًا بتصمم على أساس:
At-least-once delivery
يعني الـmessage ممكن توصل للworker أكثر من مرة.
وده معناه:
Same job
↓
Worker A
↓
Crash before acknowledgment
↓
Queue redelivers
↓
Worker B
فلازم الـworker يكون:
Idempotent
Azure Service Bus توضح أن نمط PeekLock يوفر at-least-once delivery، وبالتالي duplicate processing ممكن يحصل، وتوصي بجعل consumer processing idempotent، مع duplicate detection عند الحاجة. citeturn0search2turn1search3
وAmazon SQS كذلك تستخدم visibility timeout، وإذا لم يتم حذف الرسالة قبل انتهاء الـtimeout يمكن أن تصبح متاحة مرة أخرى لمعالجة أخرى؛ كما توصي باستخدام DLQ لمعالجة حالات الفشل المتكرر. citeturn1search0turn1search12
🧠 Idempotency
يعني:
لو نفس العملية اتنفذت أكثر من مرة، النتيجة النهائية ما تتكرر بشكل خاطئ.
مثلاً:
Generate Report
لو اتنفذت مرتين بالغلط:
❌ report_1.xlsx
❌ report_2.xlsx
ممكن تكون مشكلة.
الأفضل:
same logical request
↓
same job
أو على الأقل:
same logical job
↓
same final state
🖱️ Duplicate Requests
المستخدم ضغط:
Generate
ثم:
Generate
بسبب lag.
أو frontend عمل retry.
أو:
Network timeout
فالـfrontend مش عارف هل الطلب وصل ولا لأ.
فبعت تاني.
لو مش عامل idempotency:
Click 1 → rpt_123
Click 2 → rpt_124
Click 3 → rpt_125
ممكن تضغط:
DB
+
Queue
+
Workers
+
Storage
بدون داعي.
🔑 Idempotency Key
حل ممتاز:
POST /reports
Idempotency-Key: 8d4f...
السيرفر يحتفظ بالمفتاح والنتيجة/الjob المرتبط به لفترة مناسبة.
لو نفس الطلب اتكرر:
Same Idempotency-Key
↓
Return existing job
بدل:
Create new job
Stripe تستخدم هذا pattern لحماية عمليات الإنشاء والتحديث من تكرار التنفيذ عند retries أو network errors، وتوضح أن إعادة استخدام نفس idempotency key تعيد نتيجة الطلب السابق بدل إنشاء عملية ثانية. citeturn1search4
🧮 Request Hash
ممكن بدل أو بجانب idempotency key تعمل fingerprint للطلب:
hash(
userId
+
reportType
+
filters
+
dateRange
+
format
)
مثلاً:
requestHash =
SHA256(canonicalRequest)
ثم:
User 42
+
Sales
+
August
+
Cairo
+
XLSX
ينتج:
abc123...
لو فيه job بنفس الـfingerprint:
Pending
Processing
Completed
ممكن ترجع الـexisting job حسب الـbusiness rules.
🧱 Unique Constraints
ممكن كمان تستخدم database constraint.
مثلاً conceptual:
UNIQUE(
userId,
requestFingerprint
)
وده مهم جدًا لأن:
Check if exists
↓
Create
لو اتعملوا منفصلين ممكن يحصل race condition:
Request A checks → not found
Request B checks → not found
A creates
B creates
💥 duplicate
الـdatabase constraint أقوى من مجرد check في application code.
⚔️ Idempotency ≠ Duplicate Detection
فرق مهم:
Queue duplicate detection
مش بالضرورة يحل:
Business duplicate
ومش بالضرورة يمنع:
Worker processing twice
Azure نفسها توضح أن duplicate detection على مستوى الرسائل لا يغني عن idempotent consumer processing. citeturn1search3turn1search1
يعني الأفضل:
API idempotency
+
DB constraints
+
Idempotent worker
+
Queue duplicate handling
حسب الـsystem.
☠️ Dead Letter Queue
طيب لو job فشل:
Retry 1
Retry 2
Retry 3
Retry 4
...
مينفعش تفضل تلف إلى الأبد.
بعد عدد معين:
Main Queue
↓
Retry
↓
Retry
↓
Retry
↓
DLQ
Dead Letter Queue تخزن الرسائل التي فشلت بشكل متكرر حتى يتم فحصها أو إعادة معالجتها.
Amazon SQS وAzure Service Bus يدعمان DLQ patterns لهذا الغرض. citeturn1search12turn1search13
📡 Status Endpoint
الـfrontend ممكن يعمل:
GET /reports/rpt_123/status
والـbackend يرجع:
{
"jobId": "rpt_123",
"status": "Processing",
"progress": 64
}
أو:
{
"jobId": "rpt_123",
"status": "Completed",
"downloadUrl": "..."
}
🔄 Polling
أبسط حل:
Frontend
↓
GET /status
↓
wait
↓
GET /status
↓
wait
↓
GET /status
لكن متعملش:
GET /status
every 50ms
😂
استخدم interval منطقي، وممكن exponential backoff أو adaptive polling.
🔔 Polling vs Push Notifications
بدل polling ممكن تستخدم:
WebSocket
SSE
Push Notification
Email
Webhook
مثلاً:
Worker completes
↓
Notification service
↓
WebSocket
↓
Frontend
↓
"Your report is ready"
والـpolling يفضل مفيد جدًا كـfallback.
☁️ File Storage
بعد توليد:
PDF
XLSX
CSV
متخليش الـworker يحتفظ بالملف في memory أو disk للأبد.
استخدم object storage مثل:
Amazon S3
Azure Blob Storage
Google Cloud Storage
ثم في ReportJobs تخزن:
storageKey
مثلاً:
reports/2026/08/rpt_123.xlsx
🔐 متديش Public URL للأبد
بدل:
https://storage.com/my-report.xlsx
ممكن تستخدم:
Short-lived signed URL
مثل:
Presigned URL
أو:
SAS URL
بحسب storage provider.
الفكرة:
User authorized
↓
API verifies ownership
↓
Generate temporary download URL
↓
User downloads
🔒 Security
التقرير ممكن يحتوي:
Customer data
Financial data
Personal data
Business data
فلازم تهتم بـ:
- Authorization
- Storage access
- Encryption
- Signed URLs
- URL expiration
- Audit logs
- Data retention
- File type validation
- Access control
وأهم نقطة:
وجود
jobIdوحده لا يعني إن أي user يقدر يشوف job.
لازم:
GET /reports/rpt_123/status
يتحقق:
Does this job belong to current user?
👮 Authorization
غلط:
GET /reports/rpt_123
ويرجع التقرير لأي حد عنده ID.
صح:
Current User
↓
Authorization
↓
Owns report?
↓
Yes → continue
No → 403 / 404 حسب policy
📈 Report Generation Optimization
هنا نقطة مهمة جدًا:
Background Job لا يجعل العمل نفسه أسرع.
هو يحسن:
UX
+
Resilience
+
Request responsiveness
لكن لو عندك:
SELECT *
FROM Orders;
وتجيب:
10 million rows
الـqueue مش هتحل المشكلة. 😂
🧠 Memory Management
أسوأ تصميم:
Load 5 million rows
↓
Put all in RAM
↓
Generate XLSX
ممكن يعمل:
Memory pressure
GC pressure
OOM
الأفضل حسب الـlibrary والـdatabase:
Streaming
Batching
Pagination
Projection
Chunk processing
مثلاً:
100k rows
↓
process
↓
write
↓
release
↓
next 100k
🗄️ Database Optimization
اسأل:
هل query indexed؟
هل SELECT يرجع columns محتاجها فقط؟
هل فيه joins ضخمة؟
هل فيه N+1؟
هل التقرير محتاج snapshot؟
هل البيانات ممكن تتغير أثناء generation؟
وممكن تحتاج:
Read replica
Materialized view
Pre-aggregation
Dedicated reporting database
Data warehouse
لو workload كبير جدًا.
🚦 Concurrency Limits
تخيل:
1000 users
×
5-minute report
هل تشغل:
1000 workers
مرة واحدة؟
غالبًا لا.
ممكن تعمل:
Queue
↓
10 workers
أو:
Priority Queue
+
Concurrency limit
عشان تحمي:
CPU
Memory
Database
Storage
🎚️ Priority Queues
ممكن يكون عندك:
High Priority
Normal Priority
Low Priority
مثلاً:
Admin urgent report
↓
High
Normal user
↓
Normal
Large historical export
↓
Low
لكن لازم تستخدمها بحذر عشان الـlow priority jobs مايحصلهاش starvation.
⏰ Timeouts
حتى الـbackground jobs تحتاج timeout.
مثلاً:
Expected:
5 minutes
لكن:
Job running:
3 hours
لازم تسأل:
Is it stuck?
Database blocked?
Deadlock?
External service?
Memory issue?
ممكن يكون عندك:
Job timeout
وبعدها:
Cancel
Retry
DLQ
Manual investigation
حسب نوع الخطأ.
🛑 Cancellation
المستخدم ممكن يقول:
"Cancel report."
تحتاج:
POST /reports/rpt_123/cancel
والـjob:
Pending
↓
Cancelled
ولو Processing:
Processing
↓
Cancellation requested
↓
Worker checks token
↓
Stop safely
لكن cancellation في distributed systems مش زر سحري.
لازم كل أجزاء العملية تكون cooperative قدر الإمكان.
👀 Observability
لو عندك:
1000 reports
لازم تعرف:
How many pending?
How many processing?
How many failed?
Average generation time?
p95 generation time?
Retry count?
DLQ count?
Queue depth?
Oldest job age?
📊 Metrics مهمة
Queue
Queue depth
Oldest message age
Enqueue rate
Dequeue rate
Retry count
DLQ count
Worker
Jobs processed
Jobs failed
Processing duration
CPU
Memory
Concurrency
Report
Generation duration
Rows processed
File size
Success rate
Failure rate
API
POST /reports latency
Status endpoint latency
Error rate
202 rate
🔍 Distributed Tracing
لو عندك:
API
↓
DB
↓
Queue
↓
Worker
↓
Storage
↓
Notification
محتاج trace يربط العملية كلها.
مثلاً:
traceId = abc123
ويظهر في:
API logs
DB logs
Worker logs
Storage operation
Notification
عشان لو job فشل بعد 4 دقائق تعرف:
فين فشل؟
💥 Failure Scenarios
لازم تفكر في:
1. API crashes
قبل enqueue.
2. Queue unavailable
بعد إنشاء DB record.
3. Worker crashes
أثناء generation.
4. Database unavailable
أثناء report.
5. Storage unavailable
بعد generation.
6. Notification fails
بعد report completion.
7. Duplicate message
الworker يشغل نفس job مرتين.
8. User retries request
Job جديد بالغلط.
9. Huge report
Memory / timeout issue.
10. Poison job
Job يفشل كل مرة.
⚔️ Race Conditions
من أخطر السيناريوهات:
Request A
Request B
الاتنين في نفس اللحظة:
Check existing job
الاثنين يلاقوا:
Not found
ثم:
A creates job
B creates job
الحل:
Database uniqueness
+
Idempotency
+
Atomic operation
مش مجرد:
if (!exists)
{
create();
}
🧱 Transactional Outbox
دي نقطة System Design متقدمة جدًا.
ممكن تحصل مشكلة:
DB transaction commits
↓
Queue publish fails
فتلاقي:
ReportJob exists
لكن:
No queue message
والـjob عالق.
أو العكس:
Queue message sent
DB transaction rolls back
فتلاقي worker عنده job مش موجود بشكل صحيح في DB.
حل مشهور:
Transactional Outbox
الفكرة:
DB Transaction
├── ReportJob
└── OutboxEvent
الاتنين يتكتبوا في نفس transaction.
بعدها:
Outbox Publisher
↓
Queue
فيبقى عندك reliability أفضل بين:
Database
+
Message Broker
🗺️ Architecture Diagram
flowchart LR
U["User / Frontend"]
API["API Server"]
DB[("Database\nReportJobs")]
Q[["Durable Queue"]]
W["Worker Pool"]
S[("Object Storage\nPDF / XLSX")]
N["Notification Service"]
OBS["Monitoring / Logs / Traces"]
U -->|"POST /reports"| API
API -->|"Create Job"| DB
API -->|"Enqueue jobId"| Q
API -->|"202 Accepted"| U
Q --> W
W -->|"Read / Update"| DB
W -->|"Generate"| S
W -->|"Completed / Failed"| DB
W --> N
N --> U
API --> OBS
W --> OBS
Q --> OBS
DB --> OBS
🔄 Complete Flow
User
↓
POST /reports
↓
Validate request
↓
Check Idempotency
↓
Create ReportJob
↓
Enqueue job
↓
202 Accepted
↓
Frontend tracks status
↓
Worker consumes job
↓
Mark Processing
↓
Query data
↓
Generate PDF/XLSX
↓
Upload object
↓
Mark Completed
↓
Notify user
↓
Frontend downloads file
📦 Example API
Create
POST /reports
Idempotency-Key: 6f3d0...
Content-Type: application/json
Response:
202 Accepted
{
"jobId": "rpt_123",
"status": "Pending",
"statusUrl": "/reports/rpt_123/status"
}
Status
GET /reports/rpt_123/status
Processing:
{
"jobId": "rpt_123",
"status": "Processing",
"progress": 62
}
Completed:
{
"jobId": "rpt_123",
"status": "Completed",
"downloadUrl": "/reports/rpt_123/download"
}
Failed:
{
"jobId": "rpt_123",
"status": "Failed",
"errorCode": "REPORT_GENERATION_FAILED"
}
🧠 هل أرجع Download URL مباشرة؟
لو التقرير جاهز بالفعل:
Yes
ممكن.
لكن لو لسه بيتولد:
No
ترجع:
jobId
+
status URL
وبعد completion:
temporary download URL
🎯 Interview Answer
لو الـinterviewer سأل:
"عندك endpoint بيولد Excel report بياخد 4 دقائق وفيه users كتير بيطلبوه. هتعمل إيه؟"
ممكن تجاوب:
"مش هخلي الـHTTP request مستني 4 دقائق. هحول العملية إلى asynchronous job. الـAPI هتعمل validation، وتتعامل مع idempotency، وتسجل ReportJob بحالة Pending، وبعدها تحط jobId في durable queue وترجع 202 Accepted مع jobId وstatus endpoint. Worker منفصل هيستهلك الـjob، ويولد التقرير، ويحفظه في object storage، ويحدث الحالة إلى Completed أو Failed. هستخدم retries للـtransient failures وDLQ للـpoison jobs، وهخلي processing idempotent لأن queue delivery قد تكون at-least-once. كمان هحدد concurrency limits عشان ما أضغطش الـdatabase والـCPU، وهراقب queue depth وprocessing time وfailure rate. ولو عندي مشكلة atomicity بين إنشاء الـjob والـenqueue، ممكن أستخدم Transactional Outbox."
دي إجابة فيها:
API Design
+
Async Processing
+
Queue
+
Reliability
+
Idempotency
+
Scalability
+
Observability
+
Database Design
وده اللي يخلي الإجابة System Design مش مجرد:
Task.Run()
❌ Common Wrong Answers
❌ "هخلي request مستني"
المشكلة:
Timeout
+
Poor UX
+
Long connection
+
Resource consumption
❌ "هعمل Task.Run"
المشكلة:
In-process
+
No durable queue
+
Process restart risk
+
Weak retry/tracking
❌ "هحطها في BackgroundService وخلاص"
أفضل من fire-and-forget في بعض الحالات، لكن لو الـjob critical ويجب ألا يضيع، فالـin-process background service وحده لا يعطيك durable queue semantics عند process failure. Microsoft توضح أن hosted services تعمل داخل application host وأن العمليات قد لا تستمر إذا فشل الـprocess بشكل مفاجئ. citeturn0search0
❌ "هستخدم Redis"
السؤال:
Redis ليه؟
هل:
Cache?
Queue?
Lock?
Broker?
اختيار technology لازم يجي بعد requirements.
❌ "هعمل Retry وخلاص"
Retry بدون:
Backoff
+
Max attempts
+
Idempotency
+
DLQ
ممكن يحول failure إلى storm.
❌ "هعمل 1000 worker"
ده ممكن يدمر:
Database
CPU
Memory
Storage
External services
لازم:
Concurrency limit
❌ "هحفظ Excel في database"
ممكن في حالات معينة، لكن object storage غالبًا أنسب للملفات الكبيرة.
الأفضل غالبًا:
DB
↓
Metadata
Object Storage
↓
File
🏗️ Production Checklist
API
- Return
202 Accepted - Validate request
- Idempotency
- Authorization
- Job ID
- Status endpoint
Database
- ReportJobs table
- Unique constraint where appropriate
- Job status
- Attempts
- Timestamps
- Error metadata
- Retention policy
Queue
- Durable queue
- Retry policy
- Visibility / lock timeout
- Dead-letter queue
- Backoff
- Monitoring
Worker
- Idempotent processing
- Concurrency limit
- Cancellation
- Timeout
- Error handling
- Graceful shutdown
Report
- Efficient queries
- Proper indexes
- Streaming / batching where appropriate
- Memory limits
- File size limits
- Format validation
Storage
- Object storage
- Private objects
- Temporary signed URLs
- Retention / cleanup
- Encryption where required
Observability
- Queue depth
- Job duration
- Success rate
- Failure rate
- Retry count
- DLQ count
- Worker health
- Tracing
- Alerts
🧭 Decision Framework
flowchart TD
A["Long-running operation"] --> B{"Can user wait synchronously?"}
B -->|Yes| C["Synchronous request may be enough"]
B -->|No| D["Asynchronous Job"]
D --> E{"Must survive process restart?"}
E -->|No| F["In-process Background Service may be enough"]
E -->|Yes| G["Durable Queue + Worker"]
G --> H{"Can duplicate execution happen?"}
H -->|Yes| I["Idempotent Worker"]
I --> J{"Can jobs fail repeatedly?"}
J -->|Yes| K["Retries + Backoff + DLQ"]
G --> L{"DB + Queue atomicity important?"}
L -->|Yes| M["Consider Transactional Outbox"]
G --> N["Status Tracking + Observability"]
N --> O["Object Storage + Notification"]
🧠 أهم نقطة في المقال
Background Job مش معناها:
"شيلت الكود من HTTP request."
معناها إنك صممت:
Durable Work
+
State
+
Delivery
+
Retry
+
Failure Handling
+
Idempotency
+
Observability
لأن السؤال الحقيقي مش:
"إزاي أشغل الكود في الخلفية؟"
السؤال الحقيقي:
"إزاي أضمن إن الشغل ده هيتنفذ بشكل موثوق حتى لو الدنيا وقعت؟"
🔥 الخلاصة
لو عندك:
Generate Report
↓
5 minutes
متعملش:
HTTP Request
↓
Wait 5 minutes
↓
Return
ومتقولش:
Task.Run()
وخلاص.
فكر كده:
User
↓
API
↓
Idempotency
↓
ReportJob
↓
Durable Queue
↓
Worker
↓
Generate
↓
Object Storage
↓
Update Status
↓
Notify
ومع كل ده:
Retry
+
Backoff
+
DLQ
+
Idempotency
+
Concurrency limits
+
Monitoring
والـbackground job مش هيخلي التقرير نفسه أسرع.
هو بيخلي النظام:
More responsive
+
More resilient
+
More scalable
+
More observable
ولو التقرير نفسه بطيء، لازم تصلح:
Queries
+
Indexes
+
Memory usage
+
File generation
+
Concurrency
🏆 Mental Model
احفظها كده:
Long Task
↓
Don't block HTTP
↓
Create Job
↓
Persist State
↓
Durable Queue
↓
Worker
↓
Idempotent Processing
↓
Retry / DLQ
↓
Store Result
↓
Notify / Poll
وأهم سؤال System Design تسأله لنفسك:
"ماذا يحدث لو الـprocess وقع بعد ثانية واحدة من بداية الشغل؟"
لو إجابتك:
"الـjob هيضيع."
يبقى عندك مشكلة.
لو إجابتك:
"الـjob persisted، والqueue هتخليه يرجع، والworker idempotent، والفشل هيروح retry ثم DLQ."
أنت بدأت تفكر كـSystem Engineer. 🚀
📚 Official Sources
Microsoft / .NET
- Microsoft Learn — Background tasks with hosted services in ASP.NET Core
- Microsoft Learn — Worker Services in .NET
- Microsoft Learn — Azure Service Bus: prevent message loss and duplicate processing
- Microsoft Learn — Azure Service Bus duplicate detection
- Microsoft Learn — Azure Service Bus dead-lettering
AWS
- AWS Documentation — Amazon SQS visibility timeout
- AWS Documentation — Amazon SQS Dead-Letter Queues
- AWS Documentation — SQS outage recovery
HTTP
API Idempotency
<div align="center">
🚀 Don't just run the job.
Design the system around the job.
Persist → Queue → Process → Retry → Observe → Complete
</div>منشورات مقترحة
مشاريع ذات صلة

منظومة أتمتة لتتبع الفرص الوظيفية في منصات العمل الحر وإرسال تنبيهات لحظية عبر الواتساب.