1. Niyə ümumiyyətlə ChatGPT App‑də asinxron tapşırıqlar
Dünya ideal olsaydı, istənilən MCP alətiniz bir neçə yüz millisekundda cavab verərdi. Amma həyatda maraqlı olan hər şey — uzun və ağırdır:
- istifadəçinin alış tarixçəsi olan böyük CSV faylının təhlili;
- bir neçə xarici API-dən məlumatların aqreqasiyası, onların hər biri gah yatır, gah da 503 ilə cavab verir;
- çoxlu aralıq addımlarla mürəkkəb tövsiyələrin qurulması;
- böyük hesabatların və təqdimatların yaradılması.
Bunu bir sinxron tool‑call-a sığışdırmağa çalışsanız, üç problemə dirənəcəksiniz.
Birincisi, timeoutlar. ChatGPT sessiyası, HTTP infrastrukturu, MCP müştərisi — bunların heç biri «beş dəqiqədən sonra» cavablara hesablanmayıb. Çox uzun müddət əlaqəni saxlayan server həm ChatGPT, həm də istifadəçi üçün «asılıb qalmış» görünəcək.
İkincisi, yüklənmənin idarə olunması. Əgər yüz istifadəçi eyni anda «Yeni il üçün super analiz»i işə salsalar, MCP serverin HTTP axınlarında yüz uzun tapşırığı sinxron saxlamasını istəmirsiniz. Sizə pikləri qəbul edən, tapşırıqları növbəyə paylayan və onları bir neçə worker ilə emal edən ara qat lazımdır.
Üçüncüsü, UX. İstifadəçi GiftGenius widget-ində düyməni sıxdı və bir 40 saniyəlik spinnerə baxır — təxminən köhnə internet bankları kimi hiss olunur. «Sürətli cavab + proqres + ləğv etmə imkanı» modeli daha xoşdur.
Bu problemlər ümumi sxemlə həll olunur: «başlat → növbə → fon → hadisələr».
2. MCP kontekstində async‑job‑un əsas arxitekturası
GiftGenius-u götürək. Tutaq ki, bizdə yeni ağır ssenari var: «Alış tarixçəsi və dostun sosial şəbəkələri üzrə üstünlüklərin dərin təhlili». Belə bir şey bir neçə dəqiqə işləyə bilər, ona görə:
- MCP aləti (tool) modeldən sorğunun parametrlərini qəbul edir.
- Hər şeyi dərhal hesablamaq əvəzinə, bazada Job qeydi yaradır.
- Tapşırığı növbəyə qoyur.
- ChatGPT-yə dərhal cavab verir: «Təhlil başladıldı, budur jobId».
- Fon worker-i tapşırığı növbədən götürür, ağır işi yerinə yetirir, bu müddətdə MCP hadisələri göndərir: job.progress və job.partial, sonda — job.completed və ya job.failed.
Arxitektura baxımından bu təxminən belə görünür:
flowchart LR
subgraph ChatGPT
U[İstifadəçi] --> GPT[Model + ChatGPT UI]
end
GPT -->|call_tool analyze_preferences| MCP[MCP server]
subgraph Backend
MCP -->|Job yarat| DB[(Job DB-si)]
MCP -->|enqueue| Q[Növbə]
W[Worker] -->|take job| Q
W -->|update status/progress| DB
W -->|MCP events: job.progress/job.completed| MCP
end
MCP -->|SSE events| GPT
Vacib fikir: MCP server mütləq monolit deyil. Çox vaxt o, sizin daxili asinxron infrastrukturunuzun fasadı kimi çıxış edir: tool‑call-ları qəbul edir, job-lar yaradır və hadisələr göndərir, ağır işi isə ayrı proses‑workerlər görür.
3. Asinxron tapşırıq üçün məlumat modeli
Sadə bir Job modeli ilə başlayaq. Stack-ə necə oturduğunu dərhal görəsiniz deyə TypeScript və şərti Node/MCP serverdən istifadə edəcəyik.
Ən sadə model yaddaşda/BD‑də belə görünə bilər:
// openai/jobs/model.ts
export type JobStatus =
| 'pending'
| 'in_progress'
| 'completed'
| 'failed'
| 'canceled';
export interface GiftJob {
id: string; // jobId
type: 'deep_gift_analysis';
status: JobStatus;
payload: {
recipientProfile: string; // profil mətni/ID-si
budget: number;
};
result?: unknown; // yekun tövsiyələr
error?: string; // xəta səbəbi
attempts: number; // neçə dəfə icra etməyə cəhd olunub
createdAt: Date;
updatedAt: Date;
}
Real layihədə GiftJob-u Postgres, DynamoDB, Firestore və s.-də saxlayacaqsınız, amma mühazirə üçün vacib sahələr bunlardır:
- status — tapşırığın cari vəziyyəti; həm hadisələrdə, həm də UX‑də əks olunacaq;
- attempts — retry üçün sayğac;
- error — loglar və sazlama üçün;
- payload — worker-in emal üçün istifadə etdiyi giriş məlumatları.
4. Async‑job yaradan MCP aləti
start_deep_analysis alətini təsəvvür edək. Əvvəllər o, hər şeyi sinxron edə bilərdi, indi isə sadəcə tapşırığı növbəyə qoyur və jobId qaytarır.
// openai/tools/startDeepAnalysis.ts
import { v4 as uuid } from 'uuid';
import { createJobAndEnqueue } from '../jobs/queue';
// MCP SDK üçün psevdotiplər
type StartDeepAnalysisInput = {
recipientProfile: string;
budget: number;
};
type StartDeepAnalysisOutput = {
jobId: string;
message: string;
};
export async function startDeepAnalysisTool(
input: StartDeepAnalysisInput
): Promise<StartDeepAnalysisOutput> {
const jobId = uuid();
await createJobAndEnqueue({
id: jobId,
type: 'deep_gift_analysis',
status: 'pending',
payload: {
recipientProfile: input.recipientProfile,
budget: input.budget,
},
attempts: 0,
createdAt: new Date(),
updatedAt: new Date(),
});
return {
jobId,
message: `Dərin təhlili başladım. Tapşırığın ID-si: ${jobId}. Hazır olduqca yeniləmələr göndərəcəyəm.`,
};
}
Burada önəmlidir ki:
- MCP aləti sürətli işləyir: maksimum bir-iki DB/növbə sorğusu;
- o, jobId ilə strukturlu cavab qaytarır; ChatGPT bunu «istifadəçiyə izah»da istifadə edə, GiftGenius widget-i isə onu öz widgetState-ində saxlaya bilər.
Bu alət üçün sizin JSON Schema sadəcə jobId-ni sətir, message-i isə insan üçün oxunaqlı mətn kimi təsvir edir — model bunun tapşırıq identifikatoru olduğunu anlayacaq və dialoqun növbəti addımlarında ona istinad edə biləcək.
5. Sadə növbə və worker: tədris versiyası
İndi Redis, RabbitMQ və s. gətirməyək, sadələşdirilmiş in‑memory növbə quraq. Real production-da, əlbəttə, bu ayrıca servis olacaq (SQS/BullMQ/Cloud Tasks və s.), amma məntiq eyni qalır.
Əvvəlcə növbənin blankı:
// openai/jobs/queue.ts
import type { GiftJob } from './model';
const jobs = new Map<string, GiftJob>(); // yaddaşda "DB"
export const queue: string[] = []; // id üzrə sadələşdirilmiş növbə
export async function createJobAndEnqueue(job: GiftJob) {
jobs.set(job.id, job);
queue.push(job.id);
}
export function getJob(id: string): GiftJob | undefined {
return jobs.get(id);
}
export function updateJob(id: string, patch: Partial<GiftJob>) {
const job = jobs.get(id);
if (!job) return;
const updated: GiftJob = { ...job, ...patch, updatedAt: new Date() };
jobs.set(id, updated);
}
İndi isə periodik olaraq növbəyə baxan, job götürən və onu emal edən primitiv worker:
// openai/jobs/worker.ts
import { getJob, updateJob } from './queue';
import { emitJobEvent } from './events';
async function processJob(jobId: string) {
const job = getJob(jobId);
if (!job) return;
updateJob(jobId, { status: 'in_progress' });
await emitJobEvent(jobId, 'job.started', {});
try {
// Burada uzun biznes məntiqini çağırırıq
const result = await doDeepGiftAnalysis(job.id, job.payload);
updateJob(jobId, { status: 'completed', result });
await emitJobEvent(jobId, 'job.completed', { resultSummary: summarize(result) });
} catch (err) {
updateJob(jobId, {
status: 'failed',
error: (err as Error).message,
});
await emitJobEvent(jobId, 'job.failed', { error: 'Internal error' });
}
}
Və tətbiq startında haradasa işə sala biləcəyiniz «dövri» worker:
// openai/jobs/workerLoop.ts
import { queue } from './queue';
import { processJob } from './worker';
export function startWorkerLoop() {
setInterval(async () => {
const jobId = queue.shift(); // normalda race condition-lara qarşı qoruma lazımdır
if (!jobId) return;
await processJob(jobId);
}, 1000); // növbəni saniyədə bir yoxlayırıq
}
Bu tədris nümunəsidir. Real dünyada setInterval əvəzinə yeni mesaj gələndə worker-i «oyadan» normal növbə olacaq. Amma ümumi fikir aydındır: worker MCP alətindən ayrıdır, fon rejimində işləyir və MCP server ilə hadisələr vasitəsilə ünsiyyət qurur.
6. Worker‑dən MCP hadisələrinin yaradılması
Əvvəlki mühazirələrdə siz artıq MCP hadisələrinin formatını görmüsünüz: tip (type), unikal event_id, timestamp, job_id və payload. İndi göstərəcəyik ki, worker necə emitJobEvent helper-ini çağırır, o isə hadisələri MCP serverin SSE kanalı ilə ChatGPT-yə çatdırır.
Sadə helper nümunəsi:
// openai/jobs/events.ts
import { randomUUID } from 'crypto';
import { sendMcpEvent } from '../mcp/eventBus';
export async function emitJobEvent(
jobId: string,
type: 'job.started' | 'job.progress' | 'job.completed' | 'job.failed',
payload: unknown
) {
const event = {
event_id: randomUUID(),
type,
job_id: jobId,
timestamp: new Date().toISOString(),
payload,
};
await sendMcpEvent(event);
}
sendMcpEvent isə MCP serverin içində SSEServerTransport-a bu hadisəni necə ötürəcəyini artıq bilir: məsələn, lokal hadisə şinindən və ya Redis Pub/Sub-dan keçərək — bunu 12‑ci modulda izah etmişdik.
Başlıca fikir: worker birbaşa ChatGPT ilə danışmır. O, MCP server ilə danışır, server isə SSE əlaqələrini saxlayır və hadisələri müştərilərə ötürür.
7. Worker‑dən proqres və qismən nəticələr
İndi ən maraqlı hissə: proqres və qismən nəticələr. GiftGenius-da uzun təhlili mərhələlərə bölmək olar:
- məlumatların toplanması və normallaşdırılması;
- baza seqmentlərinin qurulması;
- ilkin hədiyyə ideyalarının generasiyası;
- son reytinqləmə və mətn izahı.
Hər mərhələdə job.progress və bəzən — UI ilk hədiyyələri göstərsin deyə job.partial göndərə bilərik.
Şərti worker:
async function doDeepGiftAnalysis(jobId: string, payload: GiftJob['payload']) {
await emitJobEvent(jobId, 'job.progress', { step: 1, totalSteps: 4 });
const normalized = await collectAndNormalizeData(payload);
await emitJobEvent(jobId, 'job.progress', { step: 2, totalSteps: 4 });
const roughGifts = await generateInitialGifts(normalized);
await emitJobEvent(jobId, 'job.partial', { gifts: roughGifts.slice(0, 3) });
await emitJobEvent(jobId, 'job.progress', { step: 3, totalSteps: 4 });
const finalGifts = await rerankAndBeautify(roughGifts);
await emitJobEvent(jobId, 'job.progress', { step: 4, totalSteps: 4 });
return finalGifts;
}
Widget hadisələri dinləyərək əvvəlcə «Hələ dəqiqləşdiririk» qeydi ilə 3 «qaralama» hədiyyəni göstərə, job.completed-dən sonra isə siyahını yeniləyib yüklənmə indikatorunu götürə bilər. Bunların hamısı 3‑cü mühazirədə danışdığımız UX nümunələrinə əla oturur.
8. Worker‑lər üçün retry məntiqi
İndi ən həssas yer: xətalar və təkrar cəhdlər.
Təsəvvür edin ki, worker tapşırığı emal edərkən xarici məhsul siyahısı API-sinə müraciət edir, o isə vaxtaşırı 500 və ya 429 qaytarır. İlk xətadan sonra tapşırığı atmaq məntiqli deyil. Amma sonsuz təkrar cəhd etmək də olmaz: bu, özünüzə və ya kənar servislərə DDoS yarada bilər.
Bizə eksponensial gecikmə və cəhd sayının məhdudiyyəti olan retry strategiyası lazımdır.
Sadələşdirilmiş, kurs boyunca da lazım olacaq xəta təsnifatı:
- müvəqqəti (transient) — timeoutlar, 500, 503, 429;
- davamlı (permanent) — yalnış giriş, mövcud olmayan resurs;
- fatal (bug) — kod xətaları, TypeError, gözlənilməz istisna.
Yalnız müvəqqəti xətaları təkrarlamaq məntiqlidir. Qalanları dürüstcə 'failed' kimi işarələmək lazımdır.
Sadələşdirək və helper yazaq:
// openai/jobs/retry.ts
export function shouldRetry(error: unknown): boolean {
if (!(error instanceof Error)) return false;
// Şərti olaraq: HTTP 5xx və ya 429
return /5\d\d|429/.test(error.message);
}
export function getDelayMs(base: number, attempt: number): number {
const jitter = Math.random() * 100; // kiçik jitter
return base * 2 ** attempt + jitter; // eksponensial backoff
}
İndi GiftJob-dakı attempts-i nəzərə alacaq şəkildə worker-i yeniləyək:
// openai/jobs/worker.ts
import { getJob, updateJob } from './queue';
import { emitJobEvent } from './events';
import { shouldRetry, getDelayMs } from './retry';
const MAX_ATTEMPTS = 5;
export async function processJob(jobId: string) {
const job = getJob(jobId);
if (!job) return;
updateJob(jobId, { status: 'in_progress' });
try {
const result = await doDeepGiftAnalysis(job.id, job.payload);
updateJob(jobId, { status: 'completed', result });
await emitJobEvent(jobId, 'job.completed', {
resultSummary: summarize(result),
});
} catch (err) {
const attempts = job.attempts + 1;
const error = err as Error;
if (attempts <= MAX_ATTEMPTS && shouldRetry(error)) {
const delay = getDelayMs(1000, attempts); // 1s,2s,4s...
updateJob(jobId, { attempts, status: 'pending', error: error.message });
setTimeout(() => {
// Real növbədə işi gecikmə ilə yenidən enqueue edərdiniz
processJob(jobId);
}, delay);
await emitJobEvent(jobId, 'job.progress', {
retry: attempts,
nextAttemptInMs: delay,
});
} else {
updateJob(jobId, { status: 'failed', error: error.message });
await emitJobEvent(jobId, 'job.failed', {
error: 'Bir neçə cəhddən sonra təhlili başa çatdırmaq mümkün olmadı',
});
}
}
}
Burada bir neçə məqam vacibdir.
Birincisi, attempts birbaşa tapşırıqda saxlanılır — bu, həm loqlaşdırma, həm də müşahidəlilik üçün rahatdır (qrafikdə retry ilə keçən tapşırıqların sayı gözəl görünəcək).
İkincisi, hər retry zamanı bu, N‑ci cəhd olduğunu açıq göstərən job.progress göndəririk. Model bu məlumatdan istifadə edərək istifadəçiyə «hədiyyə serveri qeyri-sabit cavab verir, bir daha yoxlayıram» kimi izah edə bilər.
Üçüncüsü, istənilən halda ya job.completed, ya da job.failed göndərildiyini təmin edirik. «Nə ölü, nə diri» asılı qalan tapşırıqlar olmamalıdır.
Ləğv ('canceled') — başqa bir önəmli statusdur. Tədris nümunələrində bunu reallaşdırmırıq, amma production-da adətən istifadəçi təşəbbüsü (widget-də «Ləğv et» düyməsi) və ya timeout ilə qoyulur. Belə halda worker növbədən tapşırığı növbəti götürəndə status: 'canceled' görür, emala başlamır, MCP server isə final hadisə job.canceled göndərir.
9. İdemponentlik və retry: eyni tələyə iki dəfə düşməyək
Retry daxil edəndə dərhal «eyni işi iki dəfə görmək» riski yaranır. Commerce modullarında bu kritikdir (məsələn, pulun iki dəfə tutulması), amma GiftGenius-da da təkrarın pis olduğu ssenarilər var: dosta iki eyni məktubun göndərilməsi, daxili analitikada yazının dublikatı və s.
Ona görə iki prinsipə arxalanmaq lazımdır.
Birinci: job handler idemponent olmalıdır.
Eyni jobId ilə onu bir neçə dəfə çağırırsınızsa (retry çərçivəsində və ya səhvən), dünya «qırılmamalıdır». Bunun üçün:
- bütün yan effektlər (DB-yə yazma, məktub göndərmə, sifariş yaratma) jobId-ə və ya digər təbii identifikatora bağlanmalıdır ki, kodda həmin addımı artıq edib-etmədiyimizi tez yoxlaya bilək;
- əgər job.status artıq 'completed' və ya 'failed'-dirsə, təkrar çağırışı ya görməməzlikdən gəlmək, ya da hazır nəticəni qaytarmaq olar.
Sadə qorunma nümunəsi:
export async function processJob(jobId: string) {
const job = getJob(jobId);
if (!job) return;
if (job.status === 'completed' || job.status === 'failed') {
// Tapşırıq artıq uğurla və ya qəti şəkildə yekunlaşıb
return;
}
// ... qalan kod
}
İkinci: hadisələr də idemponent olmalıdır.
Biz artıq event_id və müştərinin dublikatları filtrləyə bilməsindən danışmışdıq, amma server tərəfində də diqqətli olmaq lazımdır: worker restartı və ya növbədən bərpa zamanı lazımsız job.progress ilə müştərini spama boğmayın.
10. Arxitekturanızda növbələr və workerlər harada yerləşir
Şəkildəkilər gözəldir, bəs worker fiziki olaraq harada çalışır? Bir neçə tipik variant var.
İnteqrasiya olunmuş worker: MCP server və worker — eyni proses/deploy. Elə o, tool‑call-ları qəbul edir, elə o, worker loop-u qaldırır. Üstünlüyü sadəlikdir: daha az servis, asan deploy. Çatışmazlıq — miqyaslama: daha çox worker əlavə etmək üçün bütün MCP serveri miqyaslamaq lazım gəlir.
Ayrı worker: MCP server — bir servis, workerlər — başqa. Aralarında — növbə və bəlkə də hadisələr üçün Pub/Sub. Bu, BullMQ/Redis və MCP hadisələri kontekstində çox yazılan mövzudur: MCP server 'mcp:events' adlı Redis kanalına abunə olur, workerlər ora hadisələr yayımlayır.
Kombinə edilmiş variant: MCP serverin bir instansı həm də worker-i işlədir, qalan instanslar yalnız HTTP/SSE-dir. Əgər Vercel və ya digər serverless platformalarda deploy edirsinizsə, davamlı fond prosesləri çətin olduğu üçün bu faydalı ola bilər.
Bizim tədris GiftGenius-u üçün hələlik birinci variant uyğundur: MCP server + prosesdə bir sadə worker. Production və miqyaslama modullarına çatanda workerləri ayrıca servisə miqrasiya edə bilərsiniz.
11. Nümunə: GiftGenius üçün tam async‑pipeline
Gəlin istifadəçi çatda belə yazanda nə baş verdiyini ardıcıl danışaq:
«Kosmos fanatı üçün, onun əvvəlki alışlarını nəzərə alaraq mürəkkəb hədiyyə seçimi lazımdır».
- Model start_deep_analysis alətini alıcı profilinin parametrləri və büdcə ilə çağırmağa qərar verir.
- Alət GiftJob-u BD‑də 'pending' statusu ilə yaradır, onu növbəyə qoyur və jobId + təsdiq mesajı qaytarır.
- ChatGPT istifadəçiyə təhlilin başladığını izah edir və jobId-ni GiftGenius widget-inə ötürə bilər.
- Widget həmin jobId üzrə SSE vasitəsilə hadisələrə abunə olur, proqres barını və «Məlumatları toplayır və təhlil edirik» statusunu göstərir.
- Worker növbədə yeni job-u görəndə statusu 'in_progress' edir və job.started göndərir.
- İş prosesində bir neçə dəfə job.progress (mərhələlər) və job.partial (ilk 2–3 hədiyyə) göndərir.
- Haradasa yolda xarici API yıxılarsa, worker eksponensial backoff ilə yenidən cəhd edir, attempts-i yeniləyir və təkrar cəhd haqqında məlumatla hadisə göndərir.
- Sonda ya qısa xülasə və son tövsiyələrlə job.completed göndərir, ya da anlaşılan izahla job.failed.
- Widget bu hadisələr əsasında UI-ni yeniləyir, ChatGPT isə mətn xülasəsi formalaşdırıb follow‑up təklif edə bilər: «Daha çox ideya göstər», «Büdcəni daralt», «Hədiyyə tipini dəyiş».
İstifadəçi baxımından bu, nəzarət altında olan «canlı» uzun prosesdir. Backend baxımından — növbə, workerlər və retry olan normal async‑pipeline.
12. Kiçik tapşırıq (özünüz üçün məşq)
Materialı möhkəmləndirmək istəsəniz, GiftGenius üçün bunları sınayın:
- real BD üçün jobs cədvəlinin sxemini düşünün: hansı indekslər lazımdır, hansı sahələr filtrasiya üçün iştirak edəcək (istifadəçiyə görə, statusa görə, yaradılma tarixinə görə);
- /api/jobs/:id HTTP endpoint-i üçün TypeScript tipini qaralama edin ki, SSE əlçatan olmasa, widget ən pis halda statusu poll edə bilsin;
- retry siyasətini təsvir edin: neçə cəhd, baza gecikməsi, yenə də yıxılan tapşırıqlarla nə etmək (sadə dead‑letter cədvəli və ya loqlaşdırma + xəbərdarlıq).
Bu tapşırıq sonradan production və müşahidəlilik modullarında «pending statusunda N dəqiqədən artıq qalmış tapşırıqların» kimi metriklər barədə danışanda işinizə yarayacaq.
13. Asinxron tapşırıqlarla işdə tipik səhvlər
Səhv №1: hər şeyi tool‑call daxilində sinxron etmək.
Ən yayğın tələ — bütün ağır işi növbəsiz tək bir MCP alətinə sığışdırmaq cəhdidir. Sorğular az olanda bu, guya işləyir. Yüklənmə artan kimi və ya xarici API-lər ləngiyən kimi timeoutlar, çatın donması və çox əsəbi UX alırsınız. Onlarla saniyələr və daha çox çəkə bilən istənilən əməliyyatı jobId ilə async‑job kimi layihələndirmək daha yaxşıdır.
Səhv №2: açıq Job modelinin olmaması.
Bəzən tərtibatçılar tapşırıq vəziyyətini BD-də saxlamadan «təkcə növbə mesajları» ilə kifayətlənməyə çalışırlar. Nəticədə baza suallarına cavab vermək çətinləşir: «tapşırığın statusu nədir?», «neçə dəfə icra etməyə cəhd etdik?», «nəyə görə yıxıldı?». Jobun status, attempts, error, createdAt kimi sahələri olan dəqiq modeli — debug, monitorinq və UX üçün əsasdır.
Səhv №3: retry-in olmaması və ya əksinə, sonsuz təkrarlar.
Kimisə ümumiyyətlə retry etmir və ilk 500-də yıxılır, kimisə while (!success) yazıb cəhd sayını məhdudlaşdırmır. Birinci halda qısamüddətli nasazlıqlara görə çoxlu tapşırıqları itirirsiniz, ikinci halda isə özünüz üçün yük «fırtınaları» yaradır, xarici API-ləri bloklama riski daşıyırsınız. Ağıllı orta yol lazımdır: məhdud sayda cəhd + eksponensial gecikmə + müvəqqəti və davamlı xətaların ayrılması.
Səhv №4: idemponent olmayan handler-lər.
Hər cəhddə məsələn, yoxlamadan kənar sistemdə yeni qeyd yaradırsınızsa, eyni ödənişi icra edir və ya eyni məktubu göndərirsinizsə — retry tez bir zamanda problemə çevrilir. Handler başa düşməlidir ki, bu jobId ilə olan tapşırıq artıq uğurla bitib və təhlükəli yan effektləri təkrarlamamalıdır.
Səhv №5: xətalarda hadisələrin olmaması.
Bəzən worker gözlənilməz istisna ilə yıxılır, onu konsola loqlaşdırır və vəssalam. İstifadəçi isə job.completed-i sonsuza qədər gözləyir, çoxdan hər şeyin öldüyündən xəbərsiz. Emal xətayla bitən istənilən qol nəticədə job.failed-ə və BD-də Job statusunun yenilənməsinə gətirib çıxarmalıdır. Bunsuz MCP axınlarınız birtərəfli «qara qutuya» çevrilir.
Səhv №6: çox tez-tez proqres hadisələri.
«Dürüst olmaq» istəyi və hər bir faiz üçün job.progress göndərmək şəbəkəni, müştərini və MCP serveri yükləyir. Proqresi mərhələ dəyişikliyində və ya böyük delta ilə (məsələn, hər 10%) göndərmək daha yaxşıdır, qalan hər şeyi yalnız daxili loqlarda saxlayın.
Səhv №7: production-da in‑memory növbədən istifadə.
queue: string[] və Map ilə tədris nümunəsi arxitekturanı anlamaq üçün yaxşıdır, amma real production sistemində ilk proses restartında və ya server yıxılanda dağılacaq. Ciddi istismar üçün xarici növbələr və yaddaşlar lazımdır: SQS, Pub/Sub, RabbitMQ, Redis Streams və s. In‑memory variantlar yalnız lokal inkişaf və sadə demo üçün uyğundur.
GO TO FULL VERSION