← بازگشت به وبلاگ

صورتحساب زیرمیلی‌ثانیه: فینو چطور مسیر اصلی درخواست را سبک نگه می‌دارد

فینو در p99 کمتر از ۲۵۰ میکروثانیه سربار اضافه می‌کند؛ نه با حذف پایداری، بلکه با نگه داشتن مسیر اصلی در حافظه و هم‌زمان کردن نوشتن Raft با انتظار برای سرویس بالادست.

جریان صورتحساب زیرمیلی‌ثانیه فینو که عبور درخواست API از مسیر اصلی در حافظه، replicaهای Raft و گره‌های دفتر کل پایدار را بدون اضافه کردن انتظار به پاسخ بالادست نشان می‌دهد.

توسعه‌دهندگان API شما را به خاطر سرعت و پایداری انتخاب می‌کنند. اگر لایه صورتحساب وسط مسیر اصلی درخواست، تأخیر ناپایدار، timeout، یا خطای 503 بی‌دلیل اضافه کند، خیلی زود دنبال راه دیگری می‌روند.

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

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

در p99، بودجه زیر ۲۵۰ میکروثانیه است.

میلی‌ثانیه‌ها واقعاً کجا می‌روند؟

در یک فراخوانی API معمولی، بیشتر تأخیر از جاهایی می‌آید که کنترل مستقیمی روی آن‌ها ندارید:

مرحلهتأخیر معمول
کلاینت ↔ لبه شبکه شما۲۰–۲۰۰ ms
درگاه ↔ ارائه‌دهنده بالادست۱۰–۵۰۰ ms
استنتاج مدل / محاسبه سنگین۲۰۰ ms – ۳۰ s

حتی ۵ ms سربار صورتحساب در SLA دیده می‌شود. ۵۰ ms می‌تواند اقتصاد یک محصول بلادرنگ را عوض کند. بنابراین فراخوانی پایگاه داده یا Redis برای هر درخواست، یا منتظر ماندن برای یک سرویس جداگانه صورتحساب، از ابتدا گزینه مناسبی نیست.

پاسخ فینو در خود معماری است: در مسیر اصلی درخواست، هیچ بخشی منتظر رفت‌وبرگشت شبکه به یک مخزن خارجی نمی‌ماند.

مسیر اصلی کاملاً در حافظه است

وقتی درخواست به درگاه می‌رسد:

  1. تطبیق مسیر — جدول مسیریابی از قبل کامپایل‌شده و بدون قفل است. برای هر درخواست هم تخصیص حافظه جدید انجام نمی‌شود.
  2. احراز هویت — توکن هش می‌شود و مشتری از جدول درون حافظه خوانده می‌شود؛ جدولی که هنگام راه‌اندازی بارگذاری شده و با اعلان پایگاه داده به‌روز می‌ماند.
  3. انتخاب منبع پرداخت — پیمایش سهمیه → کیف پول → اعتبار از وضعیت دفتر کل در حافظه.
  4. قیمت‌گذاری — بدترین حالت هزینه از بسته قیمتی مشتری، چه ثابت باشد چه پلکانی یا حجمی، با اعداد صحیح micros محاسبه می‌شود.
  5. رزرو — دستور به Raft پیشنهاد می‌شود و گره‌های پیرو هم کافی بودن موجودی را با همان منطق دوباره محاسبه می‌کنند.

بدون رفت‌وبرگشت به پایگاه داده. بدون Redis. بدون HTTP به یک سرویس SaaS صورتحساب.

نمونه اندازه‌گیری در بار کاری شبیه production:

عملیاتهزینه
جستجوی احراز< ۱ µs
بررسی موجودی / سهمیه< ۱ µs
تطبیق مسیر~۵ µs
تبدیل درخواست (jaq کامپایل‌شده)۱۰–۵۰ µs
رزرو + نهایی‌سازی (Raft گروهی)۲۰–۱۰۰ µs
تبدیل پاسخ۱۰–۵۰ µs

یک رفت‌وبرگشت ساده داخل دیتاسنتر حدود ~۱ ms طول می‌کشد؛ یعنی چندین برابر بیشتر از سربار فینو. بخش کند همان ارائه‌دهنده بالادست است. سهم فینو در زمان کل، آن‌قدر کوچک است که بیشتر شبیه خطای گرد کردن است تا یک مرحله قابل مشاهده.

پایداری بدون معطل کردن پاسخ

سؤال طبیعی این است: «اگر همه‌چیز در حافظه است، پس پایداری از کجا می‌آید؟»

