ट्रिग्राम खोज बहुत धीमी हो जाती है क्योंकि खोज स्ट्रिंग अधिक लंबी हो जाती है


16

9.1 डेटाबेस में एक पोस्टग्राउंड में, मेरे पास table1~ 1.5M पंक्तियों वाली एक तालिका और एक कॉलम है label(इस प्रश्न के लिए सरलीकृत नाम)।

एक कार्यात्मक ट्रिग्राम-इंडेक्स है lower(unaccent(label))( unaccent()इसे इंडेक्स में इसके उपयोग की अनुमति देने के लिए अपरिवर्तनीय बनाया गया है)।

निम्नलिखित क्वेरी काफी तेज़ है:

SELECT count(*) FROM table1
WHERE (lower(unaccent(label)) like lower(unaccent('%someword%')));
 count 
-------
     1
(1 row)

Time: 394,295 ms

लेकिन निम्नलिखित क्वेरी धीमी है:

SELECT count(*) FROM table1
WHERE (lower(unaccent(label)) like lower(unaccent('%someword and some more%')));
 count 
-------
     1
(1 row)

Time: 1405,749 ms

और अधिक शब्दों को जोड़ना और भी धीमा है, हालांकि खोज कठोर है।

मैंने पहले शब्द के लिए एक सबकुछ चलाने के लिए एक सरल चाल की कोशिश की और फिर पूरी खोज स्ट्रिंग के साथ एक क्वेरी, लेकिन (दुख की बात है) क्वेरी प्लानर ने मेरी यंत्रणा के माध्यम से देखा:

EXPLAIN ANALYZE
SELECT * FROM (
   SELECT id, title, label from table1
   WHERE lower(unaccent(label)) like lower(unaccent('%someword%'))
   ) t1
WHERE lower(unaccent(label)) like lower(unaccent('%someword and some more%'));
तालिका 1 पर बिटमैप हीप स्कैन (लागत = 16216.01..16220.04 पंक्तियाँ = 1 चौड़ाई = 212) (वास्तविक समय = 1824.017..1824.019 पंक्तियाँ = 1 छोर = 1)
  Recheck Cond: ((निचला (अस्वीकार्य ((लेबल) :: टेक्स्ट))) ~~ '% someord%' :: टेक्स्ट) और (निचला (अस्वीकार्य (लेबल) :: टेक्स्ट)) ~~ '% someord और कुछ और % ':: पाठ))
  -> तालिका 1 पर बिटमैप इंडेक्स स्कैन_ लैबल_हेन_जिन_ट्रैगम (लागत = 0.00..16216.01 पंक्तियों = 1 चौड़ाई = 0) (वास्तविक समय = 1823.900..1823.900 पंक्तियों = 1 छोरों = 1)
        सूचकांक कंडोम: ((कम (अस्वीकार्य ((लेबल) :: पाठ))) ~~ '% someord%' :: पाठ) और (कम (अस्वीकार्य (लेबल) :: पाठ)) ~~ '% someord और कुछ और % ':: पाठ))
कुल रनटाइम: 1824.064 एमएस

मेरी अंतिम समस्या यह है कि खोज स्ट्रिंग एक वेब इंटरफेस से आता है जो काफी लंबे तार भेज सकता है और इस प्रकार काफी धीमा हो सकता है और एक डॉस वेक्टर का गठन भी कर सकता है।

तो मेरे सवाल हैं:

  • क्वेरी को कैसे गति दें?
  • क्या इसे उप-क्षेत्रों में तोड़ने का एक तरीका है ताकि यह तेज हो?
  • हो सकता है कि Postgres का बाद का संस्करण बेहतर हो? (मैं 9.4 की कोशिश की और यह तेजी से नहीं लगता है: अभी भी एक ही प्रभाव। शायद बाद के संस्करण?)
  • शायद एक अलग अनुक्रमण रणनीति की आवश्यकता है?

1
यह उल्लेख किया जाना चाहिए कि unaccent()एक अतिरिक्त मॉड्यूल द्वारा भी प्रदान किया गया है और पोस्टग्रेज डिफ़ॉल्ट रूप से फ़ंक्शन पर अनुक्रमित का समर्थन नहीं करता है क्योंकि यह नहीं है IMMUTABLE। आपने कुछ बदला होगा और आपने अपने प्रश्न में जो किया है उसका उल्लेख करना चाहिए। मेरी स्थायी सलाह: stackoverflow.com/a/11007216/939860 । इसके अलावा, ट्रिग्राम इंडेक्स बॉक्स के बाहर केस-असंवेदनशील मिलान का समर्थन करता है। आप इसे सरल बना सकते हैं: WHERE f_unaccent(label) ILIKE f_unaccent('%someword%')- एक मिलान सूचकांक के साथ। विवरण: stackoverflow.com/a/28636000/939860
एरविन ब्रान्डसेट्टर

मैंने बस unaccentअपरिवर्तनीय घोषित किया । मैंने इसे सवाल से जोड़ा।
P.Péter

ध्यान रखें कि जब आप unaccentमॉड्यूल को अपडेट करते हैं तो हैक अधिलेखित हो जाता है। इसके कारणों में से एक है कि मैं इसके बजाय एक फ़ंक्शन आवरण का सुझाव देता हूं।
इरविन ब्रान्डस्टेट्टर

जवाबों:


34

PostgreSQL 9.6 में pg_trgm, 1.2 का एक नया संस्करण होगा, जो इस बारे में बहुत बेहतर होगा। थोड़े से प्रयास से, आप PostgreSQL 9.4 के तहत काम करने के लिए यह नया संस्करण भी प्राप्त कर सकते हैं (आपको पैच लागू करना होगा, और विस्तार मॉड्यूल को स्वयं संकलित करना होगा और इसे स्थापित करना होगा)।

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

