[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$fNg0Oag3OEJDaMD5d9RH3pM5H6YwDeWlu51PsYS8Aj14":3},{"data":4,"related":101},{"id":5,"title":6,"slug":7,"excerpt":8,"cover":9,"category":11,"author":14,"track":20,"level":23,"reading_minutes":26,"is_featured":27,"published_at":28,"updated_at":28,"body":29,"toc":30,"tags":69,"faqs":85,"related_services":98,"comments_enabled":99,"meta_title":19,"meta_description":100,"canonical_url":19,"noindex":27,"is_live":99},1,"«idle in transaction» در PostgreSQL: قاتل خاموش autovacuum","postgres-idle-in-transaction-autovacuum-bloat","نشست‌های idle in transaction تراکنش را باز نگه می‌دارند، جلوی autovacuum را می‌گیرند و جدول‌ها را باد می‌کنند تا جایی که دیسک پر و اتصال‌ها تمام می‌شود. این مقاله تشخیص و درمان کامل آن است.",{"url":10,"alt":6},"https:\u002F\u002Fapi.cloudicap.com\u002Fmedia\u002Fposts\u002F0d697b95-c5d7-4b87-92f3-ae560e3d792e.png",{"name":12,"slug":13},"مانیتورینگ و پایداری","monitoring",{"name":15,"slug":16,"role":17,"bio":18,"linkedin_url":19,"avatar_url":19},"تحریریه کلودیکپ","cloudicap-editorial","تیم محتوای کلودیکپ","تیم فنی و محتوای کلودیکپ؛ مهندسانی که هر روز با DevOps، Kubernetes و زیرساخت ابری کار می‌کنند و تجربه‌هایشان را اینجا می‌نویسند.",null,{"value":21,"label":22},"engineers","مهندسان",{"value":24,"label":25},"intermediate","متوسط",8,false,"2026-10-06T17:33:36+00:00","\u003Cp>اگر یک کلاستر PostgreSQL را مدتی در پروداکشن رها کنید، دیر یا زود با یکی از این سه نشانه روبه‌رو می‌شوید: خطای \u003Ccode>FATAL: sorry, too many clients already\u003C\u002Fcode> هنگام بالا آمدن اپلیکیشن، هشدار \u003Ccode>WARNING: oldest xmin is far in the past\u003C\u002Fcode> در لاگ، و دیسکی که بدون هیچ رشد واقعی در حجم داده مدام پرتر می‌شود. در بیشتر موارد مقصر یک چیز است: نشست‌هایی که در وضعیت \u003Ccode>idle in transaction\u003C\u002Fcode> گیر کرده‌اند.\u003C\u002Fp>\n\n\u003Ch2 id=\"شرح-مسئله-چه-اتفاقی-می‌افتد-و-چه-نشانه‌هایی-دارد\">شرح مسئله: چه اتفاقی می‌افتد و چه نشانه‌هایی دارد\u003C\u002Fh2>\n\n\u003Cp>سناریوی کلاسیک این است: اپلیکیشن زیر بار ترافیک بالا نمی‌آید و pool اتصالات خالی نمی‌شود:\u003C\u002Fp>\n\n\u003Cpre>\u003Ccode class=\"language-bash\">FATAL:  sorry, too many clients already\u003C\u002Fcode>\u003C\u002Fpre>\n\n\u003Cp>همزمان در لاگ خود PostgreSQL این خطوط تکرار می‌شوند:\u003C\u002Fp>\n\n\u003Cpre>\u003Ccode class=\"language-bash\">WARNING:  oldest xmin is far in the past\nHINT:  Close open transactions soon to avoid wraparound problems.\nWARNING:  database \"appdb\" must be vacuumed within 1499321 transactions\nHINT:  To avoid a database shutdown, execute a database-wide VACUUM in that database.\u003C\u002Fcode>\u003C\u002Fpre>\n\n\u003Cp>و کوئری زیر معمولاً همه چیز را روشن می‌کند؛ تراکنش‌هایی که ساعت‌ها باز مانده‌اند و هیچ کاری هم نمی‌کنند:\u003C\u002Fp>\n\n\u003Cpre>\u003Ccode class=\"language-sql\">SELECT pid,\n       usename,\n       application_name,\n       client_addr,\n       state,\n       now() - xact_start   AS xact_age,\n       now() - state_change AS state_age,\n       left(query, 80)      AS last_query\nFROM pg_stat_activity\nWHERE state IN ('idle in transaction', 'idle in transaction (aborted)')\nORDER BY xact_start\nLIMIT 20;\u003C\u002Fcode>\u003C\u002Fpre>\n\n\u003Cpre>\u003Ccode class=\"language-bash\">  pid  | usename | application_name |        state        |  xact_age\n-------+---------+------------------+---------------------+------------\n 48213 | app     | api-gateway      | idle in transaction | 03:41:22\n 48290 | app     | api-gateway      | idle in transaction | 02:58:10\n 48877 | report  | metabase         | idle in transaction | 01:12:44\u003C\u002Fcode>\u003C\u002Fpre>\n\n\u003Cp>حالت \u003Ccode>idle in transaction (aborted)\u003C\u002Fcode> هم یعنی یک دستور داخل تراکنش خطا داده، اما اپلیکیشن نه \u003Ccode>ROLLBACK\u003C\u002Fcode> زده و نه \u003Ccode>COMMIT\u003C\u002Fcode>؛ اتصال همان‌جا معلق مانده است.\u003C\u002Fp>\n\n\u003Ch2 id=\"علت-ریشه‌ای-چرا-باز-ماندن-یک-تراکنش-این‌قدر-مخرب-است\">علت ریشه‌ای: چرا باز ماندن یک تراکنش این‌قدر مخرب است\u003C\u002Fh2>\n\n\u003Cp>PostgreSQL از MVCC استفاده می‌کند. هر تراکنش یک snapshot از دیتابیس می‌گیرد و تا وقتی آن تراکنش تمام نشده، هیچ tuple ای که «نسخه‌ی قدیمی‌تر از snapshot آن» است قابل حذف نیست. به این مرز، \u003Cem>xmin horizon\u003C\u002Fem> می‌گویند.\u003C\u002Fp>\n\n\u003Cp>وقتی یک نشست با \u003Ccode>BEGIN\u003C\u002Fcode> باز می‌ماند و بعدش هیچ اتفاقی نمی‌افتد، آن نشست یک snapshot منقضی‌نشده نگه می‌دارد. نتیجه این است که autovacuum روی جدول‌های داغ اجرا می‌شود، همه‌ی صفحات را می‌خواند، و به tuple های مرده می‌رسد که هنوز «مرگ‌شان برای همه تأیید نشده» — پس آن‌ها را پس نمی‌گیرد. جدول و ایندکس‌هایش باد می‌کنند، صفحات بیشتری در buffer cache مصرف می‌شود و کوئری‌ها کندتر می‌شوند. بدتر از آن، \u003Ccode>datfrozenxid\u003C\u002Fcode> بالا می‌رود و اگر این وضعیت ادامه پیدا کند، در نهایت نوشتن روی دیتابیس متوقف می‌شود تا از wraparound جلوگیری شود.\u003C\u002Fp>\n\n\u003Cp>سه اثر جانبی دیگر هم وجود دارد:\u003C\u002Fp>\n\n\u003Cul>\n\u003Cli>\u003Cstrong>اشغال اتصال:\u003C\u002Fstrong> هر تراکنش باز یک کانکشن از pool را قفل می‌کند. با \u003Ccode>pool_size = 20\u003C\u002Fcode> و بیست تراکنش رهاشده، تمام درخواست‌های جدید در صف می‌مانند و خطای \u003Ccode>too many clients\u003C\u002Fcode> ظاهر می‌شود.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>نگه‌داشتن lock:\u003C\u002Fstrong> تمام قفل‌هایی که تراکنش گرفته (حتی قفل‌های \u003Ccode>ACCESS SHARE\u003C\u002Fcode> روی جدول‌ها) تا پایان تراکنش آزاد نمی‌شوند و \u003Ccode>ALTER TABLE\u003C\u002Fcode> یا \u003Ccode>DROP\u003C\u002Fcode> را بلاک می‌کنند.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>تشدید در replica:\u003C\u002Fstrong> اگر \u003Ccode>hot_standby_feedback\u003C\u002Fcode> روشن باشد، همین xmin به standby هم تحمیل می‌شود و آنجا هم vacuum عقب می‌افتد.\u003C\u002Fli>\n\u003C\u002Ful>\n\n\u003Cp>ریشه‌ی اصلی تقریباً همیشه سمت اپلیکیشن است: ORM با \u003Ccode>autocommit=false\u003C\u002Fcode> که session را بدون \u003Ccode>commit()\u003C\u002Fcode> یا \u003Ccode>rollback()\u003C\u002Fcode> رها می‌کند، نبود بلوک \u003Ccode>try\u002Ffinally\u003C\u002Fcode>، یا انجام یک کار کند (کال به API بیرونی، ارسال ایمیل، تولید گزارش سنگین) داخل تراکنش دیتابیس.\u003C\u002Fp>\n\n\u003Ch2 id=\"راه‌حل-گام‌به‌گام\">راه‌حل گام‌به‌گام\u003C\u002Fh2>\n\n\u003Ch3 id=\"گام-۱-بفهمید-کدام-تراکنش-xmin-را-نگه-داشته-است\">گام ۱: بفهمید کدام تراکنش xmin را نگه داشته است\u003C\u002Fh3>\n\n\u003Cpre>\u003Ccode class=\"language-sql\">SELECT pid,\n       state,\n       now() - xact_start       AS xact_age,\n       age(backend_xmin)        AS xmin_age,\n       usename,\n       application_name,\n       left(query, 60)          AS last_query\nFROM pg_stat_activity\nWHERE backend_xmin IS NOT NULL\nORDER BY xmin_age DESC\nLIMIT 10;\n\n-- چه کسی چه کسی را بلاک کرده؟\nSELECT pid,\n       pg_blocking_pids(pid) AS blockers,\n       state,\n       now() - xact_start    AS xact_age,\n       left(query, 60)       AS last_query\nFROM pg_stat_activity\nWHERE cardinality(pg_blocking_pids(pid)) &gt; 0;\u003C\u002Fcode>\u003C\u002Fpre>\n\n\u003Cp>ستون \u003Ccode>xmin_age\u003C\u002Fcode> همان مقداری است که autovacuum را متوقف می‌کند. اگر این عدد در حد ساعت است، مسئله را پیدا کرده‌اید.\u003C\u002Fp>\n\n\u003Ch3 id=\"گام-۲-شدت-بلoat-را-اندازه-بگیرید\">گام ۲: شدت بلoat را اندازه بگیرید\u003C\u002Fh3>\n\n\u003Cpre>\u003Ccode class=\"language-sql\">SELECT relname,\n       n_live_tup,\n       n_dead_tup,\n       round(100.0 * n_dead_tup \u002F NULLIF(n_live_tup + n_dead_tup, 0), 1) AS dead_pct,\n       last_autovacuum,\n       last_vacuum\nFROM pg_stat_user_tables\nWHERE n_dead_tup &gt; 1000\nORDER BY n_dead_tup DESC\nLIMIT 20;\n\n-- اندازه‌گیری دقیق روی یک جدول مشخص\nSELECT * FROM pgstattuple('public.orders');\u003C\u002Fcode>\u003C\u002Fpre>\n\n\u003Cp>خروجی \u003Ccode>pgstattuple\u003C\u002Fcode> شامل \u003Ccode>dead_tuple_percent\u003C\u002Fcode> و \u003Ccode>free_percent\u003C\u002Fcode> است. اگر این دو مجموعاً بالای ۳۰٪ باشند، جدول واقعاً باد کرده و reclaim فیزیکی لازم است.\u003C\u002Fp>\n\n\u003Ch3 id=\"گام-۳-به-تراکنش‌های-رهاشده-پایان-دهید\">گام ۳: به تراکنش‌های رهاشده پایان دهید\u003C\u002Fh3>\n\n\u003Cp>برای کوئری‌های در حال اجرا اول \u003Ccode>pg_cancel_backend\u003C\u002Fcode>، و برای نشست‌های بی‌کار بازمانده \u003Ccode>pg_terminate_backend\u003C\u002Fcode>:\u003C\u002Fp>\n\n\u003Cpre>\u003Ccode class=\"language-sql\">SELECT pg_terminate_backend(pid)\nFROM pg_stat_activity\nWHERE state IN ('idle in transaction', 'idle in transaction (aborted)')\n  AND now() - state_change &gt; interval '10 minutes'\n  AND pid != pg_backend_pid();\u003C\u002Fcode>\u003C\u002Fpre>\n\n\u003Cp>حواس‌تان باشد که این کار تراکنش را rollback می‌کند؛ پس مطمئن شوید که آن تراکنش در میانه‌ی یک عملیات حساس نیست.\u003C\u002Fp>\n\n\u003Ch3 id=\"گام-۴-تور-محافظ-را-پهن-کنید\">گام ۴: تور محافظ را پهن کنید\u003C\u002Fh3>\n\n\u003Cp>مهم‌ترین تنظیم همین است. از PostgreSQL 9.6 به بعد، \u003Ccode>idle_in_transaction_session_timeout\u003C\u002Fcode> نشستی را که تراکنش باز دارد ولی بی‌کار است بعد از مهلت مشخص می‌کشد:\u003C\u002Fp>\n\n\u003Cpre>\u003Ccode class=\"language-sql\">ALTER SYSTEM SET idle_in_transaction_session_timeout = '5min';\nALTER SYSTEM SET statement_timeout = '30s';\nALTER SYSTEM SET lock_timeout = '5s';\n\n-- فقط در PostgreSQL 14 و بالاتر:\nALTER SYSTEM SET idle_session_timeout = '30min';\n\nSELECT pg_reload_conf();\u003C\u002Fcode>\u003C\u002Fpre>\n\n\u003Cp>اگر نمی‌خواهید این محدودیت برای همه اعمال شود، آن را روی نقش یا دیتابیس خاص بگذارید:\u003C\u002Fp>\n\n\u003Cpre>\u003Ccode class=\"language-sql\">ALTER ROLE app_user IN DATABASE appdb\n  SET idle_in_transaction_session_timeout = '60s';\n\nALTER DATABASE reporting\n  SET idle_in_transaction_session_timeout = '30min';\u003C\u002Fcode>\u003C\u002Fpre>\n\n\u003Cp>برای کاربر \u003Ca href=\"\u002Fservices\u002Fmonitoring\">مانیتورینگ\u003C\u002Fa> هم نقش \u003Ccode>pg_monitor\u003C\u002Fcode> را بدهید تا بتواند بدون superuser همه‌ی این view ها را بخواند:\u003C\u002Fp>\n\n\u003Cpre>\u003Ccode class=\"language-sql\">GRANT pg_monitor TO monitoring_user;\u003C\u002Fcode>\u003C\u002Fpre>\n\n\u003Ch3 id=\"گام-۵-سمت-اپلیکیشن-و-pooler-را-درست-کنید\">گام ۵: سمت اپلیکیشن و pooler را درست کنید\u003C\u002Fh3>\n\n\u003Cp>سمت اپلیکیشن، قاعده ساده است: هیچ تراکنشی نباید باز بماند. در SQLAlchemy این یعنی هر عملیات در بلوک مدیریت‌شده انجام شود و pool هم سقف داشته باشد:\u003C\u002Fp>\n\n\u003Cpre>\u003Ccode class=\"language-python\">from sqlalchemy import create_engine, text\n\nengine = create_engine(\n    \"postgresql+psycopg2:\u002F\u002Fapp:secret@db:5432\u002Fappdb\",\n    pool_size=10,\n    max_overflow=5,\n    pool_pre_ping=True,\n    pool_recycle=1800,\n    connect_args={\"options\": \"-c idle_in_transaction_session_timeout=60000\"},\n)\n\n# تراکنش خودکار commit یا rollback می‌شود\nwith engine.begin() as conn:\n    conn.execute(text(\"UPDATE accounts SET balance = balance - 100 WHERE id = :id\"), {\"id\": 42})\n\u003C\u002Fcode>\u003C\u002Fpre>\n\n\u003Cp>اگر از PgBouncer استفاده می‌کنید، حالت \u003Ccode>transaction\u003C\u002Fcode> و مهلت‌های سرور را فعال کنید:\u003C\u002Fp>\n\n\u003Cpre>\u003Ccode class=\"language-ini\">[pgbouncer]\nlisten_addr = 0.0.0.0\nlisten_port = 6432\npool_mode = transaction\nmax_client_conn = 1000\ndefault_pool_size = 20\nserver_idle_timeout = 60\nquery_wait_timeout = 30\n\u003C\u002Fcode>\u003C\u002Fpre>\n\n\u003Cp>نکته مهم: در حالت \u003Ccode>transaction\u003C\u002Fcode> سرور بعد از هر تراکنش به pool برمی‌گردد، بنابراین یک اپلیکیشن باگ‌دار نمی‌تواند همه‌ی کانکشن‌ها را برای همیشه ببلعد.\u003C\u002Fp>\n\n\u003Ch3 id=\"گام-۶-اگر-جدول-قبلا-باد-کرده-باشد\">گام ۶: اگر جدول قبلاً باد کرده باشد\u003C\u002Fh3>\n\n\u003Cp>\u003Ccode>VACUUM\u003C\u002Fcode> معمولی فضا را به سیستم‌عامل برنمی‌گرداند، فقط برای استفاده مجدد آزاد می‌کند. برای جدول‌های خیلی بادکرده:\u003C\u002Fp>\n\n\u003Cpre>\u003Ccode class=\"language-sql\">VACUUM (VERBOSE, ANALYZE) public.orders;\u003C\u002Fcode>\u003C\u002Fpre>\n\n\u003Cp>اگر حجم فیزیکی هم باید کم شود، \u003Ccode>VACUUM FULL\u003C\u002Fcode> قفل \u003Ccode>ACCESS EXCLUSIVE\u003C\u002Fcode> می‌گیرد و روی پروداکشن معمولاً گزینه نیست. به‌جایش از \u003Ccode>pg_repack\u003C\u002Fcode> استفاده کنید که آنلاین کار می‌کند:\u003C\u002Fp>\n\n\u003Cpre>\u003Ccode class=\"language-bash\">pg_repack -d appdb -t public.orders --no-superuser-check\u003C\u002Fcode>\u003C\u002Fpre>\n\n\u003Ch3 id=\"گام-۷-autovacuum-را-برای-جدول‌های-داغ-تهاجمی‌تر-کنید\">گام ۷: autovacuum را برای جدول‌های داغ تهاجمی‌تر کنید\u003C\u002Fh3>\n\n\u003Cp>پیش‌فرض \u003Ccode>autovacuum_vacuum_scale_factor = 0.2\u003C\u002Fcode> یعنی روی جدول ۱۰۰ میلیون ردیفی، تا ۲۰ میلیون ردیف مرده انباشته نشود vacuum شروع نمی‌شود. برای جدول‌های پرنوسان این را جدولی تنظیم کنید:\u003C\u002Fp>\n\n\u003Cpre>\u003Ccode class=\"language-sql\">ALTER TABLE public.orders SET (\n  autovacuum_vacuum_scale_factor  = 0.02,\n  autovacuum_vacuum_threshold     = 100,\n  autovacuum_analyze_scale_factor = 0.01\n);\n\n-- برای جدول‌هایی که فقط insert می‌شوند (PostgreSQL 13+)\nALTER TABLE public.events SET (\n  autovacuum_vacuum_insert_threshold    = 10000,\n  autovacuum_vacuum_insert_scale_factor = 0.05\n);\u003C\u002Fcode>\u003C\u002Fpre>\n\n\u003Cp>در سطح کلاستر هم اگر I\u002FO اجازه می‌دهد:\u003C\u002Fp>\n\n\u003Cpre>\u003Ccode class=\"language-sql\">ALTER SYSTEM SET autovacuum_max_workers = 6;\nALTER SYSTEM SET autovacuum_naptime = '30s';\nALTER SYSTEM SET autovacuum_vacuum_cost_delay = 0;\nSELECT pg_reload_conf();\u003C\u002Fcode>\u003C\u002Fpre>\n\n\u003Cp>\u003Ccode>autovacuum_vacuum_cost_delay = 0\u003C\u002Fcode> throttle را حذف می‌کند؛ روی استوریج با IOPS محدود این کار می‌تواند تأخیر کوئری‌ها را بالا ببرد، پس با احتیاط و همراه با مانیتورینگ اعمالش کنید. هرگز \u003Ccode>autovacuum = off\u003C\u002Fcode> نگذارید.\u003C\u002Fp>\n\n\u003Ch2 id=\"بررسی-اینکه-مشکل-واقعا-حل-شده-است\">بررسی اینکه مشکل واقعاً حل شده است\u003C\u002Fh2>\n\n\u003Cp>بعد از اعمال تغییرات، این چهار بررسی را انجام دهید:\u003C\u002Fp>\n\n\u003Col>\n\u003Cli>\u003Cstrong>هیچ تراکنش بی‌کاری نمانده باشد:\u003C\u002Fstrong>\n\u003Cpre>\u003Ccode class=\"language-sql\">SELECT count(*) AS stuck\nFROM pg_stat_activity\nWHERE state IN ('idle in transaction', 'idle in transaction (aborted)');\u003C\u002Fcode>\u003C\u002Fpre>\nاین عدد باید صفر بماند. اگر بعد از چند ساعت دوباره بالا رفت، یعنی باگ سمت اپلیکیشن هنوز رفع نشده و timeout فقط دارد علامت را درمان می‌کند.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>autovacuum دوباره کار می‌کند:\u003C\u002Fstrong>\n\u003Cpre>\u003Ccode class=\"language-sql\">SELECT relname, n_dead_tup, last_autovacuum\nFROM pg_stat_user_tables\nWHERE relname = 'orders';\n\nSELECT pid, relid::regclass AS table_name, phase,\n       heap_blks_scanned, heap_blks_total\nFROM pg_stat_progress_vacuum;\u003C\u002Fcode>\u003C\u002Fpre>\nمقدار \u003Ccode>n_dead_tup\u003C\u002Fcode> باید بعد از هر vacuum به‌طور محسوس افت کند و \u003Ccode>last_autovacuum\u003C\u002Fcode> به‌روز شود.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>سن xmin در حال کاهش باشد:\u003C\u002Fstrong>\n\u003Cpre>\u003Ccode class=\"language-sql\">SELECT datname, age(datfrozenxid) AS xid_age\nFROM pg_database\nORDER BY xid_age DESC;\u003C\u002Fcode>\u003C\u002Fpre>\nاین عدد باید در طول روزهای بعد تدریجاً پایین بیاید، نه اینکه مدام رشد کند.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>اتصال‌ها آزاد باشند:\u003C\u002Fstrong>\n\u003Cpre>\u003Ccode class=\"language-sql\">SELECT count(*) AS total,\n       count(*) FILTER (WHERE state = 'active') AS active\nFROM pg_stat_activity;\u003C\u002Fcode>\u003C\u002Fpre>\nتعداد کل باید به‌طور پایدار زیر \u003Ccode>max_connections\u003C\u002Fcode> بماند و اپلیکیشن دیگر خطای \u003Ccode>too many clients\u003C\u002Fcode> نگیرد.\u003C\u002Fli>\n\u003C\u002Fol>\n\n\u003Ch2 id=\"پیشگیری-و-بهترین-روش‌ها\">پیشگیری و بهترین روش‌ها\u003C\u002Fh2>\n\n\u003Cul>\n\u003Cli>\u003Cstrong>کارهای کند را از تراکنش بیرون بکشید.\u003C\u002Fstrong> هیچ درخواست شبکه‌ای، ارسال ایمیل یا تولید PDF داخل \u003Ccode>BEGIN ... COMMIT\u003C\u002Fcode> انجام ندهید.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>\u003Ccode>idle_in_transaction_session_timeout\u003C\u002Fcode> را همیشه روشن بگذارید\u003C\u002Fstrong> — حتی روی دیتابیس‌های داخلی. این تنظیم ارزان‌ترین بیمه‌نامه‌ی ممکن است.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>\u003Ccode>statement_timeout\u003C\u002Fcode> و \u003Ccode>lock_timeout\u003C\u002Fcode> را سطح‌به‌سطح تعریف کنید.\u003C\u002Fstrong> برای سرویس‌های API چند ثانیه، برای کارهای batch چند دقیقه، و برای گزارش‌های تحلیلی روی یک role جدا با مقدار بالاتر.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>از connection pooler استفاده کنید.\u003C\u002Fstrong> PgBouncer در حالت \u003Ccode>transaction\u003C\u002Fcode> جلوی خیلی از این فاجعه‌ها را می‌گیرد و \u003Ccode>max_connections\u003C\u002Fcode> را هم پایین نگه می‌دارد.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>هشدار بگذارید، نه واکنش.\u003C\u002Fstrong> روی تعداد نشست‌های \u003Ccode>idle in transaction\u003C\u002Fcode> با عمر بیشتر از ۵ دقیقه، روی \u003Ccode>age(datfrozenxid)\u003C\u002Fcode> بالای ۵۰۰ میلیون، و روی تعداد اتصال‌های مصرف‌شده بالای ۸۰٪.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>گزارش‌های سنگین را از OLTP جدا کنید.\u003C\u002Fstrong> یک replica اختصاصی برای کوئری‌های طولانی، هم مسئله‌ی xmin و هم مسئله‌ی IOPS را حل می‌کند.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>هرگز \u003Ccode>autovacuum\u003C\u002Fcode> را خاموش نکنید.\u003C\u002Fstrong> اگر بار آن زیاد است، آن را تنظیم کنید؛ خاموش کردنش فقط بدهی را به آینده منتقل می‌کند.\u003C\u002Fli>\n\u003C\u002Ful>\n\n\u003Cblockquote>قاعده سرانگشتی: هر تراکنشی که بیشتر از چند ثانیه باز بماند، در حال مصرف منابعی است که هیچ‌کس نمی‌بیند — تا روزی که دیسک پر شود یا دیتابیس از پذیرفتن نوشتن خودداری کند.\u003C\u002Fblockquote>",[31,35,38,41,45,48,51,54,57,60,63,66],{"id":32,"text":33,"level":34},"شرح-مسئله-چه-اتفاقی-می‌افتد-و-چه-نشانه‌هایی-دارد","شرح مسئله: چه اتفاقی می‌افتد و چه نشانه‌هایی دارد",2,{"id":36,"text":37,"level":34},"علت-ریشه‌ای-چرا-باز-ماندن-یک-تراکنش-این‌قدر-مخرب-است","علت ریشه‌ای: چرا باز ماندن یک تراکنش این‌قدر مخرب است",{"id":39,"text":40,"level":34},"راه‌حل-گام‌به‌گام","راه‌حل گام‌به‌گام",{"id":42,"text":43,"level":44},"گام-۱-بفهمید-کدام-تراکنش-xmin-را-نگه-داشته-است","گام ۱: بفهمید کدام تراکنش xmin را نگه داشته است",3,{"id":46,"text":47,"level":44},"گام-۲-شدت-بلoat-را-اندازه-بگیرید","گام ۲: شدت بلoat را اندازه بگیرید",{"id":49,"text":50,"level":44},"گام-۳-به-تراکنش‌های-رهاشده-پایان-دهید","گام ۳: به تراکنش‌های رهاشده پایان دهید",{"id":52,"text":53,"level":44},"گام-۴-تور-محافظ-را-پهن-کنید","گام ۴: تور محافظ را پهن کنید",{"id":55,"text":56,"level":44},"گام-۵-سمت-اپلیکیشن-و-pooler-را-درست-کنید","گام ۵: سمت اپلیکیشن و pooler را درست کنید",{"id":58,"text":59,"level":44},"گام-۶-اگر-جدول-قبلا-باد-کرده-باشد","گام ۶: اگر جدول قبلاً باد کرده باشد",{"id":61,"text":62,"level":44},"گام-۷-autovacuum-را-برای-جدول‌های-داغ-تهاجمی‌تر-کنید","گام ۷: autovacuum را برای جدول‌های داغ تهاجمی‌تر کنید",{"id":64,"text":65,"level":34},"بررسی-اینکه-مشکل-واقعا-حل-شده-است","بررسی اینکه مشکل واقعاً حل شده است",{"id":67,"text":68,"level":34},"پیشگیری-و-بهترین-روش‌ها","پیشگیری و بهترین روش‌ها",[70,73,76,79,82],{"name":71,"slug":72},"PostgreSQL","postgresql",{"name":74,"slug":75},"Autovacuum","autovacuum",{"name":77,"slug":78},"Performance","performance",{"name":80,"slug":81},"Connection Pool","connection-pool",{"name":83,"slug":84},"Database Tuning","database-tuning",[86,89,92,95],{"question":87,"answer":88},"تفاوت idle in transaction و idle in transaction (aborted) چیست؟","در حالت idle in transaction تراکنش سالم است ولی هیچ دستوری در حال اجرا نیست؛ یعنی اپلیکیشن BEGIN زده و منتظر مانده. در حالت (aborted) یکی از دستورهای داخل تراکنش خطا داده و تراکنش در وضعیت شکست‌خورده است؛ در این حالت تا زمانی که ROLLBACK زده نشود هیچ دستور دیگری کار نمی‌کند و بهتر است نشست سریعاً terminate شود.",{"question":90,"answer":91},"اگر idle_in_transaction_session_timeout را فعال کنم، ممکن است تراکنش‌های سالم و طولانی هم کشته شوند؟","نه. این تنظیم فقط نشست‌هایی را می‌کشد که تراکنش باز دارند و در همان لحظه هیچ کوئری‌ای اجرا نمی‌کنند. یک کوئری طولانی که در حال اجراست مشمول statement_timeout است، نه این پارامتر. با این حال برای jobهای batch که بین دو مرحله باید تراکنش را باز نگه دارند، بهتر است مقدار را روی role یا دیتابیس همان job بالاتر بگذارید.",{"question":93,"answer":94},"چرا بعد از کشتن تراکنش‌های idle in transaction، فضای دیسک آزاد نشد؟","چون VACUUM فضا را داخل همان فایل‌های جدول برای استفاده مجدد آزاد می‌کند و به سیستم‌عامل برنمی‌گرداند. برای کاهش حجم فیزیکی باید autovacuum کارش را تمام کند و سپس در صورت نیاز از pg_repack استفاده کنید. VACUUM FULL هم فضا را برمی‌گرداند اما قفل ACCESS EXCLUSIVE می‌گیرد و روی سیستم زنده توصیه نمی‌شود.",{"question":96,"answer":97},"چطور بفهمم autovacuum به‌خاطر xmin قدیمی عقب افتاده و نه به‌خاطر کمبود منابع؟","به pg_stat_activity نگاه کنید و ببینید آیا نشستی با backend_xmin غیر NULL و xact_age بالا وجود دارد. اگر بله، سقف xmin مشکل اصلی است. اگر چنین نشستی نیست ولی جدول‌ها هنوز dead tuple دارند، احتمالاً autovacuum_max_workers یا autovacuum_vacuum_cost_delay گلوگاه است و باید تنظیمات throttle را بازتر کنید.",[],true,"تشخیص و رفع idle in transaction در PostgreSQL: تأثیر روی autovacuum، جلوگیری از wraparound، تنظیم idle_in_transaction_session_timeout و رفع بلoat جدول.",[102],{"id":34,"title":103,"slug":104,"excerpt":105,"cover":106,"category":108,"author":109,"track":110,"level":111,"reading_minutes":112,"is_featured":27,"published_at":113,"updated_at":113},"پرشدن دیسک PostgreSQL با WAL اسلات replication غیرفعال","postgresql-replication-slot-wal-disk-full","یک replication slot غیرفعال می‌تواند pg_wal را تا پر شدن کامل دیسک نگه دارد و دیتابیس را عملاً از کار بیندازد. در این مقاله ریشه‌یابی، رفع و پیشگیری از آن را گام‌به‌گام بررسی می‌کنیم.",{"url":107,"alt":103},"https:\u002F\u002Fapi.cloudicap.com\u002Fmedia\u002Fposts\u002Ffc88585f-2f8a-4ddd-8903-43b0b8b4aacc.png",{"name":12,"slug":13},{"name":15,"slug":16,"role":17,"avatar_url":19},{"value":21,"label":22},{"value":24,"label":25},6,"2026-10-06T17:39:51+00:00"]