برنامه نویسی

ترکیب Node.js با Async Rust برای عملکرد قابل توجه

Summarize this content to 400 words in Persian Lang
ماه گذشته ما اعلام کردیم که Encore.ts – یک چارچوب باطن منبع باز برای TypeScript – به طور کلی در دسترس و آماده استفاده در تولید است.

بنابراین ما متوجه شدیم که اکنون زمان بسیار خوبی برای بررسی برخی از تصمیمات طراحی که در طول مسیر گرفته ایم، و اینکه چگونه آنها به اعداد عملکرد قابل توجهی که دیده ایم، می رسند، است.

تعداد

ما Encore.ts، Bun، Fastify، و Express را با و بدون اعتبار سنجی طرحواره محک زدیم.

برای اعتبار سنجی طرحواره، ما در صورت امکان از Zod استفاده کردیم. در مورد Fastify ما از Ajv به عنوان کتابخانه اعتبارسنجی طرحواره پشتیبانی شده رسمی استفاده کردیم.

برای هر بنچمارک بهترین نتیجه را از پنج اجرا گرفتیم. هر اجرا با درخواست تا حد امکان با 150 کارگر همزمان، بیش از 10 ثانیه انجام شد. تولید بار با oha، یک ابزار تست بار HTTP مبتنی بر Rust و Tokio انجام شد.

بحث بس است، اعداد را ببینیم!

(کد بنچمارک را در GitHub بررسی کنید.)

جدا از عملکرد، Encore.ts در عین حفظ به این امر دست می یابد سازگاری 100٪ با Node.js.

چه طور ممکنه؟ از آزمایش خود ما سه منبع اصلی عملکرد را شناسایی کرده‌ایم که همگی به نحوه عملکرد Encore.ts در زیر کاپوت مربوط می‌شوند.

تقویت شماره 1: قرار دادن یک حلقه رویداد در حلقه رویداد خود

Node.js کد جاوا اسکریپت را با استفاده از یک حلقه رویداد تک رشته ای اجرا می کند. علی‌رغم ماهیت تک رشته‌ای، این در عمل کاملاً مقیاس‌پذیر است، زیرا از عملیات ورودی/خروجی غیرمسدود استفاده می‌کند و موتور جاوا اسکریپت V8 (که کروم را نیز تامین می‌کند) بسیار بهینه شده است.

اما می دانید چه چیزی سریعتر از یک حلقه رویداد تک رشته ای است؟ یک رشته چند رشته ای.

Encore.ts از دو بخش تشکیل شده است:

یک TypeScript SDK که هنگام نوشتن backend با استفاده از Encore.ts استفاده می کنید.
یک زمان اجرا با کارایی بالا، با یک حلقه رویداد چند رشته ای و ناهمزمان که در Rust نوشته شده است (با استفاده از Tokio و Hyper).

Encore Runtime تمام ورودی/خروجی ها مانند پذیرش و پردازش درخواست های HTTP ورودی را مدیریت می کند. این به‌عنوان یک حلقه رویداد کاملاً مستقل اجرا می‌شود که از تعداد رشته‌هایی که سخت‌افزار اصلی پشتیبانی می‌کند، استفاده می‌کند.

هنگامی که درخواست به طور کامل پردازش و رمزگشایی شد، به حلقه رویداد Node.js تحویل داده می شود و سپس پاسخ را از کنترل کننده API می گیرد و آن را برای مشتری می نویسد.

(قبل از اینکه بگویید: بله، ما یک حلقه رویداد در حلقه رویداد شما قرار می دهیم، بنابراین می توانید در حین حلقه رویداد، حلقه رویداد را انجام دهید.)

تقویت شماره 2: پیش محاسبه طرحواره های درخواست

Encore.ts همانطور که از نامش پیداست از ابتدا برای TypeScript طراحی شده است. اما در واقع نمی‌توانید TypeScript را اجرا کنید: ابتدا باید با حذف تمام اطلاعات نوع در جاوا اسکریپت کامپایل شود. این بدان معناست که دستیابی به ایمنی نوع زمان اجرا بسیار سخت‌تر است، که انجام کارهایی مانند اعتبارسنجی درخواست‌های دریافتی را دشوار می‌کند و منجر به محبوب شدن راه‌حل‌هایی مانند Zod برای تعریف طرح‌واره‌های API در زمان اجرا می‌شود.

