Postgres में UUID version 4 primary key से बचना क्यों चाहिए
(andyatkinson.com)- UUID v4 में बहुत अधिक randomness होती है, जिससे index inefficiency और excessive I/O होता है, और PostgreSQL में इसे primary key के रूप में इस्तेमाल करने पर performance गिरती है
- random inserts की वजह से page split और index fragmentation बार-बार होते हैं, साथ ही WAL log size बढ़ती है और write latency आती है
- UUID का आकार 16 bytes होता है, जो bigint से दोगुनी जगह लेता है, और इससे cache hit ratio घटता है तथा memory waste होता है
- इसे अक्सर security identifier समझ लिया जाता है, लेकिन RFC 4122 के अनुसार UUID, guess रोकने के लिए security mechanism नहीं है
- नए database में integer sequence-based key इस्तेमाल करने की सलाह दी जाती है, और अगर UUID ज़रूरी हो तो time-ordered UUID v7 बेहतर है
UUID v4 की performance समस्या
- PostgreSQL में UUID v4 primary key इस्तेमाल करने वाले database पिछले 10 सालों से लगातार performance degradation और excessive I/O दिखाते रहे हैं
- UUID v4 के 122 bits random होते हैं, इसलिए index ordering संभव नहीं होती
- insert के समय data sequential pages में store नहीं होता, जिससे random access होता है, और update/delete के समय भी inefficient lookup की ज़रूरत पड़ती है
- B-Tree index sorted data को ध्यान में रखकर बनाया गया है, लेकिन UUID v4 में ordering नहीं होती, इसलिए insert efficiency कम रहती है
- हर insert किसी भी arbitrary page पर लिखा जाता है, इसलिए mid-page split बार-बार होता है
- इससे write latency और WAL growth होती है
UUID की structure और alternatives
- UUID, 128-bit (16-byte) identifier होता है, और PostgreSQL में binary uuid type के रूप में store किया जाता है
- UUID v4 random bits पर आधारित है, जबकि UUID v7 अपने शुरुआती 48 bits में timestamp शामिल करता है, जिससे index efficiency बेहतर होती है
- PostgreSQL 18 (2025 के लिए निर्धारित) में UUID v7 का built-in support आने वाला है
- UUID v7 time-ordered sorting को संभव बनाता है, जिससे page density और cache efficiency बेहतर होती है
UUID चुनने के कारण और सीमाएँ
- multi-client या microservices environment में, जब collision-free identifier generation की ज़रूरत हो, तब UUID इस्तेमाल किया जाता है
- उदाहरण: कई database instances में एक साथ ID generate करना
- लेकिन RFC 4122 साफ़ कहता है: “UUID को unguessable मानकर न चलें”, इसलिए यह security identifier के लिए उपयुक्त नहीं है
- collision probability 2.71×10¹⁸ IDs generate होने पर 50% तक पहुँचती है; व्यवहार में collision दुर्लभ है, लेकिन performance cost बहुत ज़्यादा है
UUID की space और I/O inefficiency
- UUID, bigint (8 bytes) से दोगुनी और int (4 bytes) से चार गुना जगह लेता है
- बड़े tables में इससे storage space बढ़ती है और backup/restore समय भी बढ़ता है
- index page density प्रयोग के नतीजे
- integer index: 97.64%
- UUID v4 index: 79.06%
- UUID v7 index: 90.09%
- Cybertec test में UUID v4 index lookup के दौरान 8.5 million अतिरिक्त page accesses और 31229% I/O increase देखा गया
- समान conditions में bigint index ने 27,332 buffer accesses किए, जबकि UUID v4 ने 8,562,960 buffer accesses किए
cache और memory पर प्रभाव
- UUID की random distribution की वजह से buffer cache hit ratio कम रहता है
- cache में ज़्यादा pages load करने पड़ते हैं, और ज़रूरी pages अक्सर evict हो जाते हैं
- cache efficiency घटने से query latency बढ़ती है और memory usage भी बढ़ता है
- performance बनाए रखने के लिए regular index rebuild (
REINDEX CONCURRENTLY) या pg_repack इस्तेमाल करने की सलाह दी जाती है
performance कम करने के उपाय
- memory बढ़ाना: database size के 4 गुना RAM की सिफारिश (उदाहरण: DB 25GB → memory 128GB)
- work_mem tuning: sorting operations के लिए ज़्यादा memory allocate करके performance सुधारी जा सकती है
- Rails environment में
implicit_order_columnसेट करके UUID की जगहcreated_atजैसे sortable field का उपयोग किया जा सकता है - CLUSTER command से sortable field के आधार पर table को फिर से arrange किया जा सकता है, लेकिन इसके लिए exclusive lock चाहिए
integer key और sequence की सिफारिश
- नए database में integer sequence-based key इस्तेमाल करने की सलाह दी जाती है
integer(4 bytes) लगभग 2 अरब values देता है, जबकिbigint(8 bytes) उससे कहीं अधिक unique values देता है
- ज़्यादातर business apps के लिए integer काफ़ी है, जबकि बड़े scale की services में bigint अधिक उपयुक्त है
- UUID v4 की जगह UUID v7 या sequential_uuids extension इस्तेमाल करना एक व्यावहारिक विकल्प है
सारांश
- UUID v4 अपनी randomness की वजह से index inefficiency, high I/O, और poor cache efficiency पैदा करता है
- इसे security identifier के रूप में इस्तेमाल नहीं किया जा सकता, और इसमें space waste भी बहुत है
- integer sequence key ज़्यादातर applications के लिए बेहतर विकल्प है
- अगर UUID इस्तेमाल करना अनिवार्य हो, तो time-ordered UUID v7 चुनना चाहिए
- PostgreSQL में gen_random_uuid() को primary key के रूप में इस्तेमाल करने से बचना चाहिए
1 टिप्पणियां
Hacker News की राय
यह premature optimization का एक क्लासिक उदाहरण है
स्थायी identifiers में data भरना data management में वर्जित माना जाता है
नॉर्वे के resident number की तरह अगर जन्मतिथि को ID में डाल दिया जाए, तो बाद में ऐसे immigrants आ सकते हैं जिनकी जन्मतिथि पहले गलत दर्ज थी, या 1 जनवरी जन्मतिथि वाले इतने अधिक हो जाएँ कि नंबर कम पड़ने लगें
पुराने card catalog के दौर में lookup cost घटाने के लिए data और identifier को मिलाना समझ में आता था, लेकिन आज हमारे पास शक्तिशाली databases हैं, इसलिए इसकी खास ज़रूरत नहीं है
असली समस्या अज्ञात जन्मदिन को 1 जनवरी मान लेना था, तारीख को key में रखना अपने आप में मूल समस्या नहीं थी
अगर 00 या 99 जैसे non-date values इस्तेमाल किए गए होते, तो टकराव नहीं होता
UUID में timestamp डालने का उद्देश्य अर्थ जोड़ना नहीं, बल्कि performance optimization है
समय के क्रम में बढ़ने वाली keys B-tree rewrite cost कम करती हैं और DB insert performance बेहतर बनाती हैं
“स्थायी identifiers में data मत रखो” एक सामान्य सिद्धांत है, लेकिन परिस्थितियों के हिसाब से trade-off स्वीकार किए जा सकते हैं
उदाहरण के लिए md5 hash को UUID की तरह इस्तेमाल करके index बनाया जाए, तो fragmentation होती है, लेकिन वह प्रबंधनीय स्तर पर रह सकती है
random बनाम time-based UUID का चयन millisecond नहीं, बल्कि second स्तर का performance अंतर ला सकता है
बड़े DB में sharding और distribution अनिवार्य हो जाते हैं, इसलिए UUID auto-increment से बेहतर काम कर सकता है
अगर format DDMMYYXXXXX है, तो यह 1 लाख लोगों तक cover कर सकता है; क्या सच में इतनी भीड़ हो सकती है, यह सवाल है
शायद किसी खास साल में बड़ी संख्या में refugees के आने जैसी विशेष स्थिति रही होगी
UUID को security token की तरह इस्तेमाल नहीं करना चाहिए
सिर्फ इसलिए कि उसका अनुमान लगाना कठिन है, उसे security feature की तरह इस्तेमाल करना जोखिम भरा है
random values का उद्देश्य केवल guessing रोकना नहीं, बल्कि लगातार आने वाले IDs के बीच के संबंध को छिपाना भी है
DB के प्रकार के अनुसार PK strategy पूरी तरह बदल जाती है
Postgres में random PK अक्षम हो सकता है, लेकिन Cockroach या Spanner जैसे distributed DB में monotonically increasing keys उल्टे hot shard problem पैदा करती हैं
UUIDv7 में upper bits sortable होते हैं और lower bits random, इसलिए nodes के बीच distribution और local storage efficiency दोनों मिलते हैं
सामान्य non-sharded DB में random keys B-tree fragmentation कराती हैं
लेकिन अगर range query बहुत हों, तो random keys नुकसानदेह होती हैं
अंततः चयन workload characteristics के आधार पर होना चाहिए
लेख ने UUIDv4 को PK की तरह इस्तेमाल करने की कमियों को अच्छी तरह पकड़ा है, लेकिन प्रस्तावित integer obfuscation method production service के लिए उपयुक्त नहीं लगती
छोटे DB के लिए UUIDv7 एक तर्कसंगत मध्य मार्ग है
क्योंकि मैं generation time के उजागर होने से बचना चाहता हूँ
जब तक data इतना अधिक न हो कि UUIDv4 की randomness performance problem पैदा करे, v4 अधिक सुरक्षित विकल्प है
थोड़ी बहुत जानकारी लीक होती है, लेकिन operational रूप से यह काफ़ी अस्पष्ट रहता है
उदाहरण के लिए AES-128 से transform करके base64 में encode करें, तो यह YouTube video ID जैसा दिख सकता है
मैं technical due diligence के दौरान बहुत-सी companies देखता हूँ, और तेज़ sharding की संभावना business growth की कुंजी होती है
अगर हर table में UUID हो, तो sharding के समय structure बदले बिना scale किया जा सकता है
यह थोड़े space और time नुकसान से कहीं बड़ा scalability benefit देता है
आखिर में data model इतना जटिल था कि migration खुद ही मुश्किल थी, UUID हो या न हो उससे फर्क कम पड़ा
हमारा app integer PK को encrypt करके UUID जैसा दिखाता है
क्योंकि अगर sequential ID खुल जाए, तो customer count का अनुमान लगाना या dictionary attack करना संभव हो जाता है
encrypted ID में decryption failure के जरिए scanning attempts को तुरंत detect किया जा सकता है
“2 अरब काफ़ी हैं” जैसी बात जोखिम भरी है
लगभग हर DBA के पास ऐसी किसी decision से शुरू हुआ एक डरावना किस्सा होता है
लेख में कहा गया कि “random values को sort करना अक्षम है”, लेकिन वास्तव में byte-order sorting संभव है
हालांकि random keys sequential inserts नहीं होतीं, इसलिए B-tree rebalancing बार-बार होती है और performance गिरती है
integer PK के indexes memory में बेहतर fit हुए, जबकि UUIDv4 में page access ज़्यादा होने से latency बढ़ी
यह लेख समस्या से पहले समाधान लेकर आई premature optimization जैसा लगता है
UUIDv4 ज़्यादातर मामलों में पर्याप्त रूप से ठीक है
performance problem आने पर ही उस पर विचार करना चाहिए
संक्षेप में, Postgres में UUIDv7, v4 की तुलना में थोड़ा बेहतर performance दिखाता है
नए versions में plugin के बिना भी UUIDv7 support संभव है
uuidv7()function है, लेकिन extensions में इससे अधिक सुविधाएँ हैं या नहीं, यह अभी स्पष्ट नहीं है