ساخت برای سیستمهای بزرگ و کارهای پسزمینه طولانی مدت.
اعتبار: ایلیاس چبی در Unsplashچند ماه پیش، نقشی را به عهده گرفتم که نیاز به ساخت زیرساخت برای پخش رسانه (صوتی) داشت. اما فراتر از ارائه صوتی به صورت قطعات قابل پخش، کارهای پردازش رسانهای طولانی مدت و یک خط لوله RAG گسترده وجود داشت که برای رونویسی، تبدیل کد، جاسازی و بهروزرسانیهای رسانهای متوالی طراحی شده بود. ساخت یک MVP با ذهنیت تولید باعث شد تا زمانی که به یک سیستم بینقص دست یافتیم، تکرار کنیم. رویکرد ما یکی از مواردی بوده است که در آن ویژگیها و پشته اولویتهای زیربنایی را ادغام کردیم.
در طول ساخت، هر تکرار به عنوان پاسخی به نیاز فوری و اغلب "فراگیر" آمد. نگرانی اولیه صفبندی کارها بود، که به راحتی با Redis کافی بود؛ ما به سادگی شلیک کردیم و فراموش کردیم. Bull MQ در چارچوب NEST JS به ما کنترل بهتری بر تلاشهای مجدد، پسافتادگیها و صف نامههای مرده داد. به صورت محلی و با چند محموله در تولید، جریان رسانه را درست انجام دادیم. ما به زودی با وزن نظارتپذیری مواجه شدیم:
لاگها → ثبت کارها (درخواستها، پاسخها، خطاها).
معیارها → چقدر / چند وقت یکبار این کارها اجرا میشوند، شکست میخورند، تکمیل میشوند و غیره.
ردیابیها → مسیری که یک کار در سرویسها طی کرد (توابع/روشهای فراخوانی شده در مسیر جریان).
میتوانید برخی از این موارد را با طراحی APIها و ساخت یک داشبورد سفارشی برای اتصال آنها حل کنید، اما مشکل مقیاس پذیری کافی خواهد بود. و در واقع، ما APIها را طراحی کردیم.
چالش مدیریت گردش کارهای پیچیده و طولانی مدت بکاند، جایی که خرابیها باید قابل بازیابی باشند و وضعیت باید پایدار باشد، Inngest نجات معماری ما شد. این اساساً رویکرد ما را بازسازی کرد: هر کار پسزمینه طولانی مدت تبدیل به یک تابع پسزمینه میشود که توسط یک رویداد خاص فعال میشود.
به عنوان مثال، یک رویداد Transcription.request یک تابع TranscribeAudio را فعال میکند. این تابع ممکن است شامل اجرای مراحلی برای: fetch_audio_metadata، deepgram_transcribe، parse_save_trasncription و notify_user باشد.
اصل اولیه دوام، اجرای مراحل است. یک تابع پسزمینه به صورت داخلی به این اجرای مراحل تقسیم میشود، که هر کدام شامل یک بلوک حداقلی و اتمی از منطق است.
خلاصه تابع Inngest:
import { inngest } from 'inngest-client';
export const createMyFunction = (dependencies) => {
return inngest.createFunction(
{
id: 'my-function',
name: 'My Example Function',
retries: 3, // retry the entire run on failure
concurrency: { limit: 5 },
onFailure: async ({ event, error, step }) => {
// handle errors here
await step.run('handle-error', async () => {
console.error('Error processing event:', error);
});
},
},
{ event: 'my/event.triggered' },
async ({ event, step }) => {
const { payload } = event.data;
// Step 1: Define first step
const step1Result = await step.run('step-1', async () => {
// logic for step 1
return `Processed ${payload}`;
});
// Step 2: Define second step
const step2Result = await step.run('step-2', async () => {
// logic for step 2
return step1Result + ' -> step 2';
});
// Step N: Continue as needed
await step.run('final-step', async () => {
// finalization logic
console.log('Finished processing:', step2Result);
});
return { success: true };
},
);
};
مدل رویداد محور Inngest بینش دقیقی در مورد هر اجرای گردش کار ارائه میدهد:
نکته مهم در اتکا به پردازش رویداد خالص این است که در حالی که Inngest به طور کارآمد اجرای توابع را در صف قرار میدهد، خود رویدادها به صورت داخلی در صف قرار نمیگیرند به معنای سنتی واسطه پیامرسانی. این عدم وجود یک صف رویداد صریح میتواند در سناریوهای ترافیک بالا به دلیل شرایط مسابقه بالقوه یا رویدادهای از دست رفته اگر نقطه پایانی دریافت اطلاعات بیش از حد بارگذاری شود، مشکلساز باشد.
برای رفع این مشکل و اعمال دوام رویداد سختگیرانه، ما یک سیستم صفبندی اختصاصی به عنوان یک بافر پیادهسازی کردیم.
سیستم صف ساده AWS (SQS) سیستم انتخابی ما بود (اگرچه هر سیستم صفبندی قوی قابل انجام است)، با توجه به زیرساخت موجود ما در AWS. ما یک سیستم دو صفی طراحی کردیم: یک صف اصلی و یک صف نامه مرده (DLQ).
ما یک محیط کارگر Elastic Beanstalk (EB) را به طور خاص پیکربندی کردیم تا پیامها را مستقیماً از صف اصلی مصرف کند. اگر یک پیام در صف اصلی توسط کارگر EB به تعداد مشخصی از دفعات پردازش نشود، صف اصلی به طور خودکار پیام ناموفق را به DLQ اختصاصی منتقل میکند. این اطمینان میدهد که هیچ رویدادی به طور دائم از دست نمیرود اگر در فعالسازی یا برداشت توسط Inngest شکست بخورد. این محیط کارگر با یک محیط سرور وب EB استاندارد متفاوت است، زیرا تنها مسئولیت آن مصرف و پردازش پیام است (در این مورد، ارسال پیام مصرف شده به نقطه پایانی API Inngest).
بخش کماهمیت و نسبتاً مرتبط با ساخت زیرساخت در مقیاس سازمانی این است که منابع را مصرف میکند و آنها طولانی مدت هستند. معماری میکروسرویس مقیاسپذیری را برای هر سرویس فراهم میکند. ذخیرهسازی، RAM و زمانهای وقفه منابع به بازی خواهند آمد. مشخصات ما برای نوع نمونه AWS، به عنوان مثال، به سرعت از t3.micro به t3.small تغییر کرد و اکنون در t3.medium ثابت شده است. برای کارهای پسزمینه طولانی مدت و پردازنده-محور، مقیاسپذیری افقی با نمونههای کوچک شکست میخورد زیرا گلوگاه زمانی است که برای پردازش یک کار واحد لازم است، نه حجم کارهای جدیدی که وارد صف میشوند.
کارها یا توابع مانند رمزگذاری، جاسازی معمولاً محدود به CPU و محدود به حافظه هستند. محدود به CPU زیرا نیاز به استفاده مداوم و شدید از CPU دارند، و محدود به حافظه زیرا اغلب نیاز به RAM قابل توجهی برای بارگیری مدلهای بزرگ یا مدیریت کارآمد فایلها یا محمولههای بزرگ دارند.
در نهایت، این معماری تقویت شده، قرار دادن دوام SQS و اجرای کنترل شده یک محیط کارگر EB مستقیماً در بالادست API Inngest، انعطافپذیری ضروری را فراهم کرد. ما به مالکیت سختگیرانه رویداد دست یافتیم، شرایط مسابقه را در طول افزایش ترافیک حذف کردیم و یک مکانیسم نامه مرده غیر فرار به دست آوردیم. ما از Inngest برای قابلیتهای هماهنگسازی گردش کار و اشکالزدایی آن استفاده کردیم، در حالی که برای حداکثر توان عملیاتی پیام و دوام به اصول اولیه AWS متکی بودیم. سیستم حاصل نه تنها مقیاسپذیر است بلکه به شدت قابل حسابرسی است، و با موفقیت کارهای پیچیده و طولانی مدت بکاند را به میکرو-مراحل امن، قابل مشاهده و مقاوم در برابر خطا تبدیل میکند.
ساخت اسپاتیفای برای موعظهها. در اصل در Coinmonks در Medium منتشر شد، جایی که مردم با برجسته کردن و پاسخ دادن به این داستان به گفتگو ادامه میدهند.