ऐसा करने की मशीनरी 9.1 में मौजूद नहीं है। 9.4 में उस मशीनरी को जोड़ा गया था, लेकिन उस समय इसका उपयोग करने के लिए pg_trgm को अनुकूलित नहीं किया गया था।

आपके पास अभी भी एक संभावित डॉस मुद्दा होगा, क्योंकि दुर्भावनापूर्ण व्यक्ति एक क्वेरी को तैयार कर सकता है जिसमें केवल सामान्य ट्रिगर्स होते हैं। जैसे '% और%', या यहां तक ​​कि '% a%'


यदि आप pg_trgm 1.2 में अपग्रेड नहीं कर सकते हैं, तो प्लानर को ट्रिक करने का एक और तरीका होगा:

WHERE (lower(unaccent(label)) like lower(unaccent('%someword%'))) 
AND   (lower(unaccent(label||'')) like 
      lower(unaccent('%someword and some more%')));

खाली स्ट्रिंग को लेबल करने के लिए, आप प्लानर को यह सोचकर चकरा देते हैं कि यह उस खंड के उस हिस्से पर सूचकांक का उपयोग नहीं कर सकता है। तो यह सिर्फ% कुछ% पर सूचकांक का उपयोग करता है, और सिर्फ उन पंक्तियों के लिए एक फ़िल्टर लागू करता है।


साथ ही, यदि आप हमेशा पूरे शब्दों को खोज रहे हैं, तो आप स्ट्रिंग को शब्दों के एक सरणी में टोकन करने के लिए एक फ़ंक्शन का उपयोग कर सकते हैं, और उस सरणी पर लौटने वाले फ़ंक्शन पर एक नियमित अंतर्निहित GIN इंडेक्स (pg_trgm) का उपयोग कर सकते हैं।


13
वर्थ ने उल्लेख किया कि आप पैच लिखने वाले व्यक्ति थे। और प्रारंभिक प्रदर्शन परीक्षण प्रभावशाली हैं। यह वास्तव में अधिक उत्थान के लायक है (वर्तमान संस्करण के साथ स्पष्टीकरण और समाधान के लिए भी)।
एरविन ब्रान्डसेट्टर

मुझे लगता है कि 9.1 में नहीं था पैच को लागू करने के लिए आपके द्वारा इस्तेमाल की जाने वाली मशीनरी के संदर्भ में कम से कम दिलचस्पी होगी। लेकिन, मैं w / एरविन खराब गधे के उत्तर से सहमत हूं।
इवान कैरोल

3

मुझे क्वेरी प्लानर को घोटाला करने का एक तरीका मिल गया है, यह एक बहुत ही सरल हैक है:

SELECT *
FROM (
   select id, title, label
   from   table1
   where  lower(unaccent(label)) like lower(unaccent('%someword%'))
   ) t1
WHERE lower(lower(unaccent(label))) like lower(unaccent('%someword and more%'))

EXPLAIN उत्पादन:

तालिका 1 पर बिटमैप हीप स्कैन (लागत = 6749.11..7332.71 पंक्तियाँ = 1 चौड़ाई = 212) (वास्तविक समय = 256.607..256.609 पंक्तियाँ = 1 छोर = 1)
  Recheck Cond: (निम्न (अस्वीकार्य ((लेबल)) :: टेक्स्ट))) ~~ '% someord%' :: टेक्स्ट)
  फ़िल्टर: (कम (कम (अस्वीकार) (लेबल) :: टेक्स्ट))) ~~ '% someord और कुछ और% "::)
  -> तालिका 1 पर बिटमैप इंडेक्स स्कैन_ लैबल_हेन_जिन_ट्रैगम (लागत = 0.00..6749.11 पंक्तियाँ = 147 चौड़ाई = 0) (वास्तविक समय = 256.499..256.499 पंक्तियाँ = 1 छोर = 1)
        सूचकांक कंडोम: (कम (अस्वीकार्य ((लेबल) :: टेक्स्ट))) ~~ '% someord%' :: टेक्स्ट)
कुल रनटाइम: 256.653 एमएस

इसलिए, जैसा कि कोई सूचकांक नहीं है lower(lower(unaccent(label))), यह एक अनुक्रमिक स्कैन बनाएगा, इसलिए यह एक साधारण फिल्टर में बदल जाता है। क्या अधिक है, एक सरल और भी ऐसा ही करेगा:

SELECT id, title, label
FROM table1
WHERE lower(unaccent(label)) like lower(unaccent('%someword%'))
AND   lower(lower(unaccent(label))) like lower(unaccent('%someword and more%'))

बेशक, यह एक हेयुरिस्टिक है जो अच्छी तरह से काम नहीं कर सकता है, अगर इंडेक्स स्कैन में उपयोग किए गए कट-आउट भाग बहुत आम है। लेकिन हमारे डेटाबेस में, वास्तव में इतना दोहराव नहीं है, अगर मैं लगभग 10-15 अक्षरों का उपयोग करता हूं।

दो छोटे प्रश्न शेष हैं:

  • क्यों नहीं पता लगा सकता है कि इस तरह से कुछ फायदेमंद होगा?
  • 0..256.499 समय सीमा (आउटपुट का विश्लेषण देखें) में पोस्टग्रैज क्या करता है?

1
0 और 256.499 के बीच की समय सीमा में यह बिटमैप का निर्माण कर रहा है। 256.499 पर यह अपना पहला आउटपुट तैयार करता है, जो बिटमैप है। जो इसका आखिरी आउटपुट भी है, क्योंकि यह केवल एक ही आउटपुट पैदा करता है - एक एकल बिटमैप।
jjanes
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.