लंबा PostgreSQL स्नैपशॉट ऐसे मध्यवर्ती पंक्ति संस्करण बनाए रख सकता है जिन्हें वह कभी पढ़ता ही नहीं। वैक्यूम यह तय करने के लिए एक सतर्क ट्रांज़ैक्शन हॉराइज़न का उपयोग करता है कि क्या हटाया जा सकता है। यह मार्गदर्शिका बताती है कि ऐसा क्यों होता है, फिर Mac पर कृत्रिम लेख पंक्तियों और node-postgres की मदद से इस व्यवहार को दोहराती है।

काम यह पहचानना है कि सफ़ाई को रोकने वाला स्नैपशॉट कौन-सा है और वैक्यूम न चलना कौन-सी स्थिति है। उदाहरण PostgreSQL 18 को लक्ष्य करता है और इसमें दो डेटाबेस कनेक्शन हैं: एक रीडर, जिसके पास Repeatable Read स्नैपशॉट है, और एक राइटर, जो एक लेख को अपडेट करता है। PostgreSQL का दस्तावेज़ फिलहाल 18 को वर्तमान समर्थित प्रमुख संस्करण बताता है; नीचे दिए गए कार्यान्वयन विवरण स्पष्ट रूप से इसकी 18 शाखा से जुड़े हैं। PostgreSQL 18 दस्तावेज़।

पुराना रीडर जिन संस्करणों को नहीं देख सकता, उन्हें क्यों बनाए रखें?

अपडेट एक नया पंक्ति संस्करण बनाता है। PostgreSQL पिछला संस्करण तब तक बनाए रखता है, जब तक किसी दूसरे ट्रांज़ैक्शन को उसकी ज़रूरत हो सकती है। अंततः, वैक्यूम पुराने संस्करणों को पुनः प्राप्त कर सकता है और उनका संग्रहण फिर से उपयोग योग्य बना सकता है। यह नियमित वैक्यूमिंग में वर्णित बुनियादी MVCC जीवनचक्र है।

बारीकी यह है कि स्नैपशॉट की दृश्यता और भौतिक रूप से हटाए जा सकने की स्थिति अलग-अलग सवालों के जवाब देती हैं। दृश्यता यह पूछती है कि किसी खास क्वेरी को कोई संस्करण लौटाना चाहिए या नहीं। सफ़ाई हर संस्करण के लिए यह साबित करने के बजाय कि मौजूदा स्नैपशॉट में से कौन उसे पढ़ सकता है, एक सतर्क कटऑफ़ का उपयोग करती है।

PostgreSQL 18 में, HeapTupleSatisfiesVacuum उस ट्रांज़ैक्शन की तुलना करता है जिसने किसी संस्करण को अप्रचलित बनाया था, OldestXmin से। किसी संस्करण को हटाने या बदलने का ट्रांज़ैक्शन कमिट हो जाने से पिछला संस्करण तुरंत हटाने योग्य नहीं हो जाता: जिन संस्करणों को हटाने वाला ट्रांज़ैक्शन उस कटऑफ़ के बराबर या उससे नया है, वे हाल ही में मृत के रूप में वर्गीकृत रहते हैं। इस नियम का वर्णन सीधे आधिकारिक टपल दृश्यता कार्यान्वयन में किया गया है।

यह उदाहरणात्मक क्रम देखें। हर अपडेट अलग से कमिट होता है और कोई अन्य रीडर शामिल नहीं है:

चरण कार्रवाई पुराना रीडर क्या देखता है नवीनतम कमिट किया गया संस्करण
1 लेख का संशोधन 0 डालें — संशोधन 0
2 रीडर Repeatable Read स्नैपशॉट स्थापित करता है संशोधन 0 संशोधन 0
3 राइटर संशोधन 1 कमिट करता है संशोधन 0 संशोधन 1
4 राइटर संशोधन 2 कमिट करता है संशोधन 0 संशोधन 2
5 राइटर संशोधन 3 कमिट करता है संशोधन 0 संशोधन 3

