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

شرح مسئله: دیسک پر میشود، ولی دادهها بزرگ نشدهاند
داستان معمولاً اینطور شروع میشود: یک شب هشدار میآید که فضای دیسک سرور دیتابیس از ۹۵٪ گذشته، یا صبح که به سرور سر میزنید اپلیکیشن خطا میدهد و در لاگ 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 را جداگانه داشته باشید.
چطور مطمئن شویم مشکل واقعاً حل شده است
- فضا آزاد شده است:
df -h /var/lib/postgresql/16/main - نوشتن روی دیسک واقعاً کار میکند. با کاربر
postgresیک سگمنت WAL جدید بسازید:
اگر این دستور بدون خطایSELECT pg_switch_wal();No space left on deviceبرگردد، دیتابیس میتواند سگمنت جدید بنویسد. - حجم WAL به محدودهی نرمال برگشته است:
SELECT pg_size_pretty(sum(size)) AS wal_size FROM pg_ls_waldir(); - هیچ اسلاتی با 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کوچک و در حال نوسان. - دیگر در لاگ
checkpoints are occurring too frequentlyتکرار نمیشود وfailed_countدرpg_stat_archiverثابت مانده است.
پیشگیری و بهترین روشها
- WAL نگهداشتهشده را پایش کنید، نه فقط فضای دیسک. یک چک سبک هر ۵ دقیقه کافی است:
هر ردیف این خروجی یعنی یک اسلات بیش از ۱۰ گیگابایت 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; - هشدار روی اسلاتهای غیرفعال:
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 تراکنش را باز نگه میدارند، جلوی autovacuum را میگیرند و جدولها را باد میکنند تا جایی که دیسک پر و اتصالها تمام میشود. این مقاله تشخیص و درمان کامل آن است.
در پیادهسازی به کمک نیاز دارید؟
تیم کلودیکپ همین کار را هر روز برای تیمهای دیگر انجام میدهد. اگر جایی گیر کردهاید، با ما صحبت کنید.