लेखों की सूची और उनकी संख्या को सुसंगत रखने के लिए, दोनों रीड को एक ही node-postgres क्लाइंट पर एक REPEATABLE READ READ ONLY ट्रांज़ैक्शन में चलाएँ। यह ट्यूटोरियल इस पैटर्न और दो सेशन वाली उस जाँच को दिखाता है जिसे आप Mac से चला सकते हैं।
लेखों की सूची और उनकी संख्या में असहमति क्यों हो सकती है?
मान लीजिए कि कोई डेस्कटॉप स्क्रिप्ट craft विषय में प्रकाशित लेख प्राप्त करती है, फिर मेल खाने वाले लेखों की गिनती करती है। इन रीड के बीच, कोई दूसरा सेशन किसी लेख का विषय बदल देता है। लौटाई गई सूची में दो लेख हो सकते हैं, जबकि संख्या एक बताती है।
PostgreSQL का डिफ़ॉल्ट आइसोलेशन स्तर, READ COMMITTED, हर सामान्य SELECT को उस स्टेटमेंट के शुरू होने पर एक स्नैपशॉट देता है। इसलिए, एक ही ट्रांज़ैक्शन के भीतर भी लगातार किए गए रीड अलग-अलग कमिट किए गए डेटा देख सकते हैं। PostgreSQL: Transaction Isolation।
इस काम के लिए अपेक्षित नतीजा लेखों की पंक्तियों का एक सुसंगत दृश्य है। अगली बार चलाने पर बाद में किया गया संपादन दिख सकता है। PostgreSQL का REPEATABLE READ लगातार किए गए रीड को ट्रांज़ैक्शन के पहले गैर-ट्रांज़ैक्शन-कंट्रोल स्टेटमेंट से स्थापित स्नैपशॉट पर रखता है; केवल BEGIN चलाने से वह स्नैपशॉट स्थापित नहीं होता। PostgreSQL: Transaction Isolation।
कार्यान्वयन की दो ज़रूरतें हैं: पढ़ने से पहले आइसोलेशन स्तर चुनें और हर संबंधित क्वेरी एक ही क्लाइंट पर चलाएँ। node-postgres स्पष्ट रूप से कहता है कि ट्रांज़ैक्शन के लिए एक क्लाइंट इस्तेमाल करें और उसके स्टेटमेंट के लिए pool.query() का उपयोग न करें। node-postgres: Transactions।
अपने Mac पर लेखों का छोटा फ़िक्स्चर तैयार करें
चालू स्थानीय PostgreSQL सर्वर, psql कमांड और npm के साथ समर्थित Node.js इंस्टॉलेशन का उपयोग करें। नीचे दिए गए कमांड मानते हैं कि आपका मौजूदा स्थानीय कनेक्शन postgres डेटाबेस के साथ काम करता है। यदि आपके कार्यरत डेटाबेस का नाम कुछ और है, तो उसे हर जगह बदलें। PostgreSQL इंस्टॉलेशन और कनेक्शन सेटअप पहले से होना ज़रूरी है।
Terminal खोलें और एक प्रोजेक्ट फ़ोल्डर बनाएँ। दस्तावेज़ित node-postgres पैकेज pg है। node-postgres: Welcome।
mkdir article-snapshot-demo
cd article-snapshot-demo
npm init -y
npm install pg
export PGDATABASE=postgres
psql -d postgres
psql प्रॉम्प्ट पर यह सेटअप एक बार चलाएँ। यह एक समर्पित टेबल बनाता है जिसमें केवल कृत्रिम लेख-पंक्तियाँ होती हैं। स्पष्ट आइडेंटिफ़ायर आगे के अभ्यास को समझना भी आसान बनाते हैं।
CREATE TABLE public.article_snapshot_demo (
id integer PRIMARY KEY,
title text NOT NULL,
topic text NOT NULL,
published boolean NOT NULL
);
INSERT INTO public.article_snapshot_demo
(id, title, topic, published)
VALUES
(1, 'Paper Shapes', 'craft', true),
(2, 'Color Studies', 'craft', true),
(3, 'Desk Layouts', 'design', true);
यदि टेबल पहले से मौजूद है, तो उसे अपने आप हटाने या बदलने के बजाय रुकें और पिछले फ़िक्स्चर की जाँच करें। उदाहरणों में ठीक यही तीन शुरुआती पंक्तियाँ मानी गई हैं। Node.js स्क्रिप्ट चलाने से पहले psql को \q के साथ छोड़ें।
एक ही चेक-आउट किए गए क्लाइंट के ज़रिए सूची और संख्या पढ़ें
नीचे दिए गए पूरे प्रोग्राम को प्रोजेक्ट फ़ोल्डर में read-articles.mjs नाम से सेव करें। new Pool() दस्तावेज़ित PostgreSQL कनेक्शन एनवायरनमेंट वेरिएबल का उपयोग करता है, जिसमें ऊपर एक्सपोर्ट की गई PGDATABASE वैल्यू भी शामिल है। node-postgres: Connecting।
प्रोग्राम एक क्लाइंट लेता है, विशेषताएँ पहले से तय करके ट्रांज़ैक्शन शुरू करता है, दोनों रीड करता है और तैयार नतीजा लौटाने से पहले कमिट करता है। PostgreSQL BEGIN में इन विशेषताओं का सीधे समर्थन करता है; ट्रांज़ैक्शन COMMIT या ROLLBACK से समाप्त होता है। PostgreSQL: BEGIN।
import pg from 'pg';
const { Pool } = pg;
const pool = new Pool({ connectionTimeoutMillis: 5000 });
pool.on('error', (error) => {
console.error('Idle database client error:', error.message);
process.exitCode = 1;
});
async function readArticles(topic) {
const client = await pool.connect();
let began = false;
let discardClient = false;
try {
await client.query(
'BEGIN ISOLATION LEVEL REPEATABLE READ READ ONLY'
);
began = true;
const articles = await client.query(
`SELECT id, title, topic
FROM public.article_snapshot_demo
WHERE topic = $1 AND published = true
ORDER BY id`,
[topic]
);
const count = await client.query(
`SELECT count(*)::text AS total
FROM public.article_snapshot_demo
WHERE topic = $1 AND published = true`,
[topic]
);
await client.query('COMMIT');
began = false;
return {
topic,
articles: articles.rows,
total: count.rows[0].total
};
} catch (error) {
if (began) {
try {
await client.query('ROLLBACK');
} catch (rollbackError) {
discardClient = true;
throw new AggregateError(
[error, rollbackError],
'Article read failed; rollback also failed'
);
}
} else {
discardClient = true;
}
throw error;
} finally {
client.release(discardClient);
}
}
try {
const result = await readArticles('craft');
console.log(JSON.stringify(result, null, 2));
} catch (error) {
console.error(error);
process.exitCode = 1;
} finally {
await pool.end();
}
उसी Terminal सेशन में प्रोग्राम चलाएँ:
node read-articles.mjs
यदि फ़िक्स्चर में कोई बदलाव नहीं हुआ है, तो अपेक्षित नतीजे में 1 और 2 लेख होंगे तथा total की वैल्यू स्ट्रिंग "2" होगी। संख्या को स्पष्ट रूप से टेक्स्ट के रूप में लौटाया जाता है, इसलिए इस उदाहरण में उसे JavaScript संख्या में बदलने की ज़रूरत नहीं है। दोनों क्वेरी एक ही विषय और प्रकाशन फ़िल्टर का उपयोग करती हैं; आइसोलेशन बेमेल प्रेडिकेट ठीक नहीं कर सकता।
सफ़ाई की व्यवस्था भी इस उपयोगी पैटर्न का हिस्सा है। रीड विफल होने पर रोलबैक होता है और रोलबैक विफल होने पर क्लाइंट को छोड़ दिया जाता है। node-postgres चेक-आउट किए गए क्लाइंट को नष्ट करने के लिए client.release(true) और स्क्रिप्ट का पूल बंद करने के लिए pool.end() दस्तावेज़ित करता है। node-postgres: Pool API।
यह एक स्वतंत्र स्क्रिप्ट है, इसलिए एक बार चलने के बाद यह अपना पूल बंद कर देती है। मौजूदा ऐप्लिकेशन में ऐप्लिकेशन के पूल का जीवनचक्र बनाए रखें और ट्रांज़ैक्शन फ़ंक्शन को उसी के भीतर रखें। इन रीड में शामिल हर हेल्पर को चेक-आउट किया गया client देना चाहिए; चुपचाप pool.query() कॉल करने वाला हेल्पर इस सीमा को तोड़ता है। node-postgres: Transactions।
दो Terminal सेशन से स्नैपशॉट की पुष्टि करें
तेज़ स्क्रिप्ट किसी दूसरे सेशन के बदलाव करने से पहले पूरी हो सकती है। समय का असर स्पष्ट करने के लिए नीचे दिया गया क्रमबद्ध अभ्यास करें। ये पहले से बनाई गई टेबल पर सेशन-विशिष्ट SQL चरण हैं, अलग-अलग सेटअप प्रोग्राम नहीं। अपेक्षित डेटाबेस नतीजे दस्तावेज़ित आइसोलेशन व्यवहार पर आधारित हैं; इन्हें चलाए गए PostgreSQL परीक्षण के रूप में प्रस्तुत नहीं किया गया है।
दो Terminal विंडो खोलें और हर एक में psql -d postgres चलाएँ। रीडर शुरू करने से पहले, सेशन B में कृत्रिम फ़िक्स्चर का विषय बहाल करें। इस स्टेटमेंट के पूरा होने तक प्रतीक्षा करें:
-- Session B: restore the fixture before beginning the exercise.
UPDATE public.article_snapshot_demo
SET topic = 'craft'
WHERE id = 2;
सेशन A में रीडर ट्रांज़ैक्शन शुरू करें और सूची प्राप्त करें। क्वेरी पूरी होने के बाद रुकें:
-- Session A: run these statements, then pause.
BEGIN ISOLATION LEVEL REPEATABLE READ READ ONLY;
SELECT id, title
FROM public.article_snapshot_demo
WHERE topic = 'craft' AND published = true
ORDER BY id;
सूची में Paper Shapes और Color Studies होने चाहिए। अब सेशन B पर जाएँ और इस स्वतंत्र स्टेटमेंट से विषय बदलकर कमिट करें। किसी स्पष्ट ट्रांज़ैक्शन ब्लॉक के बाहर PostgreSQL सफल स्टेटमेंट को अपने आप कमिट करता है। PostgreSQL: BEGIN।
-- Session B: run while session A remains open.
UPDATE public.article_snapshot_demo
SET topic = 'design'
WHERE id = 2;
सेशन A पर लौटें और संबंधित रीड पूरा करें:
-- Session A: finish the original reader transaction.
SELECT count(*)::text AS total
FROM public.article_snapshot_demo
WHERE topic = 'craft' AND published = true;
COMMIT;
अपेक्षित संख्या 2 है, जो सेशन A की पहले प्राप्त सूची से मेल खाती है। इसके बाद Node.js स्क्रिप्ट को नए सिरे से चलाने पर केवल Paper Shapes और कुल "1" लौटना चाहिए। पुराना और नया नतीजा, दोनों अपने-अपने सुसंगत दृश्य दर्शाते हैं।
नियंत्रित तुलना के लिए, सेशन B में शुरुआती बहाली समेत पूरा अभ्यास दोहराएँ, लेकिन सेशन A को BEGIN ISOLATION LEVEL READ COMMITTED READ ONLY; से शुरू करें। रुकने और अपडेट करने का क्रम वही रखें। अपेक्षित शुरुआती सूची में दो लेख होंगे, जबकि बाद की संख्या 1 होगी। इस तुलना में बदली हुई शर्त केवल ट्रांज़ैक्शन सेटिंग है।
संबंधित रीड के लिए सही सीमा चुनें
उदाहरण को अपनाते समय इस निर्णय-सहायिका का उपयोग करें। पहले तय करें कि किन आउटपुट का आपस में मेल खाना ज़रूरी है, फिर ऐसा सबसे छोटा डेटाबेस ऑपरेशन चुनें जो उन्हें उपलब्ध कराए।
| रीडर का काम | उपयुक्त तरीका | बनाए रखने की शर्त |
|---|---|---|
| एक सामान्य क्वेरी से लेखों की एक सूची लौटाना | एक क्वेरी READ COMMITTED का उपयोग कर सकती है |
बाद की किसी क्वेरी को इसका स्नैपशॉट साझा करने की ज़रूरत नहीं होनी चाहिए। |
| सूची और अलग से क्वेरी की गई संख्या लौटाना | एक REPEATABLE READ READ ONLY ट्रांज़ैक्शन |
एक ही क्लाइंट और मेल खाते फ़िल्टर इस्तेमाल करें। |
| एक सुसंगत नतीजे के लिए कई बैच प्राप्त करना | रीड को एक ही ट्रांज़ैक्शन में रखें | नया ट्रांज़ैक्शन नया दृश्य स्थापित करता है। |
| अलग-अलग डेटाबेस सेशन में रीड का समन्वय करना | स्पष्ट स्नैपशॉट एक्सपोर्ट और इम्पोर्ट | स्वतंत्र ट्रांज़ैक्शन अपने आप स्नैपशॉट साझा नहीं करते। |
कई सेशन के लिए PostgreSQL pg_export_snapshot() और SET TRANSACTION SNAPSHOT देता है। अन्य ट्रांज़ैक्शन द्वारा स्नैपशॉट इम्पोर्ट किए जाने तक एक्सपोर्टर खुला रहना चाहिए। इम्पोर्ट करने वाले ट्रांज़ैक्शन को REPEATABLE READ या SERIALIZABLE का उपयोग करना चाहिए और पहली क्वेरी या डेटा-संशोधन स्टेटमेंट से पहले इम्पोर्ट करना चाहिए। Serializable इम्पोर्ट पर कमांड संदर्भ में अतिरिक्त प्रतिबंध दिए गए हैं। PostgreSQL: Snapshot Synchronization Functions, PostgreSQL: SET TRANSACTION।
इस डेस्कटॉप सूची और संख्या वाले काम के लिए, एक ही क्लाइंट बनाए रखने से यह अतिरिक्त समन्वय नहीं करना पड़ता। किसी व्यक्ति के अगली कार्रवाई चुनने की प्रतीक्षा करने से पहले डेटाबेस रीड पूरे करें और ट्रांज़ैक्शन बंद करें।
यह आइसोलेशन स्तर किन बातों को हल नहीं करता?
PostgreSQL का REPEATABLE READ फ़ैंटम रीड रोकता है, लेकिन सीरियलाइज़ेशन विसंगतियाँ फिर भी संभव हैं। इसलिए स्थिर स्नैपशॉट यह सुनिश्चित नहीं करता कि समवर्ती वर्कफ़्लो ऐसे चले जैसे उन्हें एक-एक करके निष्पादित किया गया हो। यह सीक्वेंस वैल्यू को भी स्थिर नहीं करता; सीक्वेंस का ट्रांज़ैक्शन व्यवहार विशेष होता है। PostgreSQL: Transaction Isolation।
READ ONLY सामान्य टेबल में बदलाव रोकता है, हालाँकि दस्तावेज़ित अपवाद हैं, जैसे कुछ अस्थायी टेबल ऑपरेशन। यह अपने आप स्थिर स्नैपशॉट नहीं चुनता। पढ़ने से पहले दोनों विशेषताएँ सेट करें: ट्रांज़ैक्शन की पहली क्वेरी या डेटा-संशोधन स्टेटमेंट के बाद PostgreSQL आपको आइसोलेशन स्तर बदलने नहीं देगा। PostgreSQL: SET TRANSACTION।
जब किसी रिपोर्ट को अधिक सख़्त serializable semantics चाहिए, तब PostgreSQL SERIALIZABLE READ ONLY DEFERRABLE का समर्थन करता है। स्नैपशॉट प्राप्त करते समय यह प्रतीक्षा कर सकता है, फिर सीरियलाइज़ेशन-विफलता रद्द किए बिना चलता है। यह समझौता यहाँ दिखाए गए सामान्य लेख-पठन की ज़रूरत से अलग है। PostgreSQL: SET TRANSACTION।
लेखों की सूची और संख्या के लिए, कार्यान्वयन की सीमा स्पष्ट रखें: एक क्लाइंट लें, आवश्यक विशेषताओं के साथ ट्रांज़ैक्शन शुरू करें, सभी संबंधित नतीजे पढ़ें, कमिट करें और क्लाइंट छोड़ें। वही फ़िल्टर दोबारा इस्तेमाल करें और ट्रांज़ैक्शन पूरा होने के बाद ही तैयार नतीजा प्रकाशित करें। इससे डेस्कटॉप की अगली कार्रवाई के लिए लेखों का एक सुसंगत डेटा-समुच्चय उपलब्ध होता है।
स्रोत
- Transaction Isolation — PostgreSQL Global Development Group।
- BEGIN — PostgreSQL Global Development Group।
- SET TRANSACTION — PostgreSQL Global Development Group।
- System Administration Functions: Snapshot Synchronization Functions — PostgreSQL Global Development Group।
- Transactions — node-postgres।
- Pool API — node-postgres।
- Connecting — node-postgres।
- Welcome — node-postgres।