چگونه یک سیستم همگامسازی «تنظیم کن و فراموش کن» ساختم
قیمت محصولات در جاهای مختلف تغییر میکند. آنها در پنل مدیریت، از طریق وارد کردن انبوه (bulk imports) یا از طریق API webhooks تغییر میکنند.
اگر بخواهید این تغییرات را با یک بازار آنلاین (marketplace) خارجی همگامسازی کنید، با یک مشکل روبرو میشوید. اضافه کردن یک فراخوانی همگامسازی به تکتک مسیرهای کد، یک اشتباه است. حتماً یکی را فراموش خواهید کرد. یا یکی را خراب خواهید کرد. در این صورت، نگهداری سیستم به یک کابوس تبدیل میشود.
Django signals این مشکل را حل میکنند. شما به رویداد ذخیرهسازی مدل (model save event) متصل میشوید. این کار باعث میشود هر تغییری را در یک نقطه شناسایی کنید.
اما سیگنالها یک نقص دارند. اگر ۱۰۰ قیمت را همزمان بهروزرسانی کنید، سیگنال ۱۰۰ بار اجرا میشود. این کار باعث ایجاد ۱۰۰ فراخوانی API میشود. در نتیجه با محدودیت نرخ درخواست (rate limits) مواجه میشوید یا منابع را هدر میدهید.
من از یک الگوی سهبخشی برای رفع این مشکل استفاده میکنم:
• یک signal handler که به جای اقدام فوری، شناسهها (IDs) را جمعآوری میکند.
• یک set در سطح هر ترد (per-thread) برای حذف موارد تکراری.
• یک flush callback با استفاده از transaction.on_commit برای پردازش همه موارد به صورت یکجا.
در اینجا نحوه عملکرد آن آمده است:
استفاده از
threading.local()از متغیر سراسری (global variable) استفاده نکنید. متغیرهای سراسری وضعیت را بین درخواستها به اشتراک میگذارند که منجر به نشت دادهها (data leakage) میشود.threading.local()دادهها را در یک ترد واحد ایزوله نگه میدارد.ثبت کنید، نه اینکه اقدام کنید signal handler صرفاً شناسه محصول را به یک set اضافه میکند. سپس به Django میگوید که تابع flush را تنها پس از موفقیت تراکنش پایگاه داده اجرا کند. این کار از همگامسازی دادههایی که در ذخیرهسازی شکست خوردهاند، جلوگیری میکند.
دستهبندی کارها (Batching) وقتی تراکنش commit میشود، تابع flush اجرا میشود. این تابع از set کپی میگیرد و آن را خالی میکند. سپس تمام لیست شناسهها را به یک لایه سرویس (service layer) میفرستد.
لایه سرویس یک کوئری انبوه (bulk query) برای دریافت تمام محصولات انجام میدهد. آنها را بر اساس فروشگاه گروهبندی میکند. در نهایت، برای هر فروشگاه یک تسک واحد به Celery میفرستد.
مزایا کاملاً مشخص هستند:
- حذف موارد تکراری خودکار است. یک set این کار را برای شما انجام میدهد.
- امنیت تراکنش (transaction safety) داخلی است. شما هرگز دادههای roll-back شده را همگامسازی نمیکنید.
- کارایی بالا است. از مشکلات N+1 query جلوگیری میکنید.
- قابلیت اطمینان بالا است. اگر API با خطا مواجه شود، Celery عملیات تلاش مجدد (retries) را مدیریت میکند.
شما سیستم را یک بار میسازید. هر ویژگی جدیدی که بعداً اضافه کنید، به طور خودکار با سیستم همگامسازی کار میکند.
شما همگامسازی با APIهای خارجی را در Django چگونه مدیریت میکنید؟ آیا از signals استفاده میکنید یا الگوی دیگری دارید؟
منبع: https://dev.to/acel/how-i-built-a-set-it-and-forget-it-sync-system-with-django-signals-2ld7