PostgreSQL-ს შეუძლია ტექსტში ძიება Elasticsearch-ის ან სხვა ცალკე სისტემის გარეშე. თითოეულ დოკუმენტს გადააქცევ tsvector-ად (ნორმალიზებული სიტყვების დახარისხებული სია პოზიციებით), საძიებო ველში შეყვანილ ტექსტს - tsquery-ად, ორივეს ადარებ @@ ოპერატორით, tsvector-ს ინდექსს უკეთებ GIN-ით, რომ შედარებამ ყველა სტრიქონი არ წაიკითხოს, ნაპოვნს კი ts_rank-ით ალაგებ. ბლოგისთვის, დახმარების ცენტრისთვის, პროდუქტის კატალოგისთვის ან შიდა ვიკისთვის რამდენიმე მილიონ ჩანაწერამდე ეს საკმარისია, თანაც ის იმავე ტრანზაქციებში, იმავე backup-ებში და იმავე კავშირის სტრიქონის უკან ცხოვრობს, რომელშიც დანარჩენი მონაცემები. ეს გზამკვლევი მას ნაბიჯ-ნაბიჯ აგებს რეალურ ცხრილზე და გეუბნება, სად წყვეტს იგი სწორი ინსტრუმენტობას.
ქვემოთ ყველაფერი მუშაობს PostgreSQL-ის ნებისმიერ ამჟამად მხარდაჭერილ ვერსიაზე, თუ სხვა რამ არ არის მითითებული. რამდენიმე ფუნქცია დანარჩენებზე ახალია და ისინი იქვე არის აღნიშნული, სადაც ჩნდება.
რას აკეთებს სრულტექსტური ძიება და რას ვერ აკეთებს LIKE#
პირველი, რასაც ხალხი ცდის, არის WHERE body ILIKE '%server%'. ასი სტრიქონისთვის ის მუშაობს, მერე კი სამი გზით ფუჭდება:
- ჩვეულებრივ ინდექსს ვერ იყენებს. დასაწყისში ჩანაცვლების სიმბოლოიანი შაბლონი აიძულებს ყველა სტრიქონის გავლას და ყველა ტექსტის ყველა ბაიტის წაკითხვას. დრო ცხრილის ზომასთან ერთად იზრდება.
- ემთხვევა სიმბოლოები და არა სიტყვები. მოთხოვნა
runპოულობსprune-სა დაbrunch-ს, მაგრამ ვერ პოულობსrunning-სა დაran-ს. - რელევანტურობა არ ესმის. სტრიქონი, რომელიც სიტყვას ერთხელ ახსენებს სქოლიოში, იმავე წონისაა, რაც სტრიქონი, რომელიც მთლიანად მასზეა.
სრულტექსტური ძიება სამივე პრობლემას წყვეტს, რადგან მუშაობს ლექსემებზე და არა სიმბოლოებზე. დოკუმენტი იშლება ტოკენებად, თითოეული ტოკენი გადის ლექსიკონში, რომელიც გამორიცხავს stop-სიტყვებს (the, and, of) და დანარჩენს ძირამდე ამცირებს (running, runs და ran ყველა ერთნაირად run არ ხდება - stemmer წესებით მუშაობს და უწესო ფორმები, როგორიცაა ran, ჩვეულებრივ გამონაკლისად რჩება - მაგრამ running და runs ხდება), და ინახება შედეგი. მოთხოვნა იმავე გზას გადის, ასე რომ ორივე მხარე თანხმდება, რა არის სიტყვა.
გარდაქმნა პირდაპირ შეგიძლია ნახო:
SELECT to_tsvector('english', 'The quick brown foxes jumped over the lazy dogs'); 'brown':3 'dog':9 'fox':4 'jump':5 'lazi':8 'quick':2სიტყვები დახარისხებულია, ძირზე დაყვანილია (foxes გახდა fox, lazy გახდა lazi, რაც ინგლისური სიტყვა არ არის და არც უნდა იყოს) და თითოეულს თან ახლავს პოზიცია, რომელიც ეკავა. the და over გაქრა, მაგრამ მათთვის პოზიციები მაინც დაითვალა - სწორედ ამით იგებს მოგვიანებით ფრაზის ძიება, რომ ორი სიტყვა ერთმანეთის გვერდით იდგა.
ორი ტიპი: tsvector და tsquery#
tsvector დოკუმენტის მხარეა. tsquery კითხვის მხარეა. შედარების ოპერატორია @@ და ის boolean-ს აბრუნებს:
SELECT to_tsvector('english', 'PostgreSQL stores documents as lexemes') @@ to_tsquery('english', 'document & lexeme');შედეგია t. ორივე მხარე ერთი და იმავე კონფიგურაციით დამუშავდა, ამიტომ documents შეხვდა document-ს. თუ კონფიგურაციებს აურევ - tsvector აგებულია english-ით, მოთხოვნა კი simple-ით - უხმო აცდენებს მიიღებ. ეს ყველაზე ხშირი მიზეზია იმისა, რომ ძიება „ჩემთან მუშაობს" და პროდაქშენში არა. ენის კონფიგურაციების ქვემოთ მოცემული ნაწილი გვიჩვენებს, როგორ აიცილო ეს.
PostgreSQL-ს tsquery-ის ასაგებად ოთხი გზა აქვს და განსხვავება ძირითადად ისაა, რა ხდება, როცა მომხმარებელი საძიებო ველში უცნაურ რამეს ჩაწერს:
| ფუნქცია | შეყვანის სტილი | როდის გამოიყენო |
|---|---|---|
to_tsquery | ოპერატორები & | ! <-> და :* | მოთხოვნები, რომლებსაც თვითონ წერ |
plainto_tsquery | ჩვეულებრივი სიტყვები, ყველა AND-ით | მარტივი ველები სინტაქსის გარეშე |
phraseto_tsquery | ჩვეულებრივი სიტყვები, ზუსტად ერთმანეთის მიყოლებით | ზუსტი ფრაზის ველები |
websearch_to_tsquery | ბრჭყალები, or, -word | საჯარო საძიებო ველი |
to_tsquery მკაცრია. მომხმარებლის ნედლი სტრიქონი რომ გადასცე, შემთხვევითმა აპოსტროფმა ან დაუხურავმა ფრჩხილმა სინტაქსის შეცდომა გამოიწვია. ის სწორი ინსტრუმენტია, როცა გამოსახულებას შენი კოდი აგებს, და არასწორი - მომხმარებლის შეყვანისთვის.
websearch_to_tsquery (PostgreSQL 11 და უფრო ახალი) ის არის, რაც საძიებო ველის უკან უნდა დადო. მომხმარებლის შეყვანაზე ის არასოდეს აგდებს სინტაქსის შეცდომას და ესმის ის წესები, რომლებიც ხალხმა ვებ-საძიებო სისტემებიდან იცის:
SELECT websearch_to_tsquery('english', '"dedicated server" -windows or linux'); 'dedic' <-> 'server' & !'window' | 'linux'ბრჭყალებში ჩასმული ფრაზა ფრაზა ხდება (ოპერატორი <-> ნიშნავს „პირდაპირ მოსდევს"), -word უარყოფა ხდება, or კი - |. ბრჭყალების გარეშე სიტყვები AND-ით ერთდება.
პრეფიქსით ძიება to_tsquery-ის შესაძლებლობაა: to_tsquery('english', 'postgr:*') პოულობს ყველა ლექსემას, რომელიც postgr-ით იწყება. ეს წერისას მინიშნების საფუძველია, ერთი დათქმით - პრეფიქსი მთლიანი სიტყვის მსგავსად stemming-ს არ გადის, ამიტომ შეამოწმე შენთვის მნიშვნელოვანი შემთხვევები.
სამუშაო მაგალითი: სტატიების პატარა ცხრილი#
შექმენი ცხრილი და ჩასვი რამდენიმე სტრიქონი. ის ჩაანაცვლებს იმას, რაშიც ძიება გინდა:
CREATE TABLE articles ( id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY, title text NOT NULL, body text NOT NULL, created_at timestamptz NOT NULL DEFAULT now());INSERT INTO articles (title, body) VALUES ('Backups that restore', 'A backup you have never restored is a hope, not a backup.'), ('Connection pooling', 'Each connection costs memory. A pooler keeps the count low.'), ('Choosing an index', 'An index speeds up reads and slows down writes on the table.');ძიების მარტივი ვერსია tsvector-ს ადგილზე ითვლის:
SELECT id, titleFROM articlesWHERE to_tsvector('english', title || ' ' || body) @@ websearch_to_tsquery('english', 'restore backup');ის სწორია და ნელა მუშაობს, რადგან PostgreSQL-მა ყოველ მოთხოვნაზე ყველა სტრიქონის ტექსტი თავიდან უნდა დაშალოს და დაამუშაოს. ამის შესაწყვეტად ორი სტანდარტული გზა არსებობს და ორივე GIN ინდექსით მთავრდება.
ვარიანტი 1: შენახული გენერირებული სვეტი
PostgreSQL 12-იდან სვეტის გამოთვლა შეიძლება სხვა სვეტებიდან და დისკზე შენახვა, ავტომატური სინქრონიზაციით:
ALTER TABLE articlesADD COLUMN search tsvectorGENERATED ALWAYS AS ( to_tsvector('english', coalesce(title, '') || ' ' || coalesce(body, ''))) STORED;coalesce მნიშვნელოვანია: NULL-ის ნებისმიერ რამესთან შეერთება NULL-ს იძლევა და ცარიელი სათაურის მქონე სტრიქონი ძიებიდან მთლიანად გაქრებოდა. გამოსახულება უცვლელი (immutable) უნდა იყოს, ამიტომ კონფიგურაციის სახელი პირდაპირ წერია. ეს ვარიანტი ნაგულისხმევად აირჩიე. ფასი ისაა, რომ tsvector დისკზე ორიგინალური ტექსტის გვერდით ადგილს იკავებს, სვეტის დამატება კი ცხრილს ერთხელ თავიდან წერს.
PostgreSQL 11-სა და უფრო ძველზე გენერირებული სვეტები არ არის; გამოიყენე tsvector სვეტი, რომელსაც trigger ანახლებს. PostgreSQL ამისთვის დამხმარე tsvector_update_trigger-ს აწვდის, აღწერილს სახელმძღვანელოს ტექსტური ძიების თავში.
ვარიანტი 2: გამოსახულების ინდექსი, დამატებითი სვეტის გარეშე
თუ ვექტორის შენახვა არ გინდა, თვითონ გამოსახულებას გაუკეთე ინდექსი:
CREATE INDEX articles_fts_exprON articles USING gin (to_tsvector('english', title || ' ' || body));მოთხოვნამ მაშინ იდენტური გამოსახულება უნდა გაიმეოროს, რომ planner-მა ამოიცნოს:
WHERE to_tsvector('english', title || ' ' || body) @@ queryეს შენახულ სვეტს გიზოგავს, მაგრამ ადვილად იშლება სინქრონი: გამოსახულებას ერთ ადგილას შეცვლი და მეორეში არა - და ინდექსი უხმოდ აღარ გამოიყენება. ისიც გაითვალისწინე, რომ ინდექსი იღებს მხოლოდ to_tsvector-ის ორარგუმენტიან ფორმას, კონფიგურაციის პირდაპირი მითითებით. ერთარგუმენტიანი ფორმა დამოკიდებულია სესიის პარამეტრზე default_text_search_config, ამიტომ immutable არ არის და PostgreSQL მას ინდექსირებაზე უარს ეუბნება.
GIN ინდექსი#
GIN ნიშნავს Generalized Inverted Index-ს. tsvector-ს თითო სტრიქონზე ბევრი ლექსემა აქვს, ამიტომ სასარგებლო სტრუქტურა ცხრილის შებრუნებულია: თითოეული ლექსემისთვის - სტრიქონების სია, რომლებიც მას შეიცავს. backup-ის ძიება მაშინ ერთი მიმართვაა და არა სკანირება.
CREATE INDEX articles_search_idx ON articles USING gin (search);რომ ინდექსი გამოიყენება, EXPLAIN-ით შეამოწმე:
EXPLAINSELECT id, title FROM articlesWHERE search @@ websearch_to_tsquery('english', 'backup');სამსტრიქონიან ცხრილზე planner თანმიმდევრულ სკანირებას აირჩევს, რასაც არ უნდა აკეთებდე, რადგან სამი სტრიქონის წაკითხვა ინდექსთან მიმართვაზე იაფია. ეს სწორი ქცევაა და არა ინდექსის არქონა. სათამაშო ცხრილზე ინდექსიანი გეგმის სანახავად სესიაში გაუშვი SET enable_seqscan = off; და explain გაიმეორე; მოძებნე Bitmap Index Scan on articles_search_idx. ეს პარამეტრი ჩართული არ დატოვო. ათიათასობით სტრიქონიან ცხრილზე ინდექსი ყოველგვარი მინიშნების გარეშე ირჩევა.
GIN ტექსტური ძიებისთვის სწორი ნაგულისხმევი არჩევანია. ალტერნატივა, GiST, უფრო პატარა ინდექსს აგებს, რომელიც უფრო სწრაფად ახლდება, მაგრამ ძიებაში უფრო ნელია და დანაკარგიანია (შეიძლება დააბრუნოს სტრიქონები, რომლებიც მერე თავიდან მოწმდება). თვით PostgreSQL-ის დოკუმენტაციის რჩევაა: GIN სტატიკური ან უმეტესად წასაკითხი მონაცემებისთვის, ეს კი უმეტეს შემთხვევას ფარავს. ზოგადი სურათისთვის იხილე PostgreSQL-ის ინდექსები: ტიპები, თანმიმდევრობა და ფასი.
ხარჯები, რომლებიც უნდა იცოდე:
- ზომა. ტექსტზე GIN ინდექსი შეიძლება თვით ტექსტის ზომის დიდ ნაწილს შეადგენდეს, ზოგჯერ მეტსაც. შეამოწმე ასე:
SELECT pg_size_pretty(pg_relation_size('articles_search_idx'));. - ჩაწერის სიჩქარე. ერთი დოკუმენტის ჩასმა ინდექსის ბევრ ჩანაწერს ეხება, თითო ჩანაწერს ყოველ განსხვავებულ ლექსემაზე. ამის შესარბილებლად GIN ახალ ჩანაწერებს მოლოდინის სიაში აგროვებს და პარტიებად აერთიანებს (შენახვის პარამეტრი
fastupdate, ნაგულისხმევად ჩართული). დიდი ცხრილის ჩატვირთვა ბევრად სწრაფია, თუ ჯერ ტვირთავ, ინდექსს კი მერე ქმნი. - ბლოუტი და მომსახურება. მოლოდინის სიას autovacuum-ი ასუფთავებს, ამიტომ ცხრილს, რომელშიც ბევრს წერენ, სჭირდება, რომ autovacuum ასწრებდეს. როგორ შეამოწმო, განმარტავს Postgres VACUUM და bloat.
შედეგების დახარისხება ts_rank-ით#
ლოგიკური დამთხვევა გეუბნება, რომელი სტრიქონები ვარგა, მაგრამ არა, რომელი აჩვენო პირველად. ts_rank ყოველ დამთხვევაზე აბრუნებს float-ს იმის მიხედვით, რამდენად ხშირად და რამდენად მჭიდროდ გვხვდება მოთხოვნის სიტყვები:
SELECT id, title, ts_rank(search, q) AS rankFROM articles, websearch_to_tsquery('english', 'backup restore') AS qWHERE search @@ qORDER BY rank DESCLIMIT 10;თუ მოთხოვნას FROM-ში q-დ ჩადებ, ის ერთხელ ითვლება და არა ყოველ სტრიქონზე. არსებობს მონათესავე ფუნქცია ts_rank_cd, რომელიც დამატებით აჯილდოებს ახლოს მდგომ სიტყვებს („cover density"). ის უფრო ძვირია და ღირს ცდად, როცა მომხმარებლებისთვის ფრაზების დამთხვევა მნიშვნელოვანია.
ორი შეზღუდვა წინასწარ უნდა იცოდე:
- დახარისხება ინდექსს ვერ იყენებს. GIN ინდექსი პოულობს შესაფერის სტრიქონებს; რანგი კი თითოეულისთვის უნდა გამოითვალოს და მერე დალაგდეს. თუ გავრცელებული სიტყვა 400 000 სტრიქონს ემთხვევა, 400 000 სტრიქონს არჩევ. ჩვეულებრივი წამალია მოთხოვნის შევიწროება (მეტი სავალდებულო სიტყვა, ფილტრი კატეგორიით ან თარიღით) ან მხოლოდ კანდიდატების ნაკრების დახარისხება.
- დახარისხება დოკუმენტების სიხშირეს არ ითვალისწინებს. PostgreSQL-ის ჩაშენებული დახარისხება არ იცის, რომ ერთი სიტყვა მთელ კორპუსში იშვიათია, მეორე კი გავრცელებული, როგორც ამას BM25 აკეთებს სპეციალიზებულ საძიებო სისტემებში. უმეტეს აპლიკაციას ეს აკმაყოფილებს და სწორედ ესაა სხვაზე გადასვლის ყველაზე ხშირი მიზეზი.
წონები: სათაურმა ტექსტზე მეტი უნდა იწონოს
დოკუმენტის ნაწილებს შეგიძლია ოთხიდან ერთი წონა მიანიჭო, A-დან D-მდე, setweight-ით. ნაგულისხმევად წონები დამთხვევას ამრავლებს 1.0-ზე A-სთვის, 0.4-ზე B-სთვის, 0.2-ზე C-სთვის და 0.1-ზე D-სთვის. ხელახლა შექმენი გენერირებული სვეტი ისე, რომ სათაურებს A ჰქონდეს, ტექსტებს კი B:
ALTER TABLE articles DROP COLUMN search;ALTER TABLE articlesADD COLUMN search tsvectorGENERATED ALWAYS AS ( setweight(to_tsvector('english', coalesce(title, '')), 'A') || setweight(to_tsvector('english', coalesce(body, '')), 'B')) STORED;CREATE INDEX articles_search_idx ON articles USING gin (search);სვეტის წაშლა ძველ ინდექსსაც შლის, ამიტომ ის ხელახლა იქმნება. გვერდი, რომლის სათაურშიც საძიებო სიტყვაა, ახლა ჯობნის იმას, რომელიც მას გზადაგზა ახსენებს. საკუთარი წონები ts_rank-ის პირველ არგუმენტად შეგიძლია გადასცე, მასივად {D, C, B, A} თანმიმდევრობით:
SELECT id, ts_rank('{0.1, 0.2, 0.4, 1.0}', search, q) AS rankFROM articles, websearch_to_tsquery('english', 'backup') AS qWHERE search @@ q;ts_rank ბოლო არგუმენტად ნორმალიზაციის პარამეტრსაც იღებს - ბიტურ ნიღაბს. მის გარეშე გრძელი დოკუმენტები უფრო მაღალ ქულას იღებს უბრალოდ იმიტომ, რომ მეტი სიტყვა აქვს. პარამეტრი 1 რანგს ყოფს 1 + log(დოკუმენტის სიგრძე)-ზე, 32 კი რანგს 0-დან 1-მდე დიაპაზონში აგდებს. მათი შეერთება შეიძლება შეკრებით: ts_rank(search, q, 1 | 32).
დამთხვევის ჩვენება ts_headline-ით#
ძიების შედეგები გაცილებით უკეთ გამოიყურება, როცა შესაფერის ამონარიდს გამოყოფილი სიტყვებით აჩვენებ. ამას ts_headline აკეთებს:
SELECT id, ts_headline('english', body, q, 'StartSel=<mark>, StopSel=</mark>, MaxWords=25, MinWords=10') AS snippetFROM articles, websearch_to_tsquery('english', 'backup') AS qWHERE search @@ qORDER BY ts_rank(search, q) DESCLIMIT 10;ორი გაფრთხილება. ts_headline მუშაობს ორიგინალურ ტექსტზე და არა tsvector-ზე, ამიტომ დოკუმენტს თავიდან შლის; გამოიძახე მხოლოდ იმ სტრიქონებზე, რომლებსაც ნამდვილად აჩვენებ (LIMIT-ის შემდეგ ათზე), არასოდეს მთელ შედეგზე დახარისხებამდე. და ის აბრუნებს ტექსტს შენ მიერ არჩეული მარკერებით, ამიტომ ვებ-გვერდის აგებისას დოკუმენტის ტექსტი გამოყოფამდე თვითონ დაიცვი escape-ით: HTML escaping ფუნქციას არ აქვს.
ენის კონფიგურაციები#
ტექსტური ძიების კონფიგურაცია არის რეცეპტი, რომელიც განსაზღვრავს, როგორ იშლება ტექსტი ტოკენებად და რომელი ლექსიკონები ამცირებს ტოკენებს. english ერთ-ერთია. ნახე, რა აქვს შენს სერვერს:
$ psql -c '\dF'ან SQL-იდან:
SELECT cfgname FROM pg_ts_config ORDER BY cfgname;SHOW default_text_search_config;სტანდარტული ინსტალაცია შეიცავს კონფიგურაციებს ევროპის ბევრი ენისთვის (french, german, spanish, russian, italian და სხვა) და კონფიგურაციას simple. ორი სახის ხარვეზი უნდა იცოდე:
- ენები ჩაშენებული კონფიგურაციის გარეშე. ჩეხური და ქართული, ორი მაგალითისთვის, სტანდარტულ სიაში არ არის. მის გარეშე გამოიყენე
simple, რომელიც სიტყვებს პატარა ასოებად გარდაქმნის და მთლიანად ტოვებს, stemming-სა და stop-სიტყვების გარეშე. ეს მხოლოდ სიტყვის ზუსტ დამთხვევას იძლევა, ამიტომserverვერ იპოვისservers-ს. უფრო სრული მხარდაჭერა ნიშნავს ლექსიკონის დამატებას (მაგალითად, Ispell ან Hunspell ჩეხურისთვის) და მასზე საკუთარი კონფიგურაციის აგებას, რისთვისაც ფაილები სჭირდება მონაცემთა ბაზის სერვერის დისკზე და მართულ ჰოსტინგზე ეს ჩვეულებრივ შეუძლებელია. - დიაკრიტიკა.
caféდაcafeნაგულისხმევად სხვადასხვა ლექსემაა. გაფართოებაunaccentამას ასწორებს, თუ მისი შექმნა შეგიძლია.
ენის არჩევა რეალური დიზაინის გადაწყვეტილებაა და ორივე მხარეს უნდა ემთხვეოდეს. თანმიმდევრულობის ყველაზე იაფი გზაა კონფიგურაცია ყოველ გამოძახებაში პირდაპირ დაწერო, არასოდეს დაეყრდნო სესიის ნაგულისხმევს და ის ერთ ადგილას გქონდეს, მაგალითად, გენერირებულ სვეტსა და მოთხოვნის ერთ დამხმარე ფუნქციაში.
დოკუმენტები რამდენიმე ენაზე
თუ სტრიქონები სხვადასხვა ენაზეა, კონფიგურაცია ყოველ სტრიქონზე შეინახე. ტიპია regconfig:
ALTER TABLE articles ADD COLUMN lang regconfig NOT NULL DEFAULT 'english';-- a generated column that uses the row's own languageALTER TABLE articlesADD COLUMN search tsvector GENERATED ALWAYS AS ( to_tsvector(lang, coalesce(title, '') || ' ' || coalesce(body, ''))) STORED;ეს დასაშვებია, რადგან regconfig არგუმენტი უცვლელია. მოთხოვნისას საძიებო სიტყვებს იმ ენით ამუშავებ, რომელზეც მომხმარებელი ეძებს და ის ჩვეულებრივ ინტერფეისიდან ჩანს და არა შიგთავსიდან. მოთხოვნის არასწორ ენაზე დამუშავება იმავე უხმო აცდენებს იძლევა, რასაც კონფიგურაციების არევა.
დიაკრიტიკის უგულებელყოფა საკუთარი კონფიგურაციით
შექმენი გაფართოება, დააკოპირე ჩაშენებული კონფიგურაცია და unaccent stemmer-ის წინ ჩააყენე:
CREATE EXTENSION IF NOT EXISTS unaccent;CREATE TEXT SEARCH CONFIGURATION english_unaccent (COPY = english);ALTER TEXT SEARCH CONFIGURATION english_unaccent ALTER MAPPING FOR hword, hword_part, word WITH unaccent, english_stem;ახლა to_tsvector('english_unaccent', 'Café') და მოთხოვნა cafe ერთსა და იმავე ლექსემას იძლევა. თუ CREATE EXTENSION უფლებების შეცდომით უარყოფილია, როლს, რომლითაც ხარ დაკავშირებული, მისი დაყენების უფლება არ აქვს; მართულ ბაზაზე, სანამ რომელიმე მათგანს დაეყრდნობი, შეამოწმე, რომელ გაფართოებებს უშვებს შენი გეგმა. ვის რა შეუძლია, განმარტავს როლები და უფლებები PostgreSQL-ში.
რატომ არაფერს პოულობს ძიება#
ჩაშენებული გამართვის ფუნქციები პასუხობს „რატომ არ ემთხვევა" ტიპის კითხვების უმეტესობას:
SELECT * FROM ts_debug('english', 'The quick brown foxes');SELECT to_tsvector('english', 'Backups') @@ to_tsquery('english', 'backup');SELECT ts_lexize('english_stem', 'foxes');SELECT * FROM ts_stat('SELECT search FROM articles') ORDER BY ndoc DESC LIMIT 20;თითოეული მათგანი სხვადასხვა კითხვას პასუხობს:
ts_debugაჩვენებს თითოეულ ტოკენს, პარსერის რომელმა წესმა შექმნა, რომელმა ლექსიკონმა დაამუშავა და რა ლექსემა გამოვიდა.ts_lexizeერთ სიტყვას ერთ ლექსიკონში ატარებს. ცარიელი შედეგი ნიშნავს, რომ სიტყვა ამ ლექსიკონში stop-სიტყვაა.ts_statჩამოთვლის სვეტში არსებულ ლექსემებს და იმას, რამდენ დოკუმენტსა და გამოყენებაში გვხვდება თითოეული. გამოგადგება სიტყვების მოსაძებნად, რომლებიც იმდენად გავრცელებულია, რომ stop-სიტყვებად უნდა ჩაითვალოს, და ისეთი უსარგებლო ტოკენების შესამჩნევად, როგორიცაა გრძელი hex სტრიქონები ან URL-ის ნაწყვეტები, რომლებიც ინდექსს ბერავს.
ცარიელი შედეგის ხშირი მიზეზები, დაახლოებით სიხშირის კლებადობით:
tsvectorერთი კონფიგურაციით აიგო, მოთხოვნა - სხვა კონფიგურაციით.- საძიებო სიტყვა ამ ენაში stop-სიტყვაა და მოთხოვნა არაფერზე დაიყვანება.
to_tsquery('english', 'the')ცარიელ მოთხოვნას იძლევა შეტყობინებით, ცარიელი მოთხოვნა კი სტრიქონებს არ პოულობს. - სიტყვის ნაწილის ძიება
:*-ის გარეშე.postgrარ არისpostgres. - სვეტი
tsvectorარისNULL, რადგან საწყისი სვეტები იყოNULL,coalesceკი არ გამოიყენეს. - სვეტი შეიცვალა, მაგრამ
tsvectorხელახლა არ აგებულა (ხელით წარმართული სვეტი თავისი trigger-ის გარეშე).
ძიება პატარა სერვერზე#
სრულტექსტური ძიება მეხსიერებას განსაკუთრებული გზით მოითხოვს: GIN-ში ძიება სწრაფია, როცა ინდექსი cache-შია, და შესამჩნევად ნელა მუშაობს, როცა დისკიდან იკითხება. პატარა ინსტანციაზე მნიშვნელოვანია რიცხვი, რომელიც ინდექსის ზომას ადარებს RAM-ს, რაც დანარჩენი სამუშაო ნაკრების შემდეგ რჩება. ბაზა 2 GB-იან გეგმაზე და 600 MB საძიებო ინდექსი კომფორტულია; 3 GB ინდექსი იმავე გეგმაზე დიდწილად დისკიდან იკითხება. ზომა შეამოწმე საძიებო სვეტის დამატებამდე და მის შემდეგ:
SELECT pg_size_pretty(pg_total_relation_size('articles')) AS table_and_indexes, pg_size_pretty(pg_relation_size('articles_search_idx')) AS gin_index;shared_buffers-ისა და work_mem-ის დაყენება განხილულია PostgreSQL-ის დაყენება პატარა სერვერებისთვის სტატიაში. ძიებისთვის ყველაზე სწრაფი მოგებაა: ინდექსი გააკეთე მხოლოდ იმ სვეტებზე, რომლებშიც ნამდვილად ეძებენ, წაშალე წონები და სვეტები, რომლებსაც არ იყენებ, და მოთხოვნები უფრო შერჩევითი გახადე, რომ ნაკლები სტრიქონი დახარისხდეს.
RE:NODE-ზე PostgreSQL ბაზის გეგმები $6-დან იწყება თვეში მონაცემთა ბაზების ჰოსტინგის გვერდზე, host, port, მომხმარებელი და პაროლი კი სერვერის შექმნისას თავად გენერირდება. უკავშირდები psql-იდან ან შენი აპლიკაციიდან ზუსტად ისე, როგორც PostgreSQL-თან დაკავშირება: საწყისი ნაბიჯები სტატიაშია, და ამ გზამკვლევის ყველა ბრძანებას ისე გაუშვებ, როგორც წერია. საძიებო სვეტი და მისი GIN ინდექსი ცხრილის ჩვეულებრივი მონაცემებია, ამიტომ ისინი შედის შენი გეგმის backup სლოტებსა და dump-ში. სანამ რამეს გაფართოებაზე, როგორიცაა unaccent, ააგებ, გაუშვი CREATE EXTENSION შენს ბაზაზე და გაარკვიე, დაშვებულია თუ არა; ამ გზამკვლევის იმედად ნუ იქნები. ინდექსი dump-ის აღდგენისას ავტომატურად თავიდან აიგება, რაც კიდევ ერთი მიზეზია, რომ გამოცდილი აღდგენის პროცედურა გქონდეს - იხილე მონაცემთა ბაზების backup და აღდგენა.
როცა PostgreSQL-ის ძიება საკმარისი არ არის#
ჩაშენებული ძიება ძლიერი ნაგულისხმევი არჩევანია და პატიოსანმა გზამკვლევმა უნდა თქვას, სად მთავრდება:
- ბეჭდური შეცდომები და ბუნდოვანი დამთხვევა. სრულტექსტური ძიება ლექსემებს ზუსტად ადარებს. „ხომ არ გულისხმობდით" ტიპის ქცევისთვის და შეცდომების ატანისთვის დაამატე გაფართოება
pg_trgm, რომელიც სამსიმბოლოიან ფრაგმენტებს ინდექსირებს (ასევე GIN-ით) და ეყრდნობა მსგავსებით დახარისხებას და სწრაფILIKE '%fragment%'-ს. ზოგი აპლიკაცია ორივეს იყენებს: ტრიგრამები მოკლე ველებისთვის, მაგალითად, სახელებისთვის, სრულტექსტურ ძიებას კი გრძელი ტექსტებისთვის. - რელევანტურობის დაზუსტება. დოკუმენტების სიხშირით ქულების გარეშე და ოთხი წონის მიღმა ველებისთვის დამატებითი უპირატესობის გარეშე, დახარისხებას ისე ვერ დაარეგულირებ, როგორც ამას სპეციალიზებული სისტემა გაძლევს.
- ფასეტური დათვლა და ძალიან დიდი კორპუსები. დამთხვევების დათვლა კატეგორიების მიხედვით მრავალ მილიონ სტრიქონზე ნელია, რადგან ყოველი დამთხვევა უნდა მოინახულო.
- ძიების მძიმე ტრაფიკი, რომელიც აპლიკაციას ეჯიბრება. ძიება და შენი ტრანზაქციული მოთხოვნები ერთსა და იმავე CPU-სა და მეხსიერებას იყოფენ. თუ ძიების დატვირთვა აპლიკაციის დანარჩენ ნაწილს ავნებს, დროა ისინი გაყო.
თუ კითხვაა, საერთოდ PostgreSQL არის თუ არა სწორი ბაზა დასაწყებად, ვარიანტებს ადარებს რომელი ბაზა ავირჩიო და MySQL თუ PostgreSQL. თუ მონაცემები უმეტესად ნახევრად სტრუქტურირებული დოკუმენტებია, PostgreSQL JSONB სრულტექსტურ ძიებასთან შეიძლება შეუთავსო: tsvector ააგე payload ->> 'title'-სა და მსგავსი გამოსახულებებისგან.
ახალი საძიებო ფუნქციის ჩამონათვალი#
- აირჩიე კონფიგურაცია (
englishან სხვა, რომელიც შენს მონაცემებს ეხამება) და ერთხელ ჩაიწერე. - დაამატე შენახული გენერირებული
tsvectorსვეტიsetweight-ით მნიშვნელოვან ველებზე დაcoalesce-ით ყველა ველზე, რომელიც NULL-ი შეიძლება იყოს. - შექმენი ამ სვეტზე
GINინდექსი. - მოითხოვე
websearch_to_tsquery-ით და კონფიგურაციის იმავე სახელით. - დაახარისხე
ts_rank-ით და შედეგიLIMIT-ით შეზღუდე;ts_headlineგამოიძახე მხოლოდ საბოლოო სტრიქონებზე. - გეგმა
EXPLAIN-ით შეამოწმე რეალისტურ მოცულობის მონაცემებზე და არა სამ სტრიქონზე. - შეამოწმე ინდექსის ზომა და ადევნე თვალი autovacuum-ს ცხრილებზე, რომლებშიც ბევრს წერენ.
FAQ#
საკმარისია PostgreSQL-ის სრულტექსტური ძიება პროდაქშენისთვის?
დიახ, კატალოგში, დოკუმენტაციაში, დახმარების ცენტრსა და ბლოგში ძიებისთვის რამდენიმე მილიონ სტრიქონამდე. ის არ არის შექმნილი ბეჭდური შეცდომების ატანისთვის ან რელევანტურობის მოწინავე დაზუსტებისთვის. ბევრი პროდაქშენ-სისტემა მასზე წლებით მუშაობს და ცალკე საძიებო სისტემას მხოლოდ მაშინ ამატებს, როცა ზემოთ აღწერილ ერთ-ერთ ზღვარს მიადგება.
რა განსხვავებაა tsvector-სა და tsquery-ს შორის?
tsvector დოკუმენტის შენახული, დამუშავებული ფორმაა: დახარისხებული ლექსემები პოზიციებით. tsquery ძიების დამუშავებული ფორმაა: ლექსემები, რომლებიც ოპერატორებითაა გაერთიანებული, როგორიცაა AND, OR, NOT და „მოსდევს". ოპერატორი @@ ამოწმებს, აკმაყოფილებს თუ არა ვექტორი მოთხოვნას.
რატომ არ იყენებს ჩემი სრულტექსტური მოთხოვნა GIN ინდექსს?
ჩვეულებრივი მიზეზებია: ცხრილი ძალიან პატარაა, რომ ინდექსი ღირდეს, მოთხოვნაში გამოსახულება განსხვავდება ინდექსისაგან, tsvector მოთხოვნისგან სხვა კონფიგურაციით არის აგებული, ან გამოყენებულია to_tsvector-ის ერთარგუმენტიანი ფორმა. გაუშვი EXPLAIN რეალისტური სტრიქონების რაოდენობის ცხრილზე და გამოსახულებები სიმბოლო-სიმბოლო შეადარე.
გენერირებული სვეტი გამოვიყენო თუ trigger?
PostgreSQL 12-სა და უფრო ახალზე გამოიყენე შენახული გენერირებული სვეტი: ის უფრო მოკლეა, კოდში დავიწყებული გზით ვერ ააცდენ და ცხრილის აღდგენისას თავისით ხელახლა აიგება. trigger სათადარიგო ვარიანტია ძველი ვერსიებისთვის ან იმ შემთხვევისთვის, როცა ვექტორი სხვა ცხრილებიდან უნდა აიგოს.
მუშაობს თუ არა სრულტექსტური ძიება ჩეხურზე, ქართულზე ან რუსულზე?
რუსულისთვის ჩაშენებული კონფიგურაცია არსებობს. ჩეხურისა და ქართულისთვის სტანდარტულ ინსტალაციაში არ არის, ამიტომ შეგიძლია გამოიყენო simple სიტყვების ზუსტი დამთხვევისთვის ან დააყენო ლექსიკონი და ააგო საკუთარი კონფიგურაცია იქ, სადაც ჰოსტინგი იძლევა ამის უფლებას. შეამოწმე შენი მონაცემებიდან რეალურ სიტყვებზე, რადგან stemming-ის ხარისხი ენის მიხედვით განსხვავდება.
როგორ გავხადო, რომ ძიებამ დიაკრიტიკა და რეგისტრი უგულებელყოს?
რეგისტრი უკვე უგულებელყოფილია, რადგან ლექსემები პატარა ასოებად გარდაიქმნება. დიაკრიტიკისთვის დააყენე გაფართოება unaccent და ააგე კონფიგურაცია, რომელიც ტოკენებს stemming-მდე მასში ატარებს, როგორც ზემოთ ჩანს, შემდეგ კი კონფიგურაციის ეს სახელი გამოიყენე სვეტშიც და მოთხოვნაშიც.


