3 पॉइंट द्वारा GN⁺ 2023-08-30 | 1 टिप्पणियां | WhatsApp पर शेयर करें
  • स्टोरेज स्पेस कम पड़ने की चेतावनी के बाद PostgreSQL की सफाई की गई, और index या data हटाए बिना 70GB से अधिक non-optimized और unused space वापस हासिल किया गया
  • शुरुआत में unused indexes, table·index bloat, REINDEX CONCURRENTLY, pg_repack, और PostgreSQL 13 की B-Tree deduplication जैसी सामान्य recovery techniques की जाँच की गई
  • निर्णायक अंतर यह था कि Oracle के विपरीत PostgreSQL NULL values को भी index करता है; cancelled_by_user_id के full index, जिसमें अधिकतर मान NULL थे, को partial index में बदलने पर उसका आकार 769MB से घटकर 5MB से कम रह गया
  • pg_stats.null_frac अधिक वाले single-column indexes खोजकर WHERE column IS NOT NULL शर्त वाले partial index candidates चुने गए, और statistics counters reset करने के बाद pg_stat_all_indexes से उनके वास्तविक उपयोग की पुष्टि की गई
  • Django में nullable ForeignKey डिफ़ॉल्ट B-Tree index बना सकता है, इसलिए db_index=False स्पष्ट रूप से सेट करना और models.Index(..., condition=Q(...__isnull=False)) से partial index को सीधे define करना ज़रूरी है

स्टोरेज स्पेस की चेतावनी से शुरू हुई सफाई

  • हर कुछ महीनों में database monitoring में स्टोरेज स्पेस कम होने की चेतावनी आती थी, और आम तौर पर इसका समाधान स्टोरेज बढ़ाकर किया जाता था
  • उस समय isolation period की वजह से system load कम था, इसलिए सामान्य से अधिक भारी cleanup work आज़माने के लिए यह अच्छा मौका था
  • नतीजतन, indexes हटाए बिना या data delete किए बिना 70GB से अधिक non-optimized और unused space वापस हासिल किया गया
  • सामान्य index·table reorganization से ही काफी जगह बची, और उसके बाद एक अप्रत्याशित खोज से लगभग 20GB अतिरिक्त unused index values भी हटाई गईं

सबसे पहले जिन कारणों पर शक हुआ

  • Unused indexes

    • Unused indexes query performance के लिए बनाए जाते हैं, लेकिन वे space लेते हैं और INSERTUPDATE को धीमा कर सकते हैं
    • pg_stat_all_indexes में idx_scan = 0, idx_tup_read = 0, idx_tup_fetch = 0 वाले indexes ढूँढकर यह देखा गया कि आख़िरी statistics reset के बाद कौन से indexes scan या fetch नहीं हुए
    • जो indexes ऊपर से unused दिखते हैं, वे हमेशा हटाने योग्य नहीं होते
      • जब PostgreSQL optimizer index खुद नहीं बल्कि उसका metadata उपयोग करता है
      • जब कोई index लंबे समय से update न हुई table पर unique या primary key constraint को enforce कर रहा हो
    • कौन-सा index वास्तव में delete किया जा सकता है, यह सूची को एक-एक करके देखकर ही तय करना पड़ता है; statistics counters को जाँच के बाद reset करके manage किया गया
    • इस मामले में, यह काम पहले से नियमित रूप से किया जाता था, इसलिए हटाने योग्य unused indexes नहीं मिले
  • Index और table bloat

    • PostgreSQL rows update करते समय पुरानी tuple को dead mark करता है और नई tuple को दूसरी जगह जोड़ता है, जिससे bloat पैदा हो सकता है
    • Bloat सिर्फ tables ही नहीं, indexes को भी प्रभावित करता है, जिससे वास्तव में ज़रूरी आकार से अधिक storage space इस्तेमाल होता है
    • Bloat का अनुमान लगाना आसान नहीं था, इसलिए PostgreSQL wiki और pgsql-bloat-estimation की queries से table और B-Tree index bloat का अनुमान लगाया गया

