یک اسکریپت پاک‌سازی که روی پلتفرم انتشار سایت اجرا شده بود، به اشتباه قطعه‌کدهای برنامه‌نویسی را به عنوان متغیرهای نامعتبر Liquid شناسایی کرد و آن‌ها را از تمام مقالاتی که حاوی آن‌ها بودند، حذف کرد. این نقص فنی باعث شد ده‌ها پست فنی که خوانندگان برای درک آن‌ها به کدهای نمونه نیاز داشتند، بدون کد باقی بمانند؛ موضوعی که منجر به بازگشت فوری به حالت قبل (rollback) و بازنگری در نحوه تست مهاجرت‌های محتوا شد.

چگونه این باگ از دید پنهان ماند

این پلتفرم از Liquid استفاده می‌کند، یک زبان قالب‌سازی (templating language) که محتوای پویا را با تگ‌هایی مانند {{ … }} نشانه‌گذاری می‌کند. قرار بود یک عملیات نگهداری روتین، تگ‌های سرگردانی را که می‌توانستند در رندر شدن محتوا اختلال ایجاد کنند، حذف کند. پارسر اسکریپت به دنبال تگ‌های بازی می‌گشت که تگ بسته‌ی متناظر نداشتند و وقتی یکی پیدا می‌کرد، کل آن بلوک را برای «اصلاح» خطا حذف می‌کرد.

در عمل، پارسر نتوانست تگ‌های {% raw %} و {% endraw %} را که بلوک‌های کد را در بر می‌گیرند، شناسایی کند. این تگ‌ها به Liquid می‌گویند که با هر آنچه در داخل آن‌هاست به عنوان متن خام (literal text) برخورد کند، اما اسکریپت معیوب، تگ باز {% raw %} را به عنوان یک متغیر باز ({{% raw %}) در نظر گرفت و کد اطراف آن را حذف کرد. پیام خطای ثبت‌شده این بود:

Liquid syntax error: Variable '{{% raw %}' was not properly terminated.

از آنجایی که اسکریپت روی مخزن محتوای زنده (live) اجرا شده بود، حذف به صورت دسته‌جمعی انجام شد و نمونه‌های کد را در یک مرحله از تمام مقالات آسیب‌دیده پاک کرد.

آنچه در خطر است

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

جزئیاتی که اکثر خوانندگان از آن غافل می‌شوند

  • پردازش دسته‌ای بدون محیط ایزوله (Sandboxing) – اسکریپت مستقیماً روی داده‌های عملیاتی (production) اجرا شد، نه روی یک نسخه آزمایشی (staging).
  • مدیریت ناکافی تگ‌ها – تنها زیرمجموعه‌ای از تگ‌های Liquid در نظر گرفته شده بود؛ {% raw %} از لیست سفید (whitelist) حذف شده بود.
  • عدم انجام تست‌های مرحله‌ای – عملیات روی کل مجموعه داده اجرا شد، بدون اینکه ابتدا یک اجرای آزمایشی (pilot run) روی یک نمونه کوچک انجام شود.

چه چیزهایی می‌توانست از این اتفاق جلوگیری کند

  • اجرای مهاجرت‌ها روی یک نسخه کپی – هرگونه تغییر دسته‌جمعی را ابتدا روی یک نسخه ایزوله از پایگاه داده اعمال کنید.
  • انجام تست واحد (Unit-test) روی پارسرها برای تمام انواع تگ‌ها – موارد خاص (edge cases) مانند بلوک‌های raw، تگ‌های کامنت و ساختارهای تو در تو را شامل شود.
  • عرضه تدریجی – تعداد محدودی از مقالات را پردازش کنید، نتایج را تأیید کنید و سپس مقیاس کار را افزایش دهید.

دیدگاه مخالف

برخی استدلال می‌کنند که تست کردن هر اسکریپت روی یک نسخه پشتیبان کامل برای سایت‌های پرتحرک غیرعملی است و نیاز به اصلاحات سریع، بر ریسک از دست رفتن داده‌ها برتری دارد. اگرچه سرعت مهم است، اما هزینه جبران یک حذف دسته‌جمعی – هم از نظر زمان توسعه‌دهنده و هم از نظر آسیب به اعتبار – اغلب از تأخیر ناشی از یک اجرای محتاطانه بیشتر است.

آنچه در آینده باید منتظر آن بود

تیم، کدهای از دست رفته را از نسخه‌های پشتیبان بازیابی کرده و در حال بازنگری در ابزار پاک‌سازی است تا تمام ساختارهای Liquid را شناسایی کند. آن‌ها قصد دارند یک گزارش تحلیل پس از حادثه (post-mortem) منتشر کنند که جزئیات گردش کار تست به‌روزشده را شرح می‌دهد و اسکریپت اصلاح‌شده را به صورت عمومی به اشتراک بگذارند تا سایر ناشران بتوانند از همین اشتباه جلوگیری کنند. این حادثه یادآور این نکته است: حتی یک خطای کوچک در تجزیه (parsing)، می‌تواند هفته‌ها تلاش نویسنده را از بین ببرد و تست دقیق را به امری غیرقابل مذاکره تبدیل کند.