Encore.ts متفاوت عمل می کند. با Encore، API های ایمن نوع را با استفاده از انواع TypeScript بومی تعریف می کنید:

import { api } from “encore.dev/api”;

interface BlogPost {
id: number;
title: string;
body: string;
likes: number;
}

export const getBlogPost = api(
{ method: “GET”, path: “/blog/:id”, expose: true },
async ({ id }: { id: number }) => Promise<BlogPost> {
// …
},
);

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

سپس Encore.ts کد منبع را برای درک طرح درخواست و پاسخی که هر نقطه پایانی API انتظار دارد، از جمله مواردی مانند سرصفحه‌های HTTP، پارامترهای پرس و جو و غیره، تجزیه می‌کند. سپس طرحواره ها پردازش، بهینه سازی شده و به عنوان یک فایل Protobuf ذخیره می شوند.

هنگامی که Encore Runtime راه اندازی می شود، این فایل Protobuf را می خواند و یک رمزگشای درخواست و رمزگذار پاسخ را از قبل محاسبه می کند، که برای هر نقطه پایانی API بهینه شده است، با استفاده از تعریف دقیق نوع مورد انتظار هر نقطه پایانی API. در واقع، Encore.ts حتی تأیید اعتبار درخواست را مستقیماً در Rust انجام می دهد، و تضمین می کند که درخواست های نامعتبر هرگز نیازی به لمس لایه JS ندارند و بسیاری از حملات انکار سرویس را کاهش می دهد.

درک Encore از طرح درخواست نیز از منظر عملکرد سودمند است. زمان‌های اجرا جاوا اسکریپت مانند Deno و Bun از معماری مشابه معماری Rust-based Encore استفاده می‌کنند (در واقع، Deno از Rust+Tokio+Hyper نیز استفاده می‌کند)، اما درک Encore از طرح درخواست را ندارد. در نتیجه، آنها باید درخواست های HTTP پردازش نشده را برای اجرا به موتور جاوا اسکریپت تک رشته ای تحویل دهند.

از طرف دیگر Encore.ts پردازش درخواست های بیشتری را در داخل Rust انجام می دهد و فقط اشیاء درخواست رمزگشایی شده را تحویل می دهد. با مدیریت بیشتر چرخه عمر درخواست در Rust چند رشته‌ای، حلقه رویداد جاوا اسکریپت آزاد می‌شود تا به جای تجزیه درخواست‌های HTTP، بر اجرای منطق تجاری برنامه‌ها تمرکز کند و عملکرد را حتی بیشتر افزایش دهد.

تقویت شماره 3: یکپارچه سازی زیرساخت

خوانندگان دقیق ممکن است متوجه روندی شده باشند: کلید عملکرد این است که تا حد امکان کار را از حلقه رویداد جاوا اسکریپت تک رشته ای بارگیری کنید.

ما قبلاً به نحوه بارگذاری Encore.ts بیشتر چرخه عمر درخواست/پاسخ به Rust نگاه کرده‌ایم. پس دیگر چه کاری برای انجام دادن وجود دارد؟

خوب، برنامه های کاربردی باطن مانند ساندویچ هستند. شما لایه بالایی پوسته را دارید که در آن به درخواست های دریافتی رسیدگی می کنید. در مرکز شما تاپینگ های خوشمزه خود را دارید (البته منطق تجاری شما). در پایین، لایه دسترسی به داده‌های crusty خود را دارید، که در آن پایگاه داده‌ها را پرس و جو می‌کنید، سایر نقاط پایانی API را فراخوانی می‌کنید و غیره.

ما نمی‌توانیم کار زیادی در مورد منطق کسب‌وکار انجام دهیم – بالاخره می‌خواهیم آن را در TypeScript بنویسیم! – اما این که همه عملیات دسترسی به داده ها حلقه رویداد JS ما را درگیر کند، فایده ای ندارد. اگر آن‌ها را به Rust منتقل کنیم، حلقه رویداد را بیشتر آزاد می‌کنیم تا بتوانیم روی اجرای کد برنامه‌مان تمرکز کنیم.