Index bloat कम करने के तरीके

  • Index को दोबारा बनाना·reorganize करना

    • Index bloat हटाने के लिए index को फिर से बनाना पड़ता है
    • सीधे drop करके create करने पर optimized form में नया index बनाया जा सकता है
    • PostgreSQL का REINDEX मौजूदा index को उसी जगह reorganize करता है
    • सामान्य recreation तरीका table पर lock लगाकर changes रोक देता है, इसलिए production environment में REINDEX INDEX CONCURRENTLY index_name ज़्यादा उपयुक्त है
    • REINDEX CONCURRENTLY _ccnew suffix वाला नया index बनाता है, changes sync करता है, फिर पूरा होने पर पुराने index से swap करके पुराना index delete कर देता है
    • अगर बीच में rebuild रुक जाए, तो नया index invalid स्थिति में रहकर space घेर सकता है; इसलिए _ccnew और indisvalid false वाले indexes ढूँढकर हटाने पड़ते हैं
  • PostgreSQL 13 की B-Tree deduplication

    • PostgreSQL 13 ने B-Tree indexes में duplicate values को अधिक efficiently store करने के लिए B-Tree Deduplication पेश की
    • PostgreSQL 12 तक, duplicate values अधिक होने पर भी हर duplicate value index leaf में बार-बार store होती थी, जिससे काफी जगह लग सकती थी
    • PostgreSQL 13 में deduplication enabled होने पर duplicate value सिर्फ एक बार store होती है, इसलिए duplicates अधिक वाले indexes के size पर इसका बड़ा असर पड़ सकता है
    • PostgreSQL 13 में deduplicate_items = ON default है, लेकिन अगर upgrade पुराने version से हुआ हो तो पूरा लाभ लेने के लिए REINDEX से index को फिर से बनाना होगा
    • 10 लाख rows के उदाहरण में, duplicate अधिक वाले non-unique column index का आकार deduplication के बाद 21MB से 6840kB हो गया, जबकि unique column index 21MB पर ही रहा
    • इस मामले में PostgreSQL 13 तब नया version था और cloud provider उसे support नहीं करता था, इसलिए deduplication से space नहीं बचाया जा सका

Table bloat और pg_repack

  • Table bloat हटाने की सीमाएँ

    • Tables में भी dead tuples के कारण bloat और fragmentation हो सकता है
    • Indexes के विपरीत, table को बस दोबारा बनाना आसान नहीं होता; इसके लिए नई table, data migration, synchronization, index·constraint·reference constraint recreation की ज़रूरत होती है
    • VACUUM FULL table_name bloat और dead tuples द्वारा घेरा गया space वापस ले सकता है, लेकिन इसके लिए table lock चाहिए
    • Table recreation और VACUUM FULL क्रमशः बड़े development effort या downtime का कारण बन सकते हैं
  • pg_repack का उपयोग

    • pg_repack PostgreSQL extension है, जिसका उपयोग downtime के बिना tables और indexes को reorganize करने के लिए किया जाता है
    • इसे उपयोग करने के लिए CREATE EXTENSION pg_repack; से extension create किया जाता है, और console में pg_repack -k --table table_name db_name जैसे चलाया जाता है
    • pg_repack नई table बनाता है, original data load करते समय नए data के साथ sync करता है, फिर indexes भी reorganize करता है और अंत में दोनों tables को swap कर देता है
    • Production में लागू करते समय कुछ सीमाएँ हैं
      • Reorganization target table और index के size के बराबर लगभग अतिरिक्त storage space चाहिए
      • अगर repack fail हो जाए या manually बीच में रोका जाए, तो बीच के objects रह सकते हैं और manual cleanup की ज़रूरत पड़ सकती है
      • अगर पहले से storage space कम हो, तो अतिरिक्त space requirement के कारण यह उपयुक्त नहीं हो सकता; इसलिए free space को monitor करना और पहले से planning करना ज़रूरी है

अप्रत्याशित खोज: PostgreSQL NULL को भी index करता है

  • सामान्य techniques से unused indexes हटाने और table·index bloat साफ़ करने के बाद भी कुछ space बचाई जा सकती थी
  • सबसे बड़ी tables में से एक transaction data store करती थी, और payment के बाद user द्वारा cancel करके refund लेने के cases भी record करती थी
  • Transaction table में purchasing user और cancelling user, दोनों के foreign key थे, और हर field पर B-Tree index define था
    • Purchasing user पर NOT NULL constraint था, इसलिए हर row में value मौजूद थी
    • Cancelling user nullable था, और cancelation बहुत कम होने की वजह से अधिकांश values NULL थीं
  • उम्मीद थी कि cancelling user index, purchasing user index से बहुत छोटा होगा, लेकिन वास्तव में दोनों का आकार समान था
  • Oracle में NULLs are not indexed सीखा था, लेकिन PostgreSQL में NULL values भी index होती हैं
  • मौजूदा full index इस प्रकार था