रीडर को सीधे संशोधन 0 की ज़रूरत है। संशोधन 1 और 2 मध्यवर्ती संस्करण हैं: यह रीडर इन्हें नहीं देख सकता, क्योंकि वे इसके स्नैपशॉट के बाद बनाए गए थे। फिर भी, उन्हें बदलने वाले ट्रांज़ैक्शन भी बनाए रखे गए सफ़ाई कटऑफ़ से नए हैं। दस्तावेज़ में दिए गए कटऑफ़ नियम को लागू करने पर समझ आता है कि ये संस्करण संशोधन 0 के साथ क्यों बने रह सकते हैं। इन्हें बनाए रखने का मतलब यह नहीं है कि रीडर इन्हें ऐतिहासिक क्वेरी परिणामों के रूप में देख सकता है। PostgreSQL 18 टपल दृश्यता कार्यान्वयन।

हॉराइज़न रीडर की चुनी हुई पंक्ति से अधिक व्यापक होता है। PostgreSQL 18 बैकएंड ट्रांज़ैक्शन IDs और स्नैपशॉट हॉराइज़न से रिलेशन के अनुकूल हॉराइज़न निकालता है; सामान्य टेबल डेटा के मामले में आम तौर पर उसी डेटाबेस के सेशन भी ध्यान में रखे जाते हैं। यह केवल किसी खास SELECT से लौटाई गई पंक्तियों को ट्रैक नहीं करता। इससे समझ आता है कि छोटी-सी क्वेरी उस डेटाबेस में कहीं और की सफ़ाई को क्यों प्रभावित कर सकती है। साझा रिलेशन और दूसरे रिलेशन प्रकारों के नियम अलग हैं, इसलिए “एक स्नैपशॉट हर जगह का सारा वैक्यूम रोक देता है” कहना बहुत व्यापक दावा होगा। PostgreSQL 18 हॉराइज़न कार्यान्वयन।

कौन-सा ट्रांज़ैक्शन वास्तव में लंबे स्नैपशॉट को बनाए रखता है?

Repeatable Read अपना स्नैपशॉट पहले ऐसे स्टेटमेंट पर स्थापित करता है जो ट्रांज़ैक्शन नियंत्रण से जुड़ा न हो, फिर बाद की क्वेरीज़ में उसी स्नैपशॉट का उपयोग करता है। केवल BEGIN जारी करना इस प्रदर्शन के लिए पर्याप्त नहीं है; शुरुआती लेख क्वेरी वह दृश्य स्थापित करती है जिसे बनाए रखना ज़रूरी है। PostgreSQL का डिफ़ॉल्ट आइसोलेशन स्तर, Read Committed, इसके बजाय हर कमांड के लिए नया स्नैपशॉट लेता है। ट्रांज़ैक्शन आइसोलेशन।

इसलिए, टेबल को क्वेरी कर चुका निष्क्रिय Repeatable Read ट्रांज़ैक्शन क्वेरी पूरी होने के बाद भी अपना स्नैपशॉट बनाए रख सकता है। लंबे समय तक चलने वाला Read Committed स्टेटमेंट भी चलते समय स्नैपशॉट बनाए रख सकता है, लेकिन उस स्तर पर अलग-अलग पूरी हो चुकी स्टेटमेंट एक ही स्थिर दृश्य साझा नहीं करतीं। इसलिए केवल ट्रांज़ैक्शन की अवधि से यह तय नहीं होता कि कौन-सा स्नैपशॉट बनाए रखा जा रहा है। ट्रांज़ैक्शन आइसोलेशन।

केवल-पढ़ने वाला ट्रांज़ैक्शन अपना टॉप-लेवल ट्रांज़ैक्शन ID रखे बिना भी हॉराइज़न में योगदान दे सकता है। कार्यान्वयन बैकएंड ट्रांज़ैक्शन IDs और स्नैपशॉट हॉराइज़न, दोनों को ध्यान में रखता है। इस उदाहरण में, गैर-शून्य backend_xmin के साथ शून्य backend_xid होना इस बात के अनुरूप है कि रीडर स्नैपशॉट बनाए रख रहा है। PostgreSQL 18 हॉराइज़न कार्यान्वयन।

Mac Terminal से व्यवहार दोहराएँ

आपको चालू स्थानीय PostgreSQL 18 सर्वर, उसके कमांड-लाइन टूल और npm के साथ Node.js चाहिए। node-postgres संगतता दस्तावेज़ में Node.js 20 या 24 सूचीबद्ध है। इस मार्गदर्शिका में माना गया है कि आपकी मौजूदा स्थानीय कनेक्शन सेटिंग्स काम करती हैं। यह एक अलग डेटाबेस बनाती है और केवल कृत्रिम लेख-पाठ का उपयोग करती है। node-postgres परिचय और इंस्टॉलेशन।