پس این کاری است که ما انجام دادیم.

با Encore.ts، می توانید منابع زیرساخت را مستقیماً در کد منبع خود اعلام کنید.

به عنوان مثال، برای تعریف موضوع Pub/Sub:

import { Topic } from “encore.dev/pubsub”;

interface UserSignupEvent {
userID: string;
email: string;
}

export const UserSignups = new Topic<UserSignupEvent>(“user-signups”, {
deliveryGuarantee: “at-least-once”,
});

// To publish:
await UserSignups.publish({ userID: “123”, email: “hello@example.com” });

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

“پس از کدام فناوری Pub/Sub استفاده می کند؟”- همه آنها!

زمان اجرا Encore Rust شامل پیاده‌سازی‌هایی برای اکثر فناوری‌های رایج Pub/Sub، از جمله AWS SQS+SNS، GCP Pub/Sub، و NSQ، با برنامه‌ریزی‌های بیشتر (کافکا، NATS، Azure Service Bus و غیره) است. می‌توانید هنگام راه‌اندازی برنامه، پیاده‌سازی را بر اساس هر منبع در پیکربندی زمان اجرا مشخص کنید، یا اجازه دهید اتوماسیون ابر DevOps Encore آن را برای شما مدیریت کند.

فراتر از Pub/Sub، Encore.ts شامل ادغام‌های زیرساختی برای پایگاه‌های داده PostgreSQL، Secrets، Cron Jobs و غیره است.

همه این ادغام های زیرساختی در Encore.ts Rust Runtime پیاده سازی شده اند.

یعنی به محض تماس .publish()، محموله به Rust تحویل داده می شود که مراقب است پیام را منتشر کند، در صورت لزوم دوباره تلاش کند و غیره. همین مورد در مورد درخواست های پایگاه داده، اشتراک در پیام های Pub/Sub و موارد دیگر نیز صدق می کند.

نتیجه نهایی این است که با Encore.ts، تقریباً تمام منطق غیر تجاری از حلقه رویداد JS بارگیری می شود.

در اصل، با Encore.ts یک باطن واقعاً چند رشته ای “رایگان” دریافت می کنید، در حالی که هنوز می توانید تمام منطق کسب و کار خود را در TypeScript بنویسید.

نتیجه

مهم بودن یا نبودن این عملکرد بستگی به مورد استفاده شما دارد. اگر در حال ساخت یک پروژه سرگرمی کوچک هستید، تا حد زیادی آکادمیک است. اما اگر یک محصول پشتیبان تولید را به فضای ابری ارسال می کنید، می تواند تأثیر بسیار زیادی داشته باشد.

تأخیر کمتر تأثیر مستقیمی بر تجربه کاربر دارد. برای بیان واضح: یک بک‌اند سریع‌تر به معنای جلویی سریع‌تر است، که به معنای کاربران شادتر است.

توان عملیاتی بالاتر به این معنی است که می‌توانید به همان تعداد کاربر با سرورهای کمتری خدمات رسانی کنید که مستقیماً با هزینه‌های کمتر ابری مطابقت دارد. یا برعکس، می‌توانید با همان تعداد سرور به کاربران بیشتری خدمات رسانی کنید و اطمینان حاصل کنید که می‌توانید بدون مواجهه با گلوگاه‌های عملکرد، مقیاس بیشتری را افزایش دهید.

در حالی که ما مغرضانه هستیم، فکر می کنیم که Encore یک راه حل بسیار عالی و بهترین راه حل برای ساخت backendهایی با کارایی بالا در TypeScript ارائه می دهد. این سریع است، از نظر نوع ایمن است، و با کل اکوسیستم Node.js سازگار است.

و همه آنها منبع باز است، بنابراین می توانید کد را بررسی کنید و در GitHub مشارکت کنید.

یا فقط آن را امتحان کنید و نظر خود را به ما بگویید!