CREATE INDEX transaction_cancelled_by_ix
ON transactions(cancelled_by_user_id);
  • Hypothesis की जाँच के लिए इसे NULL values को छोड़ने वाले partial index में बदला गया
DROP INDEX transaction_cancelled_by_ix;

CREATE INDEX transaction_cancelled_by_part_ix
ON transactions(cancelled_by_user_id)
WHERE cancelled_by_user_id IS NOT NULL;
  • Reindexing के बाद full index का size 769MB था और 99% से अधिक values NULL थीं
  • NULL को छोड़ने वाला partial index 5MB से कम था, यानी index का 99% से अधिक हिस्सा कम हो गया
  • Table statistics reset करके कुछ समय observation करने पर पता चला कि नया index पुराने index की तरह ही उपयोग हो रहा था, और performance degradation के बिना 760MB से अधिक unused indexed tuples हट गए

Partial index candidates ढूँढना

  • एक partial index से अच्छे नतीजे मिलने के बाद, इसी तरह के indexes ढूँढने के लिए pg_stats.null_frac अधिक वाले columns के indexes खोजे गए
  • null_frac PostgreSQL द्वारा अनुमानित उस column में NULL ratio है
  • Candidate search query में ये conditions लागू की गईं
    • Primary key partial नहीं हो सकती, इसलिए उसे छोड़ा गया
    • जो पहले से partial index थे, उन्हें छोड़ा गया
    • Composite indexes छोड़े गए
    • सिर्फ 10MB से बड़े indexes शामिल किए गए
  • Example result में tx_cancelled_by_ix 1418MB था और null_frac 96.15% था, इसलिए लगभग 1363MB बचत की संभावना वाले candidate के रूप में दिखा
  • Index से सभी NULL को बाहर करना हमेशा फ़ायदेमंद नहीं होता
    • IS NULL condition से NULL खोजने वाली queries NULL values पर index के लाभ पा सकती हैं
    • सिर्फ non-NULL values ही नहीं, बल्कि ऐसे अन्य values भी partial index से बाहर किए जा सकते हैं जिनकी query बहुत कम होती है
  • शीर्षक में 20GB इसलिए संभव हुआ क्योंकि primary database में लगभग 10GB घटने पर replica में भी लगभग उतनी ही storage साथ में कम हो गई

Django ORM में partial index पर migrate करना

  • Nullable ForeignKey और implicit index

    • अगर db_index=False स्पष्ट रूप से न दिया जाए, तो Django models.ForeignKey field पर B-Tree index implicitly create करता है
    • Canceling user को store करने वाले nullable ForeignKey पर कोई setting न होने पर Django full index बना देगा
    • Partial index बनाने के लिए FK field में db_index=False देना होगा और Meta.indexes में conditional models.Index जोड़ना होगा
    class Transaction(models.Model):
        cancelled_by_user = models.ForeignKey(
            to=User,
            null=True,
            on_delete=models.CASCADE,
            db_index=False,
        )
    
        class Meta:
            indexes = (
                models.Index(
                    fields=('cancelled_by_user_id', ),
                    name='%(class_name)s_cancelled_by_part_ix',
                    condition=Q(cancelled_by_user_id__isnull=False),
                ),
            )
    
    • Nullable foreign key अक्सर partial index candidate बनने के लिए अच्छा होता है
    • Implicit feature अनजाने में index न बना दे, इसके लिए Django check बनाकर foreign key में db_index को हमेशा explicitly लिखने के लिए enforce किया गया
  • Downtime के बिना full index को partial index में बदलना

    • Full index को partial index से replace करते समय downtime या performance degradation से बचने के लिए इसे चरणबद्ध तरीके से करना चाहिए
    • संबंधित Django model को बदलकर full index की जगह partial index किया गया, लेकिन Django द्वारा generated migration को वैसे का वैसा run नहीं किया गया
    • वह migration FK constraint disable कर सकती थी, मौजूदा full index drop कर सकती थी और फिर नया partial index बना सकती थी, जिससे downtime और performance degradation हो सकता था
    • ./manage.py sqlmigrate से SQL बनाया गया, उसमें से सिर्फ CREATE INDEX निकाला गया, उसे CONCURRENTLY के साथ बदला गया और database में manually चलाया गया
    • चूँकि पुराना full index अभी मौजूद था, इसलिए partial index बनने के दौरान queries पुराने index का इस्तेमाल करती रहीं
    • यह जाँचने के लिए कि नया partial index उपयोग हो रहा है या नहीं, full index statistics counter को pg_stat_reset_single_table_counters(<full index oid>) से reset किया गया
    • उसके बाद pg_stat_all_indexes के idx_scan, idx_tup_read, idx_tup_fetch को observe करके full index और partial index के उपयोग की तुलना की गई
    • जब यह तय हो गया कि partial index उपयोग हो रहा है, तब full index drop किया गया और वास्तव में मिली storage savings की पुष्टि की गई
    • Database state model state से मेल खाने के बाद ./manage.py migrate --fake से Django migration को executed के रूप में mark किया गया
    • Dev, QA, staging जैसे environments में, जहाँ downtime की चिंता कम थी, Django migration सामान्य रूप से चलाई गई और full index partial index से replace हो गया