Terminal में एक कार्य निर्देशिका बनाएँ, ड्राइवर इंस्टॉल करें और उदाहरण डेटाबेस बनाएँ:

mkdir article-snapshot-lab
cd article-snapshot-lab
npm init -y
npm install pg
createdb snapshot_lab

नीचे दिया गया पूरा प्रोग्राम snapshot-demo.cjs के रूप में सहेजें। इसके कनेक्शन PostgreSQL की पर्यावरण सेटिंग्स इस्तेमाल करेंगे, जिनमें PGDATABASE, PGHOST और PGPORT शामिल हैं, जैसा node-postgres कनेक्टिंग में बताया गया है। createdb और प्रोग्राम के लिए समान स्थानीय कनेक्शन सेटिंग्स का उपयोग करें।

रीडर के सभी ट्रांज़ैक्शन स्टेटमेंट एक ही समर्पित क्लाइंट पर चलते हैं। node-postgres के लिए यह ज़रूरी है: ट्रांज़ैक्शन में शुरू से अंत तक एक ही क्लाइंट का उपयोग होना चाहिए, इसलिए उसके स्टेटमेंट pool.query() से चलाना उपयुक्त नहीं है। दूसरा क्लाइंट स्वतंत्र रूप से अपडेट और वैक्यूम कमांड चलाता है। node-postgres ट्रांज़ैक्शन।

उदाहरण revision को भी इंडेक्स करता है, जो हर अपडेट पर बदलता है। इससे ये अपडेट जानबूझकर HOT का उपयोग नहीं करते, और प्रदर्शन को समझना आसान हो जाता है। PostgreSQL के दस्तावेज़ के अनुसार, HOT के लिए ज़रूरी है कि अपडेट साधारण इंडेक्स में संदर्भित कॉलम को अपरिवर्तित रखें और मूल पेज पर पर्याप्त जगह हो। Heap-Only Tuples।

const { Client } = require('pg');

async function main() {
  const reader = new Client({
    application_name: 'article_snapshot_reader',
    connectionTimeoutMillis: 5000
  });
  const writer = new Client({
    application_name: 'article_snapshot_writer',
    connectionTimeoutMillis: 5000
  });
  let readerOpen = false;
  writer.on('notice', notice => console.log(notice.message));

  try {
    await reader.connect();
    await writer.connect();
    const version = await writer.query('SHOW server_version_num');
    const major = Math.floor(Number(version.rows[0].server_version_num) / 10000);
    if (major !== 18) throw new Error('This walkthrough targets PostgreSQL 18.');

    await writer.query(
      'CREATE TABLE public.article_snapshot_demo (' +
      'id integer PRIMARY KEY, revision integer NOT NULL, title text NOT NULL)'
    );
    await writer.query(
      'CREATE INDEX article_snapshot_demo_revision_idx ' +
      'ON public.article_snapshot_demo (revision)'
    );
    await writer.query(
      'INSERT INTO public.article_snapshot_demo VALUES (1, 0, $1)',
      ['Article draft']
    );

    await reader.query('BEGIN ISOLATION LEVEL REPEATABLE READ READ ONLY');
    readerOpen = true;
    const selectRow = 'SELECT id, revision, title FROM public.article_snapshot_demo';
    console.log('Reader before:', (await reader.query(selectRow)).rows);

    for (const revision of [1, 2, 3]) {
      await writer.query(
        'UPDATE public.article_snapshot_demo SET revision = $1, title = $2 WHERE id = 1',
        [revision, 'Article revision ' + revision]
      );
    }

    const pid = (await reader.query('SELECT pg_backend_pid() AS pid')).rows[0].pid;
    console.log('Reader activity:', (await writer.query(
      'SELECT state, backend_xid, backend_xmin FROM pg_stat_activity WHERE pid = $1',
      [pid]
    )).rows);
    console.log('Writer latest:', (await writer.query(selectRow)).rows);
    console.log('Reader still:', (await reader.query(selectRow)).rows);

    console.log('Vacuum with snapshot open:');
    await writer.query('VACUUM (VERBOSE) public.article_snapshot_demo');

    await reader.query('COMMIT');
    readerOpen = false;

    console.log('Vacuum after reader commit:');
    await writer.query('VACUUM (VERBOSE) public.article_snapshot_demo');
    console.log('Reader fresh:', (await reader.query(selectRow)).rows);
  } finally {
    if (readerOpen) await reader.query('ROLLBACK').catch(console.error);
    await Promise.allSettled([reader.end(), writer.end()]);
  }
}

