- डेटा किसी खास तारीख (पिछले मंगलवार) के समय कैसा दिखता था, ऐसे सवालों का जवाब देने के लिए Postgres 19 native temporal table सपोर्ट ला रहा है, जिससे अलग audit trigger सिस्टम के बिना बदलाव से पहले और बाद का डेटा ट्रैक किया जा सकेगा
- SQL:2011 standard ने एक दशक से भी पहले Temporal tables को परिभाषित किया था, लेकिन Postgres अब जाकर इसे core में ला रहा है, यानी दूसरे DB engines की तुलना में यह देर से जुड़ रहा है
- पहले
valid_from/valid_toदो कॉलम औरbtree_gistextension पर आधारित exclusion constraint का इस्तेमाल होता था, लेकिन अब एक ही range type कॉलम औरWITHOUT OVERLAPSconstraint के साथ इसे ज्यादा सहज तरीके से व्यक्त किया जा सकेगा FOR PORTION OFsyntax के जरिए UPDATE·DELETE के समय rows का automatic split होगा, और time axis पर gap या overlap रोकने का काम engine खुद करेगा- यह जोड़ bi-temporal systems के सिर्फ valid time (application time) वाले आधे हिस्से को कवर करता है; system time अभी समर्थित नहीं है, लेकिन आगे की releases के लिए नींव तैयार हो गई है
पुराना तरीका - The Old-Fashioned Way
- किसी product की price history को समय के हिसाब से ट्रैक करने की शुरुआती कोशिश
valid_from,valid_toदो date columns औरvalid_from < valid_toCHECK constraint से बनती थी- लेकिन यह एक ही product के लिए date range overlap करने वाली दो rows को insert होने से नहीं रोक पाता था (जैसे product 42 का उसी मंगलवार को $9.99 और $14.99 दोनों होना)
- पारंपरिक समाधान
btree_gistextension और exclusion constraint का उपयोग थाEXCLUDE USING gist (product_id WITH =, daterange(valid_from, valid_to) WITH &&)जैसे रूप में, overlap करने वाली row insert करने पर error मिलता था
- इस तरीके की समस्याएँ
- GiST Postgres का अपना index type है, जिसे समझने के लिए अनुभव चाहिए, और optional extension होने से entry barrier भी बनता है
- exclusion constraint का syntax सहज नहीं है, इसलिए इसे standard approach के रूप में सोचना आसान नहीं
- table में temporal awareness built-in नहीं होती, इसलिए time range बदलने पर rows को manually split या merge करना पड़ता है, और temporal consistency का बोझ application पर आ जाता है
समय का संक्षिप्त इतिहास - A Brief History of Time
- SQL:2011 standard ने valid time period (
APPLICATION TIME),WITHOUT OVERLAPSconstraint, और temporal data manipulation के लिएFOR PORTION OFsyntax पेश किया - Henrietta Dombrovskaya (Hetti) ने Chad Slaughter के साथ मिलकर pg_bitemporal extension विकसित किया, जो PL/pgSQL में Postgres के भीतर bi-temporal tables को manage करने का framework है
- 2015 से कई conferences में इस concept को पेश किया गया, और valid time (वह समय जब वास्तविक दुनिया में कोई तथ्य सही है) तथा transaction time (वह समय जब DB ने उस तथ्य को रिकॉर्ड किया) को साथ ट्रैक करने का तरीका दिखाया गया
- दो time dimensions का फर्क
- valid time का मतलब है: "यह price जनवरी से जून तक valid थी"
- transaction time का मतलब है: "यह row 12 मार्च को 3:47 PM पर insert हुई, और 3 अप्रैल को 9:01 AM पर replace हुई" — यानी DB के नज़रिये से
- दोनों को जोड़ने पर ऐसा bi-temporal table बनता है जो यह जवाब दे सकता है: "पिछले मंगलवार की price के बारे में उस समय हम क्या मानते थे?"
- pg_bitemporal
EXCLUDE USING gistका दो बार उपयोग करता है, एकeffectiverange (valid time) के लिए और एकassertedrange (transaction time) के लिए- यह bi-temporal insert, update, correction, inactivation, deletion functions और temporal inference के लिए Allen's interval relationships भी देता है
- extension की सीमाएँ
- query planner को temporal conditions समझाने, engine-level constraint system के साथ integrate करने, या native manipulation syntax देने की क्षमता नहीं होती → इसलिए core support ज़रूरी था
- Postgres 19 bi-temporal system के valid time वाले आधे हिस्से को अपनाता है; पूरा नहीं, पर यह बड़ी प्रगति है
range से समाधान - Ranges to the Rescue
- Postgres 19 में अलग
valid_from/valid_toके बजाय एक ही range type कॉलमvalid_at DATERANGEइस्तेमाल होता हैPRIMARY KEY (product_id, valid_at WITHOUT OVERLAPS)के साथ अबbtree_gistextension और exclusion constraint की ज़रूरत नहीं रहतीWITHOUT OVERLAPSयह सुनिश्चित करता है किproduct_idकिसी भी समय बिंदु पर unique रहे, लेकिन range overlap न होने पर उसी product की कई rows हो सकती हैं
- अंदरूनी तौर पर अभी भी GiST index इस्तेमाल होता है, और key के non-temporal columns के लिए
btree_gistचाहिए, लेकिन constraint initialize करते समय Postgres इसकी dependency खुद संभाल लेता है - range notation में
[का मतलब include और)का मतलब exclude है →[2025-01-01, 2025-07-01)में 1 जनवरी शामिल है, 1 जुलाई नहीं- आख़िरी Gadget row
[2026-01-01,)open-ended range है, यानी current price के लिए कोई defined end date नहीं
- आख़िरी Gadget row
- overlap protection कैसे काम करता है
- गलत range
[2025-03-01, 2025-01-01)insert करने पर "range lower bound must be less than or equal to range upper bound" जैसी error मिलती है - overlap करने वाली range
[2025-03-01, 2025-09-01)insert करने परproducts_pkeyexclusion constraint violation error आती है - सिर्फ range का उपयोग करके दो तरह की validation एक साथ मिल जाती है
- गलत range
काटना और बाँटना - Slicing and Dicing
- अगर किसी product की price सिर्फ मार्च 2025 से सितंबर 2025 तक $10.99 करनी हो, तो पुराने तरीके में rows को manually split और insert करना पड़ता था, और गलती होने पर gap या overlap बन सकता था
- temporal history table में इसे सीधे इरादे के अनुसार लिखा जा सकता है
UPDATE products FOR PORTION OF valid_at FROM '2025-03-01' TO '2025-09-01' SET price = 10.99 WHERE product_id = 1
- नतीजे में Widget की rows 3 से बढ़कर 5 हो जाती हैं
- पुरानी $9.99 row
[2025-01-01, 2025-03-01)तक सिमट जाती है - बची हुई range के लिए नई $10.99 row जुड़ती है
- पुरानी $12.99 row
[2025-09-01, 2026-01-01)तक सिमट जाती है - और बची हुई
[2025-07-01, 2025-09-01)range के लिए नई $10.99 row जुड़ती है
- पुरानी $9.99 row
- $10.99 दो rows में बँटने का कारण यह है कि
FOR PORTION OFmatch होने वाली हर row को अलग-अलग process करता है, और बाद में adjacent ranges को merge (coalesce) नहीं करता- अंतिम परिणाम में न gap होता है, न overlap; यह सिर्फ exclusion logic से मिलने वाले परिणाम से बेहतर है
- edge cases
- अगर
FOR PORTION OFrange किसी एक existing row के भीतर पूरी तरह आती है, तो अधिकतम 2 residual rows (पहले और बाद के हिस्से) बन सकती हैं - अगर यह existing boundary से ठीक-ठीक मेल खाए, तो residual rows की ज़रूरत नहीं
- अगर
- नई बनने वाली temporal residual rows के लिए
INSERTpermission की ज़रूरत नहीं होती, लेकिन existingINSERTtriggers फिर भी fire होते हैं → इसलिए audit logging याSECURITY DEFINERtrigger functions में सावधानी चाहिए
इतिहास मिटाना - Erasing History
FOR PORTION OFDELETEमें भी काम करता है; उदाहरण के लिए, किसी खास product को जून 2025 से अक्टूबर 2025 तक catalog से अस्थायी रूप से हटानाDELETE FROM products FOR PORTION OF valid_at FROM '2025-06-01' TO '2025-10-01' WHERE product_id = 2
- परिणाम
- जून से अक्टूबर का हिस्सा कट जाता है, और
[2025-04-01, 2026-01-01)को कवर करने वाली $22.99 row दो residual rows में बँट जाती है — एक जून पर खत्म होती है और दूसरी अक्टूबर से शुरू होती है - gap के पहले और बाद की price information अपनी मूल value के साथ बनी रहती है, यानी DELETE से rows की संख्या बढ़ भी सकती है
- जून से अक्टूबर का हिस्सा कट जाता है, और
- temporal history table का management mechanism यह सब automatically संभाल लेता है, जिससे application level पर over-delete या orphaned fragment का खतरा खत्म होता है
विज्ञापन की सच्चाई - Truth in Advertising
- temporal history tables temporal foreign key के बिना पूरी नहीं मानी जा सकतीं, और Postgres 19 इसे
PERIODkeyword के साथ support करता हैFOREIGN KEY (product_id, PERIOD valid_at) REFERENCES products (product_id, PERIOD valid_at)जैसे रूप में
PERIODkeyword यह बताता है कि foreign key खुद temporal है- referenced product को variant के
valid_atrange की पूरी अवधि के दौरान मौजूद होना चाहिए - reference table की सभी matching rows का combination referenced row की पूरी अवधि को cover करना चाहिए
- referenced product को variant के
- product के time range से आगे जाने वाली variant बनाने की कोशिश (
[2025-01-01, 2027-01-01)) reject कर दी जाती है- अगर Widget की price सिर्फ 2026 के मध्य तक defined है, तो 2027 तक valid होने का दावा करने वाला variant foreign key constraint violation से reject होगा
- एक अहम सीमा
- temporal foreign key referential action के रूप में सिर्फ
NO ACTIONsupport करती है;CASCADE,SET NULL,SET DEFAULTसमर्थित नहीं हैं - अगर कोई product row, जिस पर variant निर्भर है, delete की जाती है, तो हमेशा error आएगी; temporal cascade operations की जटिलता के कारण इसे application को explicitly handle करना होगा
- temporal foreign key referential action के रूप में सिर्फ
छोटे कदम - Baby Steps
- अभी जो capabilities मिली हैं: overlap protection के साथ valid-time-based temporal history tables, temporal data manipulation, और temporal foreign keys
- सबसे बड़ी कमी अब भी transaction time, यानी system time है
- valid time उस समय को ट्रैक करता है जब तथ्य वास्तविक दुनिया में सही था, जबकि system time उस समय को ट्रैक करता है जब DB ने उसे जाना; बहुत से systems दोनों का इस्तेमाल करते हैं
- यही वह क्षेत्र है जिसे 2015 से pg_bitemporal extension भरता आ रहा है
- triggers के जरिए system time का emulation संभव है, लेकिन यह उन नए temporal features जैसा नहीं है जिन्हें engine transparently manage करता है
- temporal history tables के docs भी साफ़ कहते हैं कि system time का native support नहीं है, हालांकि उसका emulation किया जा सकता है; Postgres 20 या उसके बाद यह आएगा या नहीं, तय नहीं, लेकिन आधार बन चुका है
अंतिम विचार - Final Thoughts
EXCLUDE USING gistवाला तरीका काम करता है, लेकिन यह अपेक्षाकृत भद्दा workaround है; pg_bitemporal जैसे extensions ने concept को साबित किया और चर्चा को आगे बढ़ाया- GiST exclusion constraints की तुलना में यह तरीका कहीं ज्यादा सहज है
- primary key में
WITHOUT OVERLAPSसाधारण अंग्रेज़ी जैसा पढ़ा जाता है, औरFOR PORTION OFअपने व्यवहार को सीधे व्यक्त करता है - temporal update और delete के समय automatic row splitting संभावित bugs की एक पूरी श्रेणी को खत्म कर देता है
- primary key में
- SQL:2011 से Postgres 19 तक की यात्रा लंबी रही है; Hetti और community ने वर्षों तक इस pattern की ज़रूरत और व्यवहारिकता साबित की, और अब यह core में आ गया है
- आगे की releases में system time support पर नज़र रखना ज़रूरी होगा; जब Postgres bi-temporal के दोनों आधे हिस्से पा लेगा, तो इसकी संभावनाएँ बहुत बढ़ जाएँगी
अभी कोई टिप्पणी नहीं है.