अंतिम सार

  • सिर्फ disk, storage parameters और configuration optimization से performance और space improvement की एक सीमा होती है
  • आख़िरी सुधार की गुंजाइश database objects में थी, और इस मामले में वह index definition बदलने में मिली
  • अपनाई गई प्रक्रिया को तीन चरणों में समेटा जा सकता है
    • Unused indexes हटाना
    • Table और index repack करना, और संभव हो तो B-Tree deduplication enable करना
    • सिर्फ ज़रूरी values को index करने के लिए partial index का उपयोग करना
  • ऐसी सफाई अतिरिक्त storage provision करने से पहले कुछ और दिनों की राहत देने वाला एक व्यावहारिक विकल्प बन सकती है

1 टिप्पणियां

 
GN⁺ 2023-08-30
Hacker News की राय
  • storage बहुत हो तब भी index को छोटा रखना अच्छा है। इससे hot set में ज्यादा data आ सकता है
    हालांकि TB-स्केल database चलाने के नजरिए से “बस और storage जोड़ दो” भी हमेशा एक वैध विकल्प है। खासकर cloud के बाहर तो और भी, और अपने hardware पर enterprise NVMe SSD करीब $80/TB, DDR4 RAM करीब $1.20/GB के स्तर पर है। engineering के 4 घंटे की लागत मोटे तौर पर $1000 मानें, तो उससे 800GB RAM या 12TB storage खरीदा जा सकता है

    • nominal capacity और पूरे system के लिए वास्तव में खरीदी जाने वाली capacity के बीच multiplier effect होता है। आप सिर्फ 1TB नहीं खरीदते; कम से कम 2 या उससे ज्यादा live failover server चाहिए होते हैं, और backup व disaster recovery की भी कई layers चाहिए होती हैं
      सिर्फ 1 साल के weekly backup में ही 1TB बढ़कर 50TB हो जाता है, offline storage हो तब भी यही बात है। हमारी company में भी live DB में storage जोड़ना आसान था, इसलिए कई साल तक हमने ऐसा ही किया, लेकिन multi-TB backup को move करना धीरे-धीरे इतना झंझटभरा हो गया कि engineering cost लगाकर भी production data घटाना जरूरी हो गया
    • यह तुलना करने का तरीका बहुत अच्छा नहीं है। engineering time एक बार चुकाने पर समस्या हल होने की संभावना होती है, लेकिन 10 SSD को rack space, बिजली, PCIe slots, समय पर replacement, management software चाहिए होते हैं और इनमें से ज्यादातर recurring cost बन जाते हैं
      एक बार के मामले में existing infra खाली rack space या extra PCIe slot जैसी लागत absorb कर सकता है, लेकिन ऐसी amortization छोटे scale पर ही अच्छी तरह काम करती है। साथ ही, system जितना बड़ा होता है, management cost और latency बढ़ने के कारण प्रति equipment unit performance अनिवार्य रूप से घटती है। अगर लगातार system बड़ा करके ही हल निकालेंगे, तो पूरा system धीरे-धीरे सुस्त होता जाएगा और शायद operate करना असंभव हो जाए। उल्टा, जरूरी system resources को घटाने वाला solution प्रति unit resource performance बढ़ाता है, और अपने-आप में बेहतर quality asset बनाता है
    • scale और complexity पर निर्भर करता है, लेकिन अगर resource usage को control नहीं किया गया तो cost exponential रूप से बढ़ सकती है। यहां तक कि business न बढ़े तब भी requirements ज्यादा complex होने से ऐसा हो सकता है
      कोई optimization आज 1TB बचाती है तो कुछ साल बाद 2TB बचा सकती है, और ऐसे कुछ फैसलों से ही single digit से ज्यादा का फर्क पड़ सकता है। बड़े scale पर यह खासकर सच है, और आखिरकार हमेशा balance बनाना पड़ता है
    • $1000 में मौजूदा server में लग सकने वाली 800GB extra RAM खरीदना मुश्किल है। ऐसी RAM आम तौर पर $3~6/GB के आसपास होती है; non-certified product $3, और motherboard manufacturer certified RAM करीब $6 होती है। basic RAM configuration की तुलना में DIMM slot cost भी करीब $0.50~$1/GB extra जुड़ती है
      “hardware झोंक देना” कभी-कभी समझदारी हो सकता है, लेकिन ज्यादातर मामलों में आज के 128GB RAM server को कल $1K में 1TB RAM में बदलना मुश्किल ही लगता है
    • cloud में भी अगर storage कम इस्तेमाल करने का कोई concrete idea नहीं है, तो आम तौर पर जवाब और storage provision करना ही होता है
      हालांकि यह one-time spend नहीं बल्कि monthly cost है, इसलिए हिसाब थोड़ा ज्यादा complex है। बेशक, अगर database एक स्थिर दर से बढ़ रहा है, तो on-premises की one-time provisioning भी असल में monthly cost जैसी ही है
  • लेख में PostgreSQL 13 में मौजूद लेकिन इस्तेमाल हो रहे 12 में नहीं मौजूद B-tree deduplication का जिक्र है, और कहा गया है कि कुछ foreign key index values में बहुत बड़ा हिस्सा NULL है
    इस case में B-tree deduplication मददगार होता या नहीं, यह जानने की उत्सुकता है। PostgreSQL 13 docs के 63.4.2 को देखें तो ऐसा लगता है: “B-Tree deduplication is just as effective with ‘duplicates’ that contain a NULL value...” बेशक, लेख में लागू किए गए partial index जितना effective नहीं होगा, लेकिन interesting है
    https://www.postgresql.org/docs/13/btree-implementation.html

    • पिछली discussion में किसी ने calculate किया था कि pg12 में हर NULL 16 bytes का है और pg13 में हर NULL 6.32 bytes का था। काफी meaningful saving है
      https://news.ycombinator.com/item?id=25989467
  • यह लेख सिर्फ मुख्य बात, यानी partial index की “खोज”, ही नहीं बताता, बल्कि PostgreSQL में space inefficient तरीके से इस्तेमाल होने की चिंता हो तो जानने लायक अच्छी techniques का कुल मिलाकर उपयोगी सार भी देता है
    हालांकि अगर startup या early-stage company है, तो यह याद रखने लायक है कि ऐसे storage issues में size optimization पर ध्यान देने के बजाय और disk space जोड़ने का विकल्प लगभग हमेशा बेहतर होता है। developers महंगे होते हैं और disk सस्ती

    • यह अच्छी सलाह है। शुरुआत में आम तौर पर जितना हो सके simple रखना बेहतर होता है
      पहले जब मैं तेजी से बढ़ रहे startup में काम करता था, founders में से एक लगातार सिर्फ एक server upgrade करते रहने पर अड़ा था; redundancy और backups थे, लेकिन architecture simple था, इसलिए manage, debug और recover करना आसान था। company के पहले 5 साल तक पूरा system एक ही server पर चलता रहा, फिर भी exponentially grow हुआ और दुनिया भर के लाखों users को handle किया। उसे देखने के बाद से मुझे यकीन है कि जरूरत पड़ने पर ही, सबसे simple और सीधी तरीके से upgrade करना चाहिए
    • इस बात पर काफी जोर दिया जाता है कि बस और storage जोड़ दो, लेकिन unnecessary indexing write और read performance को भी प्रभावित करती है और कुछ मामलों में काफी ज्यादा
      जहां ज्यादातर values NULL हों, ऐसे use case से साफ मेल खाने वाला partial index इस्तेमाल करना सही modeling के ज्यादा करीब है; इसे premature optimization या developer time की बर्बादी नहीं मानना चाहिए
  • इसी संदर्भ में ये scripts काफी मददगार रहीं
    https://github.com/NikolayS/postgres_dba
    बड़ी table के column order को restructure करके storage का करीब 10%, यानी लगभग 100GB वापस पा सके

  • पिछले हफ्ते सिर्फ reindex और pg_repack से 600GB DB में 200GB वापस पाए। सबसे खराब table में data 17GB था लेकिन indexes 142GB थे, और reindexing के बाद 21GB तक घट गए
    इस table में indexing बहुत ज्यादा थी, इसलिए कई column combinations के लिए कई indexes थे। विशाल index के कारणों में से एक data distribution रहा होगा। 2015 से insert, update, delete लगातार होते रहे हैं, और पुराने data के delete होने की संभावना ज्यादा होने के कारण recent years का data ज्यादा है, लेकिन फिर भी करीब 0.1% data 2015 का है। मुझे लगता है कि ऐसी long-tail वाली skewed distribution की वजह से vacuum के लिए index bloat संभालना ज्यादा मुश्किल रहा होगा

  • उस समय भी इस पर चर्चा हुई थी: An unexpected find that freed 20GB of unused index space in PostgreSQL - https://news.ycombinator.com/item?id=25988871 - फरवरी 2021, 78 comments

  • उपयोग में न आने वाले indexes, optimization opportunities और high-latency queries खोजने के लिए pganalyze.com की जोरदार सिफारिश

  • सच में “एक अजीब trick से 20GB बचत” कहना कोई अतिशयोक्ति नहीं था। पुराने Oracle / SQL Server environments में लगभग कभी न सुने गए PostgreSQL के अनोखे index forms इस्तेमाल करने की मांगें बहुत मिलती हैं, लेकिन यह नहीं पता था कि NULL values भी indexed होती हैं

    • यह सुनकर कि यह बात नई है, थोड़ा सुकून है कि सिर्फ मैं ही इसे नहीं सोच पाया। लगता है हमारे पास भी कुछ बड़े indexes होंगे जिन्हें इसी तरह handle किया जा सकता है
  • जिस पहले बड़े project में शामिल हुआ था, उसमें dataset बढ़ने पर team समझ नहीं पा रही थी कि काम धीमा क्यों हो गया
    indexes में per-record insert time log(n) होता है। test DB में records 1000 से बढ़कर 65k के करीब हो जाएं तो insert time 60% बढ़ जाता है (2^10 बनाम 2^16)। सफलता ही हर चीज़ को धीमा बना देती है, और server upgrade की भी एक सीमा होती है। business की मांगी हुई किसी अस्पष्ट feature के कारण कुछ और indexes जोड़ दें, तो अब यह दोगुना हो जाता है

    • बहुत छोटी संख्या ही तो दोगुनी हो रही है। अगर वही सच में समस्या है, तो तेज़ disk इस्तेमाल करें। 2015 के मेरे PC से कम IOPS वाले EBS पर DB चलाने की कोशिश करने के मामले बहुत ज़्यादा हैं
      करोड़ों records और प्रति table/collection 40 से ज़्यादा indexes वाले कई DB भी manage कर रहा हूं
    • ऐसे छोटे indexes में inserts फिर भी बहुत तेज़ होने चाहिए, ऐसा लगता है
      B-tree page के अंदर binary search 100 cycles से कम है। 10 करोड़ records पर B-tree traversal भी फिर भी microseconds के order में होना चाहिए, और उसके ऊपर की binary search भी, सैकड़ों nanoseconds न सही, microseconds के order में होगी
  • क्या यह principle तब भी लागू होगा जब एक या कुछ values बहुत common हों? उदाहरण के लिए integer column के 90% values 0 हों

    • हां। Postgres सबसे common values को track करता है। अगर उसे पता है कि 0 common है और where val = 0 rows का 90% छोड़ता है, तो वह index के बजाय table scan चुन सकता है
    • indexes unique value sets को represent करते समय सबसे efficient होते हैं। grouping के लिए भी वे अभी भी उपयोगी हैं, लेकिन अगर groups बहुत ही छोटे अनुपात को दर्शाते हैं, तो index खंगालने में काफी space और cycles waste होंगे
    • क्या 0 वाले cases और non-0 cases के लिए अलग indexes नहीं बनाए जा सकते?
    • ऐसा केवल तब है जब common value को NULL से represent किया गया हो; आम तौर पर यह अच्छा idea नहीं है