main().catch(error => {
  console.error(error.message);
  process.exitCode = 1;
});

डेटाबेस को स्पष्ट रूप से चुनकर इसे चलाएँ:

PGDATABASE=snapshot_lab node snapshot-demo.cjs

प्रोग्राम के JavaScript सिंटैक्स को Node.js के साथ जाँचा गया। इस लेख के लिए डेटाबेस क्रम को प्राप्त दस्तावेज़ के आधार पर जाँचा गया; इसे PostgreSQL सर्वर पर चलाया नहीं गया।

वैक्यूम के दोनों चरणों को कैसे समझें

क्वेरी के अपेक्षित परिणाम हैं: Reader before के लिए संशोधन 0, Writer latest के लिए संशोधन 3 और Reader still के लिए फिर से संशोधन 0। रीडर के कमिट करने के बाद, उसकी अगली स्वतंत्र क्वेरी से संशोधन 3 लौटना चाहिए। ये कृत्रिम क्रम के अपेक्षित नतीजे हैं, दर्ज किए गए परीक्षण परिणाम नहीं।

एक्टिविटी के परिणाम में राइटर के निरीक्षण के दौरान रीडर की स्थिति idle in transaction दिखनी चाहिए और उसके पास backend_xmin बना रहना चाहिए। PostgreSQL इस स्थिति को ऐसे ट्रांज़ैक्शन के रूप में परिभाषित करता है जो किसी अन्य कमांड की प्रतीक्षा कर रहा हो, और backend_xmin को बैकएंड का वर्तमान हॉराइज़न बताता है। संचयी सांख्यिकी प्रणाली।

पहली वैक्यूम रिपोर्ट में उन पुराने संस्करणों को देखें जिन्हें हटाया नहीं जा सकता। रीडर के कमिट करने के बाद दूसरी रिपोर्ट से तुलना करें कि क्या वे संस्करण हटे हैं। अगर डेटाबेस अन्यथा शांत हो, तो तीन कमिट किए गए प्रतिस्थापनों से जाँचने के लिए तीन पुराने संस्करण मिलते हैं। अन्य बनाए रखे गए हॉराइज़न या साथ चल रहे रखरखाव से सटीक रिपोर्ट प्रभावित हो सकती है, इसलिए रिपोर्ट के शब्दशः एक जैसे होने की अपेक्षा करने के बजाय पहले और बाद का क्रम देखें।

VACUUM (VERBOSE) विस्तृत एक्टिविटी रिपोर्ट देता है। प्रोग्राम node-postgres के दस्तावेज़ में बताए गए notice इवेंट के ज़रिए सर्वर नोटिस प्रिंट करता है। दोनों वैक्यूम कॉल स्पष्ट ट्रांज़ैक्शन ब्लॉक के बाहर चलती हैं, क्योंकि PostgreSQL किसी ब्लॉक के भीतर VACUUM चलाने की अनुमति नहीं देता। PostgreSQL VACUUM, node-postgres Client API।

जब परिणाम अपेक्षा से अलग हो, तो इस निर्णय मार्गदर्शिका का उपयोग करें:

अवलोकन अगली जाँच
कमिट करने से पहले रीडर को संशोधन 3 दिखता है पुष्टि करें कि Repeatable Read शुरू किया गया था और अपडेट से पहले पहली लेख क्वेरी चली थी।
पहली वैक्यूम में पुराने संस्करण बने रहते हैं रीडर के कमिट के बाद वाले चरण से तुलना करें; स्नैपशॉट खुला रहते हुए उनका बने रहना अपेक्षित है।
रीडर के कमिट के बाद भी संस्करण बने रहते हैं कोई और बनाए रखा गया हॉराइज़न जाँचें; इस रीडर को समाप्त करने से केवल उसका योगदान हटता है।
टेबल के आँकड़ों की गिनती असंगत लगती है n_dead_tup को अनुमान मानें और वैक्यूम रिपोर्टों की तुलना करें।
टेबल फ़ाइल का आकार कम नहीं होता जाँचें कि संग्रहण फिर से उपयोग योग्य हुआ है या नहीं; फ़ाइल का आकार घटना अलग नतीजा है।

