فهرست مطالب
ساعت ده صبح یک روز کاری، پشتیبانی پیامی از یک مشتری میگیرد: «دکمهٔ ثبت سفارش کار نمیکند.» همین یک جمله شروع چیزی است که خیلی از تیمها چند ساعت، و گاهی چند روز، درگیرش میشوند. نه به این دلیل که مشکل پیچیده است، بلکه چون اطلاعات لازم برای فهمیدنش در پنج جای مختلف پخش شده: مرورگر کاربر، لاگ سرویسهای بکاند، دیتابیس، خود سرور، و جایی که تیم کارهایش را پیگیری میکند.
در این نوشته یک حادثهٔ ساختگی ولی کاملاً واقعینما را لایهبهلایه دنبال میکنیم. در هر لایه میگوییم دقیقاً به چه چیزی باید نگاه کرد، چه دادهای باید از قبل جمع شده باشد تا در آن لحظه به کار بیاید، و کدام ابزار از خانوادهٔ MugTools آن لایه را پوشش میدهد. اگر ابزار دیگری دارید، چکلیست هر لایه همچنان به کارتان میآید.
سناریو: «دکمهٔ ثبت سفارش کار نمیکند»
یک فروشگاه اینترنتی را در نظر بگیرید: فرانتاند یک برنامهٔ تکصفحهای است، بکاند دو سرویس ASP.NET Core دارد (یک دروازهٔ API و سرویس سفارشها) و دادهها در دیتابیسی به نام Sales روی SQL Server نگهداری میشود که روی یک Windows Server اجرا میشود. از حدود ساعت ۹:۴۰ بعضی کاربران بعد از زدن دکمهٔ «ثبت سفارش» فقط پیام کلی «خطایی رخ داد» میبینند. مرور محصولات و جستوجو سالم است؛ فقط ثبت سفارش خراب است.
در این لحظه تقریباً هیچ چیز نمیدانیم. اشتباه رایج این است که مستقیم سراغ کد برویم یا سرویس را ریاستارت کنیم. راه بهتر این است که شواهد را از نزدیکترین لایه به کاربر جمع کنیم و قدمبهقدم پایین برویم.
لایهٔ ۱: مرورگر، اولین شاهد
گزارش «کار نمیکند» تقریباً هیچ اطلاعات قابل استفادهای ندارد: کدام صفحه؟ کدام مرورگر؟ کاربر دقیقاً چه کرد؟ چه پیامی دید؟ میشود اینها را از کاربر پرسید، ولی جوابها دیر میرسند و اغلب ناقصاند. چیزی که واقعاً به کار میآید، دادهای است که مرورگر از قبل و بهصورت خودکار ثبت کرده باشد:
- خطای شبکه: درخواست
POST /api/ordersبا چه کد وضعیتی برگشت؟ 500، 502 یا timeout؟ چقدر طول کشید؟ - خطای جاوااسکریپت: آیا کد فرانت بعد از پاسخ ناموفق خودش هم شکسته، مثلاً خواندن خاصیتی از
undefined؟ - Breadcrumbs: کاربر قبل از خطا روی چه چیزهایی کلیک کرد و از کدام صفحهها گذشت؟
- نسخهٔ انتشار: خطا از کدام release شروع شد؟ آیا دیشب چیزی منتشر شده؟
- دامنه: چند کاربر متأثر شدهاند و از چه ساعتی؟
در سناریوی ما دادههای مرورگر نشان میدهد که از ساعت ۹:۴۱ درخواست ثبت سفارش با کد 500 برمیگردد، ۶۳ کاربر مختلف به آن خوردهاند، و فرانت انتشار تازهای نداشته است. پس مشکل از کد سمت مرورگر نیست؛ پاسخ خطا از بکاند میآید. همین نتیجه، که در چند دقیقه به دست آمد، جلوی ساعتها گشتن در کد فرانت را میگیرد. برای جزئیات ثبت این دادهها، راهنمای رصد خطای جاوااسکریپت و نوشتهٔ گزارش باگ با اسکرینشات را ببینید.
اگر API شما در پاسخ خطا یک شناسهٔ درخواست (مثلاً trace_id) برمیگرداند، آن را در پیام خطای کاربر هم نشان دهید: «کد پیگیری: 6603ff2d». پشتیبانی با همین یک رشته مستقیم به لاگ بکاند همان درخواست میرسد.
لایهٔ ۲: لاگ بکاند، کدام درخواست و کدام خط کد
حالا یک بازهٔ زمانی و یک endpoint مشخص داریم. اگر لاگها فایلهایی روی دو سرور جدا باشند، قدم بعدی SSH و grep است و احتمالاً فایلی که لازم داریم همین امروز صبح چرخیده و فشرده شده. این همان دلیلی است که در نوشتهٔ لاگ متمرکز و چرا SSH و grep کافی نیست به آن پرداختهایم. با لاگ متمرکز، سه کار انجام میدهیم:
- فیلتر روی سطح Error، سرویس orders و بازهٔ ۹:۴۰ به بعد.
- نگاه به گروهبندی خطاها: آیا یک نوع خطای تازه ظاهر شده یا خطای قدیمیای که ناگهان زیاد شده؟
- باز کردن همهٔ خطوط یک درخواست ناموفق با trace_id، از دروازه تا سرویس سفارش.
خطوط یک درخواست ناموفق کنار هم اینطور است:
09:41:07.112 INFO gateway POST /api/orders
09:41:07.140 INFO orders Creating order for customer 88213
09:41:08.301 ERROR orders SqlException (0x80131904): The transaction log for
database 'Sales' is full due to 'LOG_BACKUP'.
این خطای 9002 در SQL Server است. حالا میدانیم کد برنامه مقصر نیست و هر درخواستی که بخواهد در Sales بنویسد شکست میخورد؛ به همین دلیل مرور محصولات (که فقط میخواند) سالم است. اینجا جایی است که خیلیها توقف میکنند و فقط «دیتابیس را درست میکنند». ولی سؤال اصلی این است که چرا لاگ تراکنش پر شد. دربارهٔ اینکه trace_id چطور خطوط سرویسهای مختلف را به هم وصل میکند، نوشتهٔ trace_id را بخوانید.
لایهٔ ۳: دیتابیس، چرا لاگ تراکنش پر شد؟
دلیل LOG_BACKUP یعنی دیتابیس در مدل بازیابی FULL است و لاگ تراکنش نمیتواند بازاستفاده شود چون بکاپ لاگ اجرا نشده. دو پرسوجوی ساده وضعیت را روشن میکند:
SELECT name, recovery_model_desc, log_reuse_wait_desc
FROM sys.databases
WHERE name = 'Sales';
SELECT TOP 5 backup_finish_date, type
FROM msdb.dbo.backupset
WHERE database_name = 'Sales' AND type = 'L'
ORDER BY backup_finish_date DESC;
نتیجه: آخرین بکاپ لاگ چهار ساعت پیش گرفته شده. تاریخچهٔ job بکاپ میگوید از آن به بعد هر بار با خطای سیستمعامل 112، یعنی «فضای کافی روی دیسک نیست»، شکست خورده است. پس ریشهٔ مشکل یک لایهٔ دیگر پایینتر است: دیسکی که بکاپها روی آن نوشته میشوند.
وسوسهٔ shrink کردن فایل لاگ یا تغییر مدل بازیابی به SIMPLE در این لحظه زیاد است، ولی این کار زنجیرهٔ بکاپ را میشکند و بازیابی نقطهای را از دست میدهید. جزئیات را در نوشتهٔ پر شدن لاگ تراکنش SQL Server ببینید.
لایهٔ ۴: سرور، دیسکی که آرامآرام پر شد
درایو بکاپها صددرصد پر است. وقتی نمودار فضای آزاد را در چند هفتهٔ گذشته نگاه کنیم، داستان روشن میشود: تا دو هفته پیش فضا هر شب با پاک شدن بکاپهای قدیمی آزاد میشد، ولی از آن روز خط فقط پایین رفته است. job پاکسازی بعد از یک تغییر رمز حساب سرویس دیگر اجرا نمیشد و کسی خبر نداشت. در لایهٔ سرور اینها را بررسی میکنیم:
- روند فضای آزاد هر درایو، نه فقط عدد همین لحظه.
- سرویسها و jobهایی که باید اجرا شوند و نمیشوند.
- تغییرات اخیر: نرمافزار تازه، حساب یا گروه ادمین تغییرکرده، آپدیت و ریاستارت.
- اینکه رشد ناگهانی فایلها عادی است یا نشانهٔ چیزی بدتر، مثل تغییر انبوه فایلها.
رفع فوری ساده بود: بعد از اطمینان از اینکه نسخهٔ بیرونی بکاپها وجود دارد، بکاپهای قدیمیتر از سیاست نگهداری پاک شدند، یک بکاپ لاگ دستی گرفته شد، لاگ تراکنش آزاد شد و ثبت سفارش ساعت ۱۰:۳۵ دوباره کار کرد. حدود ۵۵ دقیقه اختلال. برای فهرست کامل چیزهایی که روی سرور باید زیر نظر باشد، چکلیست پایش سرور ویندوز و لینوکس را ببینید.
لایهٔ ۵: تابلوی تیم، از خاموش کردن آتش تا رفع ریشه
آتش خاموش شد، ولی ریشه هنوز سر جایش است. اگر کار همینجا تمام شود، چند ماه بعد همین اتفاق با شکل دیگری تکرار میشود. خروجی درست یک حادثه، چند کارت مشخص در اسپرینت بعدی است:
- اصلاح حساب سرویس job پاکسازی و آزمودن آن.
- هشدار روی روند پر شدن درایو بکاپ و روی عقب افتادن بکاپ لاگ.
- نمایش کد پیگیری در پیام خطای ثبت سفارش.
- نوشتن یک دستورالعمل کوتاه برای خطای 9002 تا نفر بعدی سراغ shrink نرود.
این کارتها باید تاریخ داشته باشند، و فقط یک تاریخ کافی نیست. اگر اصلاح هشدار دیسک قرار بود پایان هفته تمام شود و دو هفته عقب افتاد، این تأخیر باید همان روز دیده شود، نه وقتی حادثهٔ بعدی رخ داد. دربارهٔ تفاوت تاریخ برنامهای و واقعی در اسپرینت، این نوشته را ببینید.
هر لایه، چه دادهای و کدام ابزار
| لایه | چه چیزی باید از قبل ثبت شده باشد | ابزار MugTools |
|---|---|---|
| مرورگر | خطای جاوااسکریپت و شبکه، breadcrumbs، گزارش کاربر با اسکرینشات | باگماگ |
| بکاند | لاگ متمرکز، trace_id، گروهبندی خطاهای همریشه | لاگماگ |
| دیتابیس | فضای لاگ، وضعیت و آزمون بکاپ، کارایی | دیبیماگ |
| سرور | روند منابع و دیسک، سرویسها، تغییرات، امنیت | سرورماگ |
| تیم | کارتها و اسپرینت با تاریخ برنامهای و واقعی | بوردماگ |
صریح بگوییم: این پنج ابزار جدا هستند و هر کدام یک لایه را پوشش میدهد. اتصال مستقیم گزارش باگماگ به لاگ بکاند همان درخواست در لاگماگ در حال ساخت است و هنوز در دسترس نیست؛ امروز پل بین این دو لایه همان بازهٔ زمانی و trace_id است که دستی دنبال میکنید. لاگماگ هم فعلاً هشدار ندارد و کارش جمعآوری، جستوجو و گروهبندی است. بوردماگ هم باگها را خودش جمع نمیکند؛ کارتها را تیم میسازد.
ولی در همین سناریو، دو هشدار میتوانست کل حادثه را پیش از رسیدن به کاربر متوقف کند: دیبیماگ هر دقیقه SQL Server را میخواند و پیش از خطای 9002 پیامکی مثل «لاگ تراکنش Sales ۹۲٪ پر است؛ علت: بکاپ لاگ ۴ ساعت عقب افتاده» میفرستاد، و سرورماگ روی پر شدن پایدار درایو بکاپ هشدار میداد. فهرست کامل ابزارها در صفحهٔ اصلی MugTools آمده است.
پرسشهای پرتکرار
اگر فقط برای یک لایه وقت یا بودجه داریم، از کجا شروع کنیم؟
از نزدیکترین لایه به کاربر. بسیاری از خطاها اصلاً به بکاند نمیرسند و بدون رصد مرورگر هرگز دیده نمیشوند. ولی اگر یک SQL Server حیاتی بدون DBA دارید، پایش دیتابیس و بکاپ را در اولویت بگذارید، چون خطای آن لایه همهچیز را با خودش پایین میکشد.
آیا همیشه باید هر پنج لایه را بررسی کرد؟
نه. وقتی به علت ریشهای رسیدید توقف کنید، ولی همیشه یک «چرا» بیشتر بپرسید. در سناریوی ما توقف در لایهٔ دیتابیس یعنی تکرار همین حادثه چند هفته بعد.
چه کسی مسئول کدام لایه است؟
در تیمهای کوچک معمولاً یک یا دو نفر همهٔ لایهها را میبینند. مهم این است که برای هر لایه مشخص باشد هشدارها به چه کسی میرسد و چه کسی کارتهای بعد از حادثه را پیگیری میکند.
جمعبندی
یک خطای واقعی تقریباً هیچوقت در همان لایهای که دیده میشود ساخته نمیشود. «دکمهٔ ثبت سفارش کار نمیکند» در مرورگر دیده شد، در لاگ بکاند نام گرفت، در دیتابیس توضیح داده شد، در سرور ریشهاش پیدا شد و در تابلوی تیم بسته شد. اگر در هر لایه دادهٔ لازم از قبل جمع شده باشد، این مسیر بهجای یک روز، یک ساعت طول میکشد؛ و اگر هشدارها درست تنظیم شده باشند، شاید اصلاً به کاربر نرسد.