ماه گذشته ما اعلام کردیم که Encore.ts – یک چارچوب باطن منبع باز برای TypeScript – به طور کلی در دسترس و آماده استفاده در تولید است.

بنابراین ما متوجه شدیم که اکنون زمان بسیار خوبی برای بررسی برخی از تصمیمات طراحی که در طول مسیر گرفته ایم، و اینکه چگونه آنها به اعداد عملکرد قابل توجهی که دیده ایم، می رسند، است.

تعداد

ما Encore.ts، Bun، Fastify، و Express را با و بدون اعتبار سنجی طرحواره محک زدیم.

برای اعتبار سنجی طرحواره، ما در صورت امکان از Zod استفاده کردیم. در مورد Fastify ما از Ajv به عنوان کتابخانه اعتبارسنجی طرحواره پشتیبانی شده رسمی استفاده کردیم.

برای هر بنچمارک بهترین نتیجه را از پنج اجرا گرفتیم. هر اجرا با درخواست تا حد امکان با 150 کارگر همزمان، بیش از 10 ثانیه انجام شد. تولید بار با oha، یک ابزار تست بار HTTP مبتنی بر Rust و Tokio انجام شد.

بحث بس است، اعداد را ببینیم!

درخواست در ثانیه

تأخیر پاسخ

(کد بنچمارک را در GitHub بررسی کنید.)

جدا از عملکرد، Encore.ts در عین حفظ به این امر دست می یابد سازگاری 100٪ با Node.js.

چه طور ممکنه؟ از آزمایش خود ما سه منبع اصلی عملکرد را شناسایی کرده‌ایم که همگی به نحوه عملکرد Encore.ts در زیر کاپوت مربوط می‌شوند.

تقویت شماره 1: قرار دادن یک حلقه رویداد در حلقه رویداد خود

Node.js کد جاوا اسکریپت را با استفاده از یک حلقه رویداد تک رشته ای اجرا می کند. علی‌رغم ماهیت تک رشته‌ای، این در عمل کاملاً مقیاس‌پذیر است، زیرا از عملیات ورودی/خروجی غیرمسدود استفاده می‌کند و موتور جاوا اسکریپت V8 (که کروم را نیز تامین می‌کند) بسیار بهینه شده است.

اما می دانید چه چیزی سریعتر از یک حلقه رویداد تک رشته ای است؟ یک رشته چند رشته ای.

Encore.ts از دو بخش تشکیل شده است:

  1. یک TypeScript SDK که هنگام نوشتن backend با استفاده از Encore.ts استفاده می کنید.

  2. یک زمان اجرا با کارایی بالا، با یک حلقه رویداد چند رشته ای و ناهمزمان که در Rust نوشته شده است (با استفاده از Tokio و Hyper).

Encore Runtime تمام ورودی/خروجی ها مانند پذیرش و پردازش درخواست های HTTP ورودی را مدیریت می کند. این به‌عنوان یک حلقه رویداد کاملاً مستقل اجرا می‌شود که از تعداد رشته‌هایی که سخت‌افزار اصلی پشتیبانی می‌کند، استفاده می‌کند.

هنگامی که درخواست به طور کامل پردازش و رمزگشایی شد، به حلقه رویداد Node.js تحویل داده می شود و سپس پاسخ را از کنترل کننده API می گیرد و آن را برای مشتری می نویسد.

(قبل از اینکه بگویید: بله، ما یک حلقه رویداد در حلقه رویداد شما قرار می دهیم، بنابراین می توانید در حین حلقه رویداد، حلقه رویداد را انجام دهید.)

نمودار

تقویت شماره 2: پیش محاسبه طرحواره های درخواست

Encore.ts همانطور که از نامش پیداست از ابتدا برای TypeScript طراحی شده است. اما در واقع نمی‌توانید TypeScript را اجرا کنید: ابتدا باید با حذف تمام اطلاعات نوع در جاوا اسکریپت کامپایل شود. این بدان معناست که دستیابی به ایمنی نوع زمان اجرا بسیار سخت‌تر است، که انجام کارهایی مانند اعتبارسنجی درخواست‌های دریافتی را دشوار می‌کند و منجر به محبوب شدن راه‌حل‌هایی مانند Zod برای تعریف طرح‌واره‌های API در زمان اجرا می‌شود.

