مانیتورینگ و پایداریمهندسانسطح متوسط

پرشدن دیسک PostgreSQL با WAL اسلات replication غیرفعال

یک replication slot غیرفعال می‌تواند pg_wal را تا پر شدن کامل دیسک نگه دارد و دیتابیس را عملاً از کار بیندازد. در این مقاله ریشه‌یابی، رفع و پیشگیری از آن را گام‌به‌گام بررسی می‌کنیم.

تحریریه کلودیکپ ۶ دقیقه مطالعه
پرشدن دیسک PostgreSQL با WAL اسلات replication غیرفعال

شرح مسئله: دیسک پر می‌شود، ولی داده‌ها بزرگ نشده‌اند

داستان معمولاً این‌طور شروع می‌شود: یک شب هشدار می‌آید که فضای دیسک سرور دیتابیس از ۹۵٪ گذشته، یا صبح که به سرور سر می‌زنید اپلیکیشن خطا می‌دهد و در لاگ PostgreSQL این خطوط را می‌بینید:

FATAL:  could not write to file "pg_wal/xlogtemp.28471": No space left on device
PANIC:  could not write to file "pg_wal/xlogtemp.28471": No space left on device
LOG:  server process (PID 28471) was terminated by signal 6: Aborted

یا از سمت کلاینت:

ERROR:  could not extend file "base/16384/16927": No space left on device
HINT:  Check free disk space.
ERROR:  could not write to WAL: No space left on device

نشانه‌های همراه این وضعیت:

  • دیتابیس عملاً read-only می‌شود؛ هر INSERT، UPDATE یا DELETE شکست می‌خورد و اپلیکیشن خطای ۵۰۰ می‌دهد.
  • در لاگ، هشدار checkpoint هم تکرار می‌شود: checkpoints are occurring too frequently (12 seconds apart).
  • خروجی df -h نشان می‌دهد پارتیشن PGDATA روی ۱۰۰٪ است، اما حجم دیتای واقعی (base/) رشد محسوسی نداشته.
  • در $PGDATA/pg_wal هزاران فایل ۱۶ مگابایتی انبار شده است.

در این لحظه فقط یک پرسش مهم وجود دارد: چه چیزی مانع بازیافت WAL شده است؟

علت ریشه‌ای: اسلاتی که WAL را قفل می‌کند

PostgreSQL برای اینکه یک replica فیزیکی یا یک مصرف‌کننده منطقی (Debezium، pglogical، logical replication) بعد از قطعی بتواند از همان نقطه ادامه دهد، از replication slot استفاده می‌کند. برخلاف wal_keep_size که فقط یک «توصیه» است، اسلات یک تضمین است: سگمنت‌های WAL از نقطه‌ی restart_lsn آن اسلات به بعد، تا وقتی مصرف‌کننده آن‌ها را تأیید نکند، بازیافت نمی‌شوند.

اگر مصرف‌کننده از کار بیفتد — یک standby خاموش، یک connector که کسی آن را حذف کرده اما اسلاتش یادش مانده — مقدار restart_lsn یخ می‌زند. از آن لحظه، هر checkpoint فقط WAL جدید تولید می‌کند و هیچ فایلی پاک نمی‌شود. نرخ تولید WAL در یک سیستم پرترافیک می‌تواند چند گیگابایت در ساعت باشد؛ یعنی چند ساعت تا پر شدن دیسک فاصله داریم.

سه عامل دیگر که دقیقاً همین نشانه را تولید می‌کنند:

  • شکست archive_command: وقتی archive_mode = on است و آرشیو به مقصد (S3، NFS، ...) ناموفق بماند، سگمنت‌های WAL تا موفقیت آرشیو پاک نمی‌شوند و pg_wal/archive_status پر از فایل‌های .ready می‌شود.
  • تراکنش باز و طولانی: یک تراکنش باز — حتی فقط SELECT — مانع استفاده مجدد از فضای قدیمی می‌شود و فشار روی WAL را بالا می‌برد.
  • wal_keep_size بزرگ: اگر این پارامتر بی‌دلیل روی مقدار بالایی تنظیم شده باشد، WAL بیشتر از نیاز نگه داشته می‌شود. (البته به‌تنهایی به‌ندرت دیسک را پر می‌کند.)

راه‌حل گام‌به‌گام

گام ۱: تأیید اینکه مقصر pg_wal است

df -h /var/lib/postgresql/16/main
sudo -u postgres du -sh /var/lib/postgresql/16/main/pg_wal
sudo -u postgres ls -1 /var/lib/postgresql/16/main/pg_wal | wc -l

و از داخل psql (کاربر postgres یا هر نقشی که pg_monitor دارد):