PostgreSQL स्पष्ट रूप से n_dead_tup को अनुमानित गिनती बताता है। दर्ज किया गया वैक्यूम समय यह भी दिखाता है कि रखरखाव चला था, लेकिन यह नहीं कि हर पुराने संस्करण को हटाया जा सकता था। ये फ़ील्ड समस्या का पता लगाने में मदद करते हैं, लेकिन इनमें से कोई भी बनाए रखे गए स्नैपशॉट और वैक्यूम रिपोर्ट की जाँच की जगह नहीं लेता। संचयी सांख्यिकी प्रणाली।

साधारण वैक्यूम आम तौर पर macOS को जगह लौटाने के बजाय टेबल के भीतर जगह को फिर से उपयोग योग्य बनाता है। टेबल के आखिर में पूरी तरह खाली पड़े पेज इसके दस्तावेज़ित अपवाद हैं। इसलिए सफ़ाई के बाद फ़ाइल का आकार स्थिर रहना यह साबित नहीं करता कि सफ़ाई विफल हुई। नियमित वैक्यूमिंग।

एप्लिकेशन में क्या बदलना चाहिए?

अगर लेख की कई क्वेरीज़ को एक ही सुसंगत डेटाबेस दृश्य साझा करना है, तो ट्रांज़ैक्शन को उन्हीं क्वेरीज़ तक सीमित रखें और उनके पूरा होते ही समाप्त करें। स्नैपशॉट बनाए रखने के परिणामस्वरूप, एप्लिकेशन डिज़ाइन के लिहाज़ से जहाँ काम अनुमति दे, ट्रांज़ैक्शन समाप्त होने के बाद असंबंधित प्रोसेसिंग और डेस्कटॉप पर किए जाने वाले काम करें।

अगर हर क्वेरी स्वतंत्र रूप से नवीनतम कमिट किए गए दृश्य का उपयोग कर सकती है, तो Read Committed कमांड-स्तर के स्नैपशॉट देता है। आइसोलेशन बदलने या बैचों के बीच कमिट करने से सुसंगतता के मायने बदलते हैं: बाद की क्वेरीज़ को लेख के नए कमिट किए गए संशोधन दिख सकते हैं। यह तरीका तभी चुनें जब बदलते नतीजे रीडर के काम के अनुकूल हों। ट्रांज़ैक्शन आइसोलेशन।

वैक्यूम की आवृत्ति बढ़ाने से कोई संस्करण अपरिवर्तित हटाने-योग्यता कटऑफ़ पार नहीं कर सकता। अनावश्यक स्नैपशॉट समाप्त करने से उसके कारण बने रहने वाले संस्करण हट सकते हैं। इसी तरह, VACUUM FULL एक टेबल को दोबारा लिखता है और इसके लिए विशेष लॉक तथा अतिरिक्त जगह चाहिए; इस उदाहरण में यह नियमित समाधान नहीं है। PostgreSQL 18 टपल दृश्यता कार्यान्वयन, PostgreSQL VACUUM।

स्क्रिप्ट अपनी बनाई कृत्रिम टेबल को निरीक्षण के लिए उपलब्ध छोड़ती है। अगर वह टेबल पहले से मौजूद हो, तो मौजूदा सामग्री को बदलने के बजाय स्क्रिप्ट जानबूझकर विफल हो जाती है। उसका निरीक्षण करने के बाद, यदि उदाहरण टेबल हटानी हो, तो snapshot_lab से जुड़े क्वेरी सेशन में निम्न कमांड चलाएँ। अगर सेटअप के बीच में कोई रन विफल हुआ हो, तो हटाने और फिर से चलाने से पहले उसी टेबल का निरीक्षण करें।

DROP TABLE public.article_snapshot_demo;

काम की नैदानिक प्रक्रिया यह बदलाव देखना है: पुराना स्नैपशॉट स्थापित करें, कई प्रतिस्थापन कमिट करें, वैक्यूम चलाएँ, स्नैपशॉट समाप्त करें और फिर वैक्यूम चलाएँ। मूल संशोधन को पढ़ने योग्य बने रहते और मध्यवर्ती संस्करणों को भी सफ़ाई से बचते देखना इस अंतर को स्पष्ट करता है। PostgreSQL के दृश्यता नियम तय करते हैं कि क्वेरी क्या लौटाती है; उसका सफ़ाई हॉराइज़न तय करता है कि पुराने संग्रहण का दोबारा उपयोग कब हो सकता है।

स्रोत