Encore.ts متفاوت عمل می کند. با Encore، API های ایمن نوع را با استفاده از انواع TypeScript بومی تعریف می کنید:

import { api } from "encore.dev/api";

interface BlogPost {
    id:    number;
    title: string;
    body:  string;
    likes: number;
}

export const getBlogPost = api(
    { method: "GET", path: "/blog/:id", expose: true },
    async ({ id }: { id: number }) => Promise<BlogPost> {
        // ...
    },
);
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

سپس Encore.ts کد منبع را برای درک طرح درخواست و پاسخی که هر نقطه پایانی API انتظار دارد، از جمله مواردی مانند سرصفحه‌های HTTP، پارامترهای پرس و جو و غیره، تجزیه می‌کند. سپس طرحواره ها پردازش، بهینه سازی شده و به عنوان یک فایل Protobuf ذخیره می شوند.

هنگامی که Encore Runtime راه اندازی می شود، این فایل Protobuf را می خواند و یک رمزگشای درخواست و رمزگذار پاسخ را از قبل محاسبه می کند، که برای هر نقطه پایانی API بهینه شده است، با استفاده از تعریف دقیق نوع مورد انتظار هر نقطه پایانی API. در واقع، Encore.ts حتی تأیید اعتبار درخواست را مستقیماً در Rust انجام می دهد، و تضمین می کند که درخواست های نامعتبر هرگز نیازی به لمس لایه JS ندارند و بسیاری از حملات انکار سرویس را کاهش می دهد.

درک Encore از طرح درخواست نیز از منظر عملکرد سودمند است. زمان‌های اجرا جاوا اسکریپت مانند Deno و Bun از معماری مشابه معماری Rust-based Encore استفاده می‌کنند (در واقع، Deno از Rust+Tokio+Hyper نیز استفاده می‌کند)، اما درک Encore از طرح درخواست را ندارد. در نتیجه، آنها باید درخواست های HTTP پردازش نشده را برای اجرا به موتور جاوا اسکریپت تک رشته ای تحویل دهند.

از طرف دیگر Encore.ts پردازش درخواست های بیشتری را در داخل Rust انجام می دهد و فقط اشیاء درخواست رمزگشایی شده را تحویل می دهد. با مدیریت بیشتر چرخه عمر درخواست در Rust چند رشته‌ای، حلقه رویداد جاوا اسکریپت آزاد می‌شود تا به جای تجزیه درخواست‌های HTTP، بر اجرای منطق تجاری برنامه‌ها تمرکز کند و عملکرد را حتی بیشتر افزایش دهد.

تقویت شماره 3: یکپارچه سازی زیرساخت

خوانندگان دقیق ممکن است متوجه روندی شده باشند: کلید عملکرد این است که تا حد امکان کار را از حلقه رویداد جاوا اسکریپت تک رشته ای بارگیری کنید.

ما قبلاً به نحوه بارگذاری Encore.ts بیشتر چرخه عمر درخواست/پاسخ به Rust نگاه کرده‌ایم. پس دیگر چه کاری برای انجام دادن وجود دارد؟

خوب، برنامه های کاربردی باطن مانند ساندویچ هستند. شما لایه بالایی پوسته را دارید که در آن به درخواست های دریافتی رسیدگی می کنید. در مرکز شما تاپینگ های خوشمزه خود را دارید (البته منطق تجاری شما). در پایین، لایه دسترسی به داده‌های crusty خود را دارید، که در آن پایگاه داده‌ها را پرس و جو می‌کنید، سایر نقاط پایانی API را فراخوانی می‌کنید و غیره.

ما نمی‌توانیم کار زیادی در مورد منطق کسب‌وکار انجام دهیم – بالاخره می‌خواهیم آن را در TypeScript بنویسیم! – اما این که همه عملیات دسترسی به داده ها حلقه رویداد JS ما را درگیر کند، فایده ای ندارد. اگر آن‌ها را به Rust منتقل کنیم، حلقه رویداد را بیشتر آزاد می‌کنیم تا بتوانیم روی اجرای کد برنامه‌مان تمرکز کنیم.