SELECT pg_size_pretty(sum(size)) AS wal_size,
       count(*)                     AS wal_segments
FROM pg_ls_waldir();

در حالت عادی، تعداد سگمنت‌ها حول max_wal_size (پیش‌فرض ۱ گیگابایت، یعنی حدود ۶۴ سگمنت) می‌چرخد. اگر عدد چند صد یا چند هزار است، مقصر مشخص است.

گام ۲: پیدا کردن اسلات مقصر

SELECT slot_name,
       slot_type,
       database,
       active,
       restart_lsn,
       pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS retained_wal
FROM pg_replication_slots
ORDER BY pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn) DESC NULLS LAST;

ترکیب active = false با retained_wal چند گیگابایتی، همان چیزی است که دنبالش هستید. روی PostgreSQL 13 و بالاتر ستون wal_status را هم اضافه کنید؛ مقادیر extended و unreserved یعنی اسلات از حد معمول فراتر رفته و وضعیت بحرانی است.

گام ۳: بررسی آرشیو (اگر فعال است)

SELECT archived_count,
       failed_count,
       last_failed_wal,
       last_failed_time
FROM pg_stat_archiver;

اگر failed_count بالا و last_failed_time نزدیک به الان است، ریشه‌ی پر شدن دیسک آرشیو است نه اسلات‌ها؛ در آن صورت مشکل مقصد آرشیو را حل کنید و آن‌گاه رهاسازی WAL خودبه‌خود انجام می‌شود.

گام ۴: تصمیم‌گیری — احیا یا حذف اسلات

اینجا نقطه‌ای است که باید آگاهانه انتخاب کنید، نه واکنشی:

  • مصرف‌کننده برمی‌گردد: اگر replica یا connector فقط قطع شده و می‌توانید در چند دقیقه آن را برگردانید، اسلات را دست نزنید. سرویس مصرف‌کننده را بالا بیاورید و صبر کنید restart_lsn جلو برود؛ WAL خودش آزاد می‌شود.
  • مصرف‌کننده مرده است: اگر آن replica یا connector دیگر وجود ندارد، اسلات را حذف کنید. این عمل برگشت‌پذیر نیست و مصرف‌کننده بعدی باید کامل resync کند.
-- فقط وقتی مطمئن هستید که مصرف‌کننده این اسلات دیگر برنمی‌گردد
SELECT pg_drop_replication_slot('debezium_slot');

-- آزادسازی فوری فضای WAL
CHECKPOINT;

اگر اسلات را فعال است، حذف با خطای replication slot "x" is active for PID 1234 شکست می‌خورد؛ ابتدا سرویس مصرف‌کننده را متوقف کنید.

اگر باید اسلات را نگه دارید ولی دیسک در حال پر شدن است، روی PostgreSQL 13+ می‌توانید سقف نگه‌داری WAL را برای اسلات‌ها محدود کنید:

ALTER SYSTEM SET max_slot_wal_keep_size = '5GB';
SELECT pg_reload_conf();
هشدار: با این تنظیم، اگر WAL نگه‌داشته‌شده از سقف بگذرد، اسلات در checkpoint بعدی invalidate می‌شود و مصرف‌کننده‌اش باید کامل resync کند. این یعنی ترجیح «دیسک زنده» بر «اسلات سالم»؛ یک تصمیم مهندسی است، نه یک تنظیم بی‌خطر. مقدار پیش‌فرض -1 یعنی بدون محدودیت.

در وضعیتی که دیسک صفر شده و هیچ فضایی برای نفس کشیدن نیست، می‌توانید pg_wal را موقتاً به دیسکی دیگر منتقل کنید:

systemctl stop postgresql
mv /var/lib/postgresql/16/main/pg_wal /mnt/fast-disk/pg_wal
ln -s /mnt/fast-disk/pg_wal /var/lib/postgresql/16/main/pg_wal
chown -R postgres:postgres /mnt/fast-disk/pg_wal
systemctl start postgresql

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

چطور مطمئن شویم مشکل واقعاً حل شده است

  1. فضا آزاد شده است:
    df -h /var/lib/postgresql/16/main
  2. نوشتن روی دیسک واقعاً کار می‌کند. با کاربر postgres یک سگمنت WAL جدید بسازید:
    SELECT pg_switch_wal();
    اگر این دستور بدون خطای No space left on device برگردد، دیتابیس می‌تواند سگمنت جدید بنویسد.
  3. حجم WAL به محدوده‌ی نرمال برگشته است:
    SELECT pg_size_pretty(sum(size)) AS wal_size FROM pg_ls_waldir();
  4. هیچ اسلاتی با WAL غیرمنتظره باقی نمانده است:
    SELECT slot_name,
           active,
           pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS retained_wal
    FROM pg_replication_slots;
    روی اسلات‌های فعال باید active = true باشد و retained_wal کوچک و در حال نوسان.
  5. دیگر در لاگ checkpoints are occurring too frequently تکرار نمی‌شود و failed_count در pg_stat_archiver ثابت مانده است.

پیشگیری و بهترین روش‌ها

  • WAL نگه‌داشته‌شده را پایش کنید، نه فقط فضای دیسک. یک چک سبک هر ۵ دقیقه کافی است:
    SELECT slot_name,
           active,
           pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn) AS retained_bytes
    FROM pg_replication_slots
    WHERE pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn) > 10737418240;
    هر ردیف این خروجی یعنی یک اسلات بیش از ۱۰ گیگابایت WAL قفل کرده است. خروجی این کوئری را به یک متریک یا هشدار تبدیل کنید.
  • هشدار روی اسلات‌های غیرفعال: SELECT count(*) FROM pg_replication_slots WHERE NOT active; اگر ناگهان بیشتر از حالت پایه شد، یعنی مصرف‌کننده‌ای از دست رفته است.
  • هشدار روی آرشیو: هرگونه رشد failed_count در pg_stat_archiver باید alert بزند، همراه با مونیتورینگ مقصد آرشیو.
  • max_slot_wal_keep_size را از پیش‌فرض -1 خارج کنید (PostgreSQL 13+) و روی مقداری بگذارید که با ظرفیت دیسک شما سازگار است؛ این تنها تور محافظ در برابر اسلات یتیم است.
  • مالکیت اسلات‌ها را مستند کنید. هر اسلات باید در runbook یا IaC به یک سرویس مشخص گره خورده باشد؛ اسلات بدون مالک را همان روز حذف کنید.
  • wal_keep_size را کوچک نگه دارید و به‌جای آن به اسلات تکیه کنید؛ اسلات دقیق‌تر و قابل‌پایش‌تر است.
  • تست دوره‌ای بازیابی مصرف‌کننده: حداقل یک‌بار در فصل، connector یا standby را عمداً قطع و وصل کنید و ببینید با چه سرعتی به restart_lsn فعلی می‌رسد. این عدد تعیین می‌کند چند ساعت فرصت دارید.
اشتراک‌گذاری:

نویسنده

تحریریه کلودیکپ

تیم محتوای کلودیکپ

تیم فنی و محتوای کلودیکپ؛ مهندسانی که هر روز با DevOps، Kubernetes و زیرساخت ابری کار می‌کنند و تجربه‌هایشان را اینجا می‌نویسند.

سوالات متداول

سوالات متداول این مقاله

خیر. حذف اسلات هیچ داده‌ای را روی primary پاک نمی‌کند و فقط رابطه‌ی نگه‌داری WAL را قطع می‌کند. تنها پیامدش این است که مصرف‌کننده‌ای که به آن اسلات وابسته بود، دیگر نمی‌تواند از نقطه‌ی قبلی ادامه دهد و باید یک resync کامل انجام دهد.

بازیافت فایل‌های WAL در PostgreSQL هنگام checkpoint انجام می‌شود، نه بلافاصله بعد از حذف اسلات. بنابراین بعد از drop کردن اسلات (یا حل مشکل archive_command) یک CHECKPOINT اجرا کنید تا سگمنت‌های آزادشده حذف شوند؛ در غیر این صورت تا checkpoint بعدی ممکن است چند دقیقه منتظر بمانید.

هرگز. <code>pg_wal</code> یک دایرکتوری مدیریت‌شده است و حذف دستی فایل‌ها باعث خطای Panic، خرابی کنترل‌فایل و در مواردی خرابی کل کلاستر می‌شود. مسیر درست این است که علت نگه‌داری WAL (اسلات، آرشیو یا تراکنش باز) را برطرف کنید و اجازه دهید checkpoint خودش فایل‌ها را بازیافت کند.

ادامه مطالعه

مقالات مرتبط

«idle in transaction» در PostgreSQL: قاتل خاموش autovacuum
مانیتورینگ و پایداریمتوسط

«idle in transaction» در PostgreSQL: قاتل خاموش autovacuum

نشست‌های idle in transaction تراکنش را باز نگه می‌دارند، جلوی autovacuum را می‌گیرند و جدول‌ها را باد می‌کنند تا جایی که دیسک پر و اتصال‌ها تمام می‌شود. این مقاله تشخیص و درمان کامل آن است.

تحریریه کلودیکپ ۸ دقیقه مطالعه

در پیاده‌سازی به کمک نیاز دارید؟

تیم کلودیکپ همین کار را هر روز برای تیم‌های دیگر انجام می‌دهد. اگر جایی گیر کرده‌اید، با ما صحبت کنید.