فینو از Raft برای همگام‌سازی خطی‌پذیر دستورها استفاده می‌کند. نکته مهم، زمان‌بندی fsync است.

در حالت WAL گروهی، رزرو در ابتدای درخواست وارد صف می‌شود، اما ثبت پایدار بعد از برگشت فراخوانی بالادست انجام می‌شود. fsync با هزینه حدود ~۱۰۰ µs، هم‌زمان با انتظاری انجام می‌شود که در هر حال برای سرویس بالادست دارید؛ انتظاری که معمولاً چند میلی‌ثانیه یا بیشتر طول می‌کشد.

خط زمانی:
|-- رزرو (در صف) --|-- بالادست 250 ms --|-- نهایی‌سازی + fsync 100 µs --|
                       ↑                                    ↑
                  پنجره هم‌پوشانی                    پاسخ آماده

پایداری حذف نمی‌شود؛ فقط در زمانی انجام می‌شود که از قبل صرف انتظار برای ارائه‌دهنده بالادست می‌کردید.

اگر leader از کار بیفتد، گره‌های پیرو کپی آماده دارند و در ۱۵۰–۳۰۰ ms leader جدید انتخاب می‌کنند. توزیع‌بار فقط به leader مسیریابی می‌کند. API در دسترس می‌ماند و دفتر کل هم یکپارچه می‌ماند.

مسیر سرد در برابر مسیر اصلی

همه‌چیز نباید در حافظه باشد؛ فینو هم چنین ادعایی ندارد.

موضوعمسیرمکانیزم
شارژ هر درخواستاصلیدفتر در حافظه + Raft
واریز، پرداخت فاکتورسردهمگام‌سازی leader از Postgres
تمدید اشتراکسرددستورات Subscribe / پایان دوره
تغییر پیکربندیگرمبارگذاری مجدد؛ جدول مسیر دوباره کامپایل

Postgres محل پیکربندی و گزارش می‌ماند. لاگ Raft منبع حقیقت برای موجودی و شارژهاست. داشبوردها از کپی می‌خوانند، اما مسیر درخواست هیچ‌وقت معطل آن‌ها نمی‌شود.

به همین دلیل فینو هم سریع می‌ماند، هم برای کار مالی قابل اتکاست. مسیر اصلی فقط یک کار دارد: دستورها را به‌صورت یکسان اعمال کند. بقیه کارها در مسیرهای کندتر انجام می‌شوند.

چرا Rust — و چرا اعداد صحیح

عملکرد فقط چند میکروثانیه کمتر نیست. مهم‌تر از آن، قابل پیش‌بینی بودن است.

  • بدون مکث جمع‌آوری زباله وسط درخواست. لایه شارژی که هر چند ثانیه ۵۰ ms سیستم را نگه دارد، برای کلاینت‌های حساس به تأخیر شبیه قطعی است.
  • ایمنی حافظه بدون قربانی کردن سرعت. خطای دسترسی به حافظه در سیستم صورتحساب فقط crash نیست؛ می‌تواند موجودی را خراب کند.
  • پول همه‌جا با micros صحیح نگهداری می‌شود. بدون خطای اعشار شناور و بدون غافلگیری‌هایی مثل «۰٫۱ + ۰٫۲». قیمت‌گذاری فقط با حساب صحیح انجام می‌شود.

با mimalloc، اتصال CPU و تنظیمات لینوکس روی گره‌های درگاه، runtime رفتاری شبیه زیرساخت پیدا می‌کند؛ نه شبیه سرور اپلیکیشنی که امیدوار است GC وسط کار مزاحم نشود.

برای تیم محصول چه معنا دارد

وقتی صورتحساب جلوی مسیر را نمی‌گیرد:

  • می‌توانید هر endpoint را بر اساس مصرف قیمت‌گذاری کنید، بدون اینکه نگران افت SLA باشید.
  • اندازه‌گیری توکن از JSON پاسخ، خط لوله observability جداگانه نمی‌خواهد.
  • 402 Payment Required یک پاسخ عادی و قابل انتظار است؛ بازخورد فوری به‌جای فاکتور غافلگیرکننده.

نباید مجبور شوید بین «دفتر کل درست» و «API سریع» یکی را انتخاب کنید. همان فرآیندی که پروکسی می‌کند، شارژ را در زمانی بسیار کوتاه در دفتر کل ثبت می‌کند.


بخش کند سیستم شما معمولاً همان بالادست است. کار فینو این است که ثبت درآمد تقریباً به سبکی lookup مسیر باشد، اما با پایداری‌ای که از یک دفتر کل مالی انتظار دارید.