پس این کاری است که ما انجام دادیم.

با Encore.ts، می توانید منابع زیرساخت را مستقیماً در کد منبع خود اعلام کنید.

به عنوان مثال، برای تعریف موضوع Pub/Sub:

import { Topic } from "encore.dev/pubsub";

interface UserSignupEvent {
    userID: string;
    email:  string;
}

export const UserSignups = new Topic<UserSignupEvent>("user-signups", {
    deliveryGuarantee: "at-least-once",
});

// To publish:
await UserSignups.publish({ userID: "123", email: "hello@example.com" });
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

“پس از کدام فناوری Pub/Sub استفاده می کند؟”
– همه آنها!

زمان اجرا Encore Rust شامل پیاده‌سازی‌هایی برای اکثر فناوری‌های رایج Pub/Sub، از جمله AWS SQS+SNS، GCP Pub/Sub، و NSQ، با برنامه‌ریزی‌های بیشتر (کافکا، NATS، Azure Service Bus و غیره) است. می‌توانید هنگام راه‌اندازی برنامه، پیاده‌سازی را بر اساس هر منبع در پیکربندی زمان اجرا مشخص کنید، یا اجازه دهید اتوماسیون ابر DevOps Encore آن را برای شما مدیریت کند.

فراتر از Pub/Sub، Encore.ts شامل ادغام‌های زیرساختی برای پایگاه‌های داده PostgreSQL، Secrets، Cron Jobs و غیره است.

همه این ادغام های زیرساختی در Encore.ts Rust Runtime پیاده سازی شده اند.

یعنی به محض تماس .publish()، محموله به Rust تحویل داده می شود که مراقب است پیام را منتشر کند، در صورت لزوم دوباره تلاش کند و غیره. همین مورد در مورد درخواست های پایگاه داده، اشتراک در پیام های Pub/Sub و موارد دیگر نیز صدق می کند.

نتیجه نهایی این است که با Encore.ts، تقریباً تمام منطق غیر تجاری از حلقه رویداد JS بارگیری می شود.

نمودار

در اصل، با Encore.ts یک باطن واقعاً چند رشته ای “رایگان” دریافت می کنید، در حالی که هنوز می توانید تمام منطق کسب و کار خود را در TypeScript بنویسید.

نتیجه

مهم بودن یا نبودن این عملکرد بستگی به مورد استفاده شما دارد. اگر در حال ساخت یک پروژه سرگرمی کوچک هستید، تا حد زیادی آکادمیک است. اما اگر یک محصول پشتیبان تولید را به فضای ابری ارسال می کنید، می تواند تأثیر بسیار زیادی داشته باشد.

تأخیر کمتر تأثیر مستقیمی بر تجربه کاربر دارد. برای بیان واضح: یک بک‌اند سریع‌تر به معنای جلویی سریع‌تر است، که به معنای کاربران شادتر است.

توان عملیاتی بالاتر به این معنی است که می‌توانید به همان تعداد کاربر با سرورهای کمتری خدمات رسانی کنید که مستقیماً با هزینه‌های کمتر ابری مطابقت دارد. یا برعکس، می‌توانید با همان تعداد سرور به کاربران بیشتری خدمات رسانی کنید و اطمینان حاصل کنید که می‌توانید بدون مواجهه با گلوگاه‌های عملکرد، مقیاس بیشتری را افزایش دهید.

در حالی که ما مغرضانه هستیم، فکر می کنیم که Encore یک راه حل بسیار عالی و بهترین راه حل برای ساخت backendهایی با کارایی بالا در TypeScript ارائه می دهد. این سریع است، از نظر نوع ایمن است، و با کل اکوسیستم Node.js سازگار است.

و همه آنها منبع باز است، بنابراین می توانید کد را بررسی کنید و در GitHub مشارکت کنید.

یا فقط آن را امتحان کنید و نظر خود را به ما بگویید!

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا