خلاصهی پاسخ: انتشار خودکار یک پست، «یک درخواست HTTP» نیست. API رسمی اینستاگرام انتشار را دو مرحلهای انجام میدهد (ابتدا ساخت کانتینر رسانه، سپس انتشار آن)، تصویر باید از یک نشانی عمومی برای خزندهی متا قابل برداشت باشد، هر حساب سهمیهی روزانه دارد و شبکه هم قطع میشود. معماری درست، این عملیات را از تراکنش کاربر بیرون میبرد و به یک صف خروجی با کلید یکتا میسپارد.
چرا انتشار مستقیم در تراکنش کاربر، اشتباه است؟
وسوسهی اول این است: کاربر دکمه را میزند، کد در همان لحظه درخواست را به API میفرستد و نتیجه را نشان میدهد. این طراحی سه شکست قابل پیشبینی دارد:
- قفل طولانی پایگاهداده: تراکنش تا پایان رفتوبرگشت شبکه باز میماند و زیر بار، کل سیستم را کند میکند.
- حالت نامعلوم: اگر ارتباط بعد از ارسال درخواست و پیش از دریافت پاسخ قطع شود، نمیدانید پست منتشر شده یا نه. تلاش دوباره ممکن است پست تکراری بسازد.
- بازگشت تراکنش (rollback) دروغین: اگر تراکنش اودو به هر دلیل برگردد، رکورد پست پاک میشود ولی پستی که واقعاً روی اینستاگرام رفته باقی میماند.
قاعدهی عمومی: هیچ ورودی/خروجی شبکهای نباید داخل تراکنش کاربر انجام شود. کار کاربر باید فقط «ثبت قصد» باشد؛ اجرای واقعی از آنِ یک کارگر پسزمینه است.
خط انتشار: پنج گام
- آمادهسازی: تخصیص کد یکتا، ساخت تصویر با نسبت و حجم مجاز و ساخت کپشن از قالب. خروجی این گام کاملاً محلی است و هیچ شبکهای لازم ندارد.
- در صف گذاشتن: یک رکورد کار (job) با کلید یکتای محتوا ساخته میشود. اگر همان محتوا دوباره در صف گذاشته شود، همان رکورد برمیگردد، نه یک رکورد تازه.
- ساخت کانتینر رسانه: کارگر پسزمینه نشانی عمومی تصویر و متن کپشن را به نقطهی
/mediaمیفرستد و یک شناسهی کانتینر میگیرد. - انتشار: شناسهی کانتینر به نقطهی
/media_publishفرستاده میشود و شناسهی رسانهی نهایی برمیگردد؛ همان چیزی که پست را برای همیشه قابل ردیابی میکند. جزئیات این جریان در مستندات انتشار محتوای متا آمده است. - همگامسازی: پیوند دائمی پست خوانده و روی رکورد ذخیره میشود تا کامنتها و آمار بعداً به همان پست وصل شوند.
مسئلهی تصویر: خزنده باید بتواند آن را بردارد
در این معماری شما تصویر را آپلود نمیکنید؛ یک نشانی میدهید و پلتفرم خودش تصویر را برمیدارد. این یک قید جدی است: نشانی باید عمومی، بدون احراز هویت و از دید سرورهای متا قابل دسترس باشد. رایجترین خطاهای این مرحله (خطای برداشت رسانه) تقریباً همیشه از یکی از اینهاست: نشانی داخلی یا پشت ورود اجباری، گواهی TLS نامعتبر، فرمت غیرمجاز (تصویر باید JPEG باشد)، حجم بیش از سقف، یا نسبت ابعاد خارج از بازهی مجاز.
برای نصبهایی که سرورشان از بیرون قابل دسترس نیست، راهحل یک رلهی واسط است: تصویر یکبار روی رله بارگذاری میشود، رله یک نشانی عمومی و موقت با توکن تصادفی میسازد و پس از انتشار، فایل با یک نظافتچی زمانبندیشده پاک میشود. مزیت جانبیاش این است که تصویر محصول شما برای همیشه روی یک نشانی حدسزدنی نمیماند.
سهمیه: بپرس، حدس نزن
هر حساب سقف مشخصی برای انتشار از طریق API در بازهی ۲۴ ساعته دارد. نکته این است که این عدد را حدس نزنید: API خودش نقطهای برای پرسیدن سهمیهی مصرفشده دارد و طراحی درست، پیش از هر انتشار همان را میپرسد. اگر سهمیه تمام شده باشد، رفتار درست «خطا دادن» نیست؛ رفتار درست این است که پست در وضعیت «انتظار سهمیه» بماند و بعد از بازشدن پنجره خودکار منتشر شود. از دید فروشنده تفاوت این دو رفتار، تفاوت بین «سیستم خراب شد» و «سیستم کارش را بلد است» است.
ایمنی در برابر تکرار: کلید یکتا و بیاثری
در هر سیستم توزیعشده، «تلاش دوباره» اجتنابناپذیر است و بدون محافظ، تلاش دوباره یعنی پست تکراری. مفهوم راهگشا بیاثری (idempotency) است — همان اصلی که در RFC 9110 برای متدهای HTTP تعریف شده: اجرای چندبارهی یک عملیات باید همان اثری را داشته باشد که اجرای یکبارهاش دارد.
پیادهسازی عملیاش ساده است: از روی محتوای واقعی پست (حساب، محصول، کد، هش تصویر و کپشن) یک UUID نسخهی ۵ — یعنی شناسهی مبتنی بر نام طبق RFC 9562 — ساخته میشود و روی جدول صف قید یکتایی میخورد. همان محتوا همیشه همان کلید را میسازد، پس رکورد دوم اصلاً بهوجود نمیآید. اگر کپشن یا تصویر عوض شود، کلید عوض میشود و انتشار تازه مجاز است — دقیقاً رفتاری که انتظار داریم.
خطاها: کدامها را دوباره تلاش کنیم؟
همهی خطاها یکسان نیستند و رفتار یکسان با آنها گران تمام میشود. دستهبندی عملی:
| دسته | نمونه | رفتار درست |
|---|---|---|
| گذرا | وقفهی شبکه، محدودیت نرخ، خطای برداشت موقت رسانه | تلاش دوباره با فاصلهی نمایی |
| سهمیه | اتمام سقف ۲۴ ساعته | نگهداشتن تا بازشدن پنجره، بدون شمردن بهعنوان شکست |
| دائمی | توکن باطل، دسترسی ردشده، محتوای نامجاز | توقف فوری و اطلاع به کاربر؛ تلاش دوباره فقط منابع را میسوزاند |
فاصلهی نمایی (exponential backoff) با کمی پراکندگی تصادفی، الگوی استاندارد برای دستهی اول است: هر شکست فاصله را چند برابر میکند تا سرویسِ زیر فشار، با هجوم تلاشهای همزمان بدتر نشود.
دو مسیر اتصال: مستقیم و رله
یک لایهی انتزاعی نازک روی تماسهای API — با دو پیادهسازی «مستقیم» و «رله» — این امکان را میدهد که کل منطق بالادست (صف، سهمیه، خطاها) یکبار نوشته شود و تغییر مسیر اتصال فقط یک انتخاب در فرم حساب باشد. در حالت رله، هر درخواست با HMAC روی سهتایی «مهر زمانی، nonce، بدنه» امضا میشود (RFC 2104) و nonce یکبارمصرف در سمت رله ثبت میشود تا بازپخش درخواست ممکن نباشد. رویدادهای ورودی هم با امضای رسمی متا اعتبارسنجی میشوند، پیش از آنکه حتی یک بایت از بدنهشان تفسیر شود.
چه چیزی را لاگ کنیم (و چه چیزی را هرگز)؟
برای عیبیابی، ثبت اینها ضروری است: نقطهی فراخوانیشده، کد وضعیت، شناسهی خطای پلتفرم، مدتزمان، شناسهی کار و شمارهی تلاش. و یک قاعدهی بیاستثنا: توکن دسترسی هرگز نباید در لاگ بنویسد — نه کامل، نه بریده. توکن روی رکورد حساب و فقط برای گروه مدیر سیستم قابل مشاهده است و در متن خطاها هم پیش از ثبت، پاکسازی میشود.
جمعبندی
یک خط انتشار قابل اتکا از چهار قطعه ساخته میشود: بیرونبردن شبکه از تراکنش کاربر، کلید یکتا برای بیاثری، دستهبندی درست خطاها و پرسیدن سهمیه بهجای حدسزدن آن. همین چهار قطعه است که تفاوت بین «اسکریپتی که گاهی کار میکند» و «قابلیتی که میشود فروش کسبوکار را به آن سپرد» را میسازد. برای دیدن نتیجهی نهایی روی اودو، راهنمای راهاندازی و برای مسیر مکمل تبدیل، مقالهی کامنت به دایرکت را ببینید.