برنامه نویسی

آیا PostgreSQL می تواند موتور جستجو باشد؟ بله ، شما ممکن است نیازی به Elasticsearch نداشته باشید

شرح تصویر

Leapcell: بهترین میزبانی وب بدون سرور

شاخص معکوس از فناوری موتور جستجو سرچشمه می گیرد و می تواند به عنوان سنگ بنای موتورهای جستجو در نظر گرفته شود. با تشکر از فناوری شاخص معکوس ، موتورهای جستجو می توانند به طور مؤثر عملیاتی مانند جستجوی پایگاه داده و حذف را انجام دهند. قبل از توضیح در فهرست معکوس ، ابتدا شاخص پیش رو مربوط را معرفی می کنیم و این دو را با هم مقایسه می کنیم.

شاخص رو به جلو

در یک موتور جستجو ، جدول جلو از شناسه سند به عنوان کلمه کلیدی استفاده می کند و جدول اطلاعات موقعیت هر کلمه را در سند ثبت می کند. هنگام جستجو ، سیستم اطلاعات کلمه را در هر سند موجود در جدول اسکن می کند تا زمانی که تمام اسناد حاوی کلمه کلیدی پرس و جو پیدا شود.

ساختار جدول رو به جلو را می توان با نمودار جعبه زیر نشان داد:

+---------------------+
|     Forward Index Table      |
+---------------------+
|  Document ID  |  Position Information |
+---------------------+
|   Doc1   |  word1@3 |
|          |  word2@7 |
+---------------------+
|   Doc2   |  word1@2 |
|          |  word3@5 |
+---------------------+
|   Doc3   |  word2@4 |
|          |  word4@6 |
+---------------------+
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

این روش سازمان در هنگام ساخت شاخص از ساختار نسبتاً ساده ای برخوردار است و ساخت آن راحت است و نگهداری آن نیز آسان است. از آنجا که این شاخص بر اساس اسناد ساخته شده است ، در صورت اضافه شدن یک سند جدید ، فقط یک بلوک شاخص جدید برای این سند ایجاد می شود و به انتهای پرونده فهرست اصلی پیوست می شود. در صورت حذف سندی ، اطلاعات شاخص مربوط به شماره سند را می توان مستقیماً پیدا و حذف کرد. با این حال ، در هنگام پرس و جو ، برای اطمینان از عدم وجود حذف ، باید تمام اسناد اسکن شوند ، که باعث افزایش زمان بازیابی می شود و منجر به بازده بازیابی کم می شود.

اگرچه اصل کار جدول رو به جلو بسیار ساده است ، اما راندمان بازیابی آن بسیار کم است و ارزش عملی کمی دارد مگر اینکه در موقعیت های خاص باشد.

شاخص معکوس

جدول معکوس از کلمات یا اصطلاحات به عنوان کلمات کلیدی برای نمایه سازی استفاده می کند ، و ورودی های ضبط مربوط به کلمات کلیدی موجود در جدول ، تمام اسنادی را که این کلمه یا اصطلاح در آن ظاهر می شود ، ضبط می کند.

ساختار جدول معکوس را می توان با نمودار جعبه زیر نشان داد:

+---------------------+
|     Inverted Index Table      |
+---------------------+
|  Keyword   |  Document List |
+---------------------+
|  word1    |  Doc1,Doc2 |
+---------------------+
|  word2    |  Doc1,Doc3 |
+---------------------+
|  word3    |  Doc2      |
+---------------------+
|  word4    |  Doc3      |
+---------------------+
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

از آنجا که تعداد اسناد مربوط به هر کلمه یا اصطلاح به صورت پویا تغییر می کند ، ایجاد و نگهداری از جدول معکوس نسبتاً پیچیده است. با این حال ، هنگام پرس و جو ، از آنجا که تمام اسناد مربوط به کلمه کلیدی پرس و جو می توانند به طور هم زمان بدست آورند ، راندمان بالاتر از جدول جلو است. در بازیابی کامل متن ، پاسخ سریع بازیابی یک عملکرد مهم است. اگرچه تأسیس شاخص نسبتاً کند است زیرا در پس زمینه انجام می شود ، اما بر کارآیی کل موتور جستجو تأثیر نمی گذارد.

شاخص جین در postgresql

نمای کلی

جین مخفف شاخص معکوس تعمیم یافته ، یعنی شاخص به اصطلاح معکوس است. مقادیر انواع داده هایی که فرآیند آن است اتمی نیست بلکه از عناصری تشکیل شده است که ما آن را انواع کامپوزیت می نامیم. به عنوان مثال ، در (hankبا 15:3 21:4) ، این بدان معنی است که هنک در موقعیت های 15: 3 و 21: 4 ظاهر می شود. موارد زیر به ما کمک می کند تا از طریق نمونه های خاص ، درک واضح تری از شاخص جین داشته باشیم.

ساختار شاخص جین

ساخت فیزیکی

ذخیره فیزیکی شاخص جین حاوی محتوای زیر است:

  1. ورود: یک عنصر در شاخص جین ، که می تواند به عنوان یک موقعیت کلمه در نظر گرفته شود و همچنین می تواند به عنوان یک کلید درک شود.
  2. درخت ورودی: یک درخت B ساخته شده در ورودی.
  3. لیست ارسال: لیستی از موقعیت های فیزیکی (Heap CTID ، شماره ردیف جدول جدول) که در آن یک ورودی ظاهر می شود.
  4. ارسال درخت: یک درخت B ساخته شده در لیست مرتبط با موقعیت های فیزیکی (Heap CTID ، شماره ردیف جدول جدول) که در آن یک ورودی ظاهر می شود. بنابراین کلید درخت ارسال CTID است و کلید درخت ورودی مقدار ستون فهرست بندی شده است.
  5. لیست در انتظار: یک لیست مرتبط با ذخیره موقت از توپل های شاخص ، که برای عملیات درج در حالت FastUpdate استفاده می شود.

از موارد فوق می توان دریافت که شاخص جین عمدتاً از درخت ورودی و درخت ارسال (یا لیست ارسال) تشکیل شده است ، جایی که درخت ورودی درخت ساختار اصلی شاخص جین است و درخت ارسال درخت کمکی است.

درخت ورود شبیه به درخت B+است و درخت ارسال شبیه به درخت B است.

علاوه بر این ، هم درخت ورودی و هم درخت ارسال به صورت منظم با توجه به کلید سازماندهی می شوند.

ساختار منطقی

از نظر منطقی ، شاخص جین می تواند به عنوان یک رابطه در نظر گرفته شود ، و این رابطه دارای دو ساختار است:

نمایه کردن فقط یک ستون از جدول پایه
| کلید | ارزش |
| —– | ————————— |
| key1 | لیست ارسال (یا ارسال درخت) |
| key2 | لیست ارسال (یا ارسال درخت) |
| … | … |

نمایه سازی چندین ستون از جدول پایه (کامپوزیت ، شاخص چند ستونی)
| column_id | کلید | ارزش |
| ———– | —– | ————————- |
| ستون 1 شماره | key1 | لیست ارسال (یا ارسال درخت) |
| ستون 2 شماره | key1 | لیست ارسال (یا ارسال درخت) |
| ستون 3 شماره | key1 | لیست ارسال (یا ارسال درخت) |
| … | … | … |

از این می توان مشاهده کرد که در زیر این ساختار ، برای همان کلید در ستون های مختلف جدول پایه ، به عنوان یک کلید متفاوت در شاخص جین نیز درمان می شود.

بازیابی متن کامل

قسمت اصلی برنامه جین تسریع در جستجوی متن کامل است. بنابراین ، در اینجا ما با استفاده از نمونه ای از جستجوی متن کامل ، شاخص جین را معرفی خواهیم کرد.

یک جدول ایجاد کنید ، و doc_tsv از نوع جستجوی متن است که می تواند به طور خودکار عناصر تکراری را مرتب و از بین ببرد:

pg_study=# create table ts(doc text, doc_tsv tsvector);
CREATE TABLE

pg_study=# insert into ts(doc) values
  ('Can a sheet slitter slit sheets?'), 
  ('How many sheets could a sheet slitter slit?'),
  ('I slit a sheet, a sheet I slit.'),
  ('Upon a slitted sheet I sit.'), 
  ('Whoever slit the sheets is a good sheet slitter.'), 
  ('I am a sheet slitter.'),
  ('I slit sheets.'),
  ('I am the sleekest sheet slitter that ever slit sheets.'),
  ('She slits the sheet she sits on.');
INSERT 0 9

pg_study=# update ts set doc_tsv = to_tsvector(doc);
UPDATE 9

pg_study=# create index on ts using gin(doc_tsv);
CREATE INDEX
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

ساختار این شاخص جین به شرح زیر است. مربع های سیاه شماره های مرتب هستند و سفیدی کلمات هستند. توجه داشته باشید که این یک لیست تک مرتبط است ، که با لیست مضاعف B-Tree متفاوت است:

+--------+     +--------+     +--------+
|  sheet |---->|  slit  |---->| slitter|
+--------+     +--------+     +--------+
   |             |             |
   v             v             v
+--------+   +--------+   +--------+
| (0,10) |   | (0,10) |   | (0,10) |
+--------+   +--------+   +--------+
   |             |             |
   v             v             v
+--------+   +--------+   +--------+
| (0,11) |   | (0,11) |   | (0,11) |
+--------+   +--------+   +--------+
   |             |             |
   v             v             v
   ...           ...           ...
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

بیایید به مثال دیگری نگاه کنیم:

pg_study=# select ctid,doc, doc_tsv from ts;
  ctid  |                          doc                           |                         doc_tsv
--------+--------------------------------------------------------+---------------------------------------------------------
 (0,10) | Can a sheet slitter slit sheets?                       | 'sheet':3,6 'slit':5 'slitter':4
 (0,11) | How many sheets could a sheet slitter slit?            | 'could':4 'mani':2 'sheet':3,6 'slit':8 'slitter':7
 (0,12) | I slit a sheet, a sheet I slit.                        | 'sheet':4,6 'slit':2,8
 (0,13) | Upon a slitted sheet I sit.                            | 'sheet':4 'sit':6 'slit':3 'upon':1
 (0,14) | Whoever slit the sheets is a good sheet slitter.       | 'good':7 'sheet':4,8 'slit':2 'slitter':9 'whoever':1
 (0,15) | I am a sheet slitter.                                  | 'sheet':4 'slitter':5
 (0,16) | I slit sheets.                                         | 'sheet':3 'slit':2
 (0,17) | I am the sleekest sheet slitter that ever slit sheets. | 'ever':8 'sheet':5,10 'sleekest':4 'slit':9 'slitter':6
 (0,18) | She slits the sheet she sits on.                       | 'sheet':4 'sit':6 'slit':2
(9 rows)
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

از بالا می توان دید که ورق ، شکاف و برش در چندین ردیف ظاهر می شود ، بنابراین چندین مورد وجود خواهد داشت. در این حالت ، یک لیست TID تولید می شود و یک درخت B جداگانه برای آن ایجاد می شود.

بیانیه زیر می تواند دریابد که چند ردیف کلمه در آن ظاهر می شود.

pg_study=# select (unnest(doc_tsv)).lexeme, count(*) from ts
group by 1 order by 2 desc;
  lexeme  | count
----------+-------
 sheet    |     9
 slit     |     8
 slitter  |     5
 sit      |     2
 upon     |     1
 mani     |     1
 whoever  |     1
 sleekest |     1
 good     |     1
 could    |     1
 ever     |     1
(11 rows)
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

مثال

پرس و جو زیر چگونه اجرا می شود؟

// Since the amount of data is small here, full table scan is disabled
pg_study=# set enable_seqscan TO off;
SET

pg_study=# explain(costs off)                                 
select doc from ts where doc_tsv @@ to_tsquery('many & slitter');
                             QUERY PLAN
---------------------------------------------------------------------
 Bitmap Heap Scan on ts
   Recheck Cond: (doc_tsv @@ to_tsquery('many & slitter'::text))
   ->  Bitmap Index Scan on ts_doc_tsv_idx
         Index Cond: (doc_tsv @@ to_tsquery('many & slitter'::text))
(4 rows)
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

ابتدا هر کلمه (کلید پرس و جو) را از پرس و جو استخراج کنید: مانی و برش. این توسط یک API ویژه تکمیل می شود ، که استراتژی های انواع داده ها و اپراتورها را در نظر می گیرد:

pg_study=# select amop.amopopr::regoperator, amop.amopstrategy
from pg_opclass opc, pg_opfamily opf, pg_am am, pg_amop amop
where opc.opcname = 'tsvector_ops'
and opf.oid = opc.opcfamily
and am.oid = opf.opfmethod
and amop.amopfamily = opc.opcfamily
and am.amname = 'gin'
and amop.amoplefttype = opc.opcintype;
        amopopr        | amopstrategy
-----------------------+--------------
 @@(tsvector,tsquery)  |            1
 @@@(tsvector,tsquery) |            2
(2 rows)
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

در درخت B حاوی کلمات ، لیست های TID مربوط به دو کلید را پیدا کنید:

مانی: (0،2)
برش: (0،1) ، (0،2) ، (1،2) ، (1،3) ، (2،2)

در آخر ، برای هر یک از آنها که پیدا شده است ، به نوبه خود با عملکرد قوام تماس بگیرید. این تابع قوام می تواند تعیین کند که آیا ردیف به TID با شرایط پرس و جو مطابقت دارد یا خیر. از آنجا که کلمات موجود در پرس و جو توسط و به هم وصل شده اند و ردیف برگشتی فقط (0،2) است.

       |      |         |  consistency
       |      |         |    function
  TID  | mani | slitter | slit & slitter
-------+------+---------+----------------
 (0,1) |    f |       T |              f 
 (0,2) |    T |       T |              T
 (1,2) |    f |       T |              f
 (1,3) |    f |       T |              f
 (2,2) |    f |       T |              f
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

نتیجه این است:

pg_study=# select doc from ts where doc_tsv @@ to_tsquery('many & slitter');
                     doc
---------------------------------------------
 How many sheets could a sheet slitter slit?
(1 row)
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

مشکل سرعت به روزرسانی آهسته

درج یا به روزرسانی داده ها در شاخص GIN بسیار کند است. از آنجا که هر ردیف معمولاً حاوی بسیاری از عناصر کلمه ای است که باید نمایه شوند. بنابراین ، هنگام افزودن یا به روزرسانی یک ردیف ، باید درخت شاخص را زیاد به روز کنیم.

از طرف دیگر ، اگر چندین ردیف به طور همزمان به روز شوند ، ممکن است برخی از عناصر کلمه آنها یکسان باشد ، بنابراین هزینه کل کمتر از هزینه به روزرسانی اسناد توسط ردیف است.

شاخص GIN دارای یک پارامتر ذخیره سازی FastUpdate است که می توانیم هنگام ایجاد فهرست مشخص کنیم و بعداً آن را به روز کنیم:

pg_study=# create index on ts using gin(doc_tsv) with (fastupdate = true);
CREATE INDEX
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

پس از فعال کردن این پارامتر ، به روزرسانی ها در یک لیست مجزا جداگانه جمع می شوند. هنگامی که این لیست به اندازه کافی بزرگ یا در هنگام خلاء (جمع آوری زباله) باشد ، تمام به روزرسانی های انباشته شده بلافاصله روی شاخص کار می شوند. لیست “به اندازه کافی بزرگ” توسط پارامتر پیکربندی “gin_pending_list_limit” یا پارامتر ذخیره سازی با همین نام هنگام ایجاد شاخص تعیین می شود.

جستجوی مسابقه جزئی

پرس و جو را که با شکاف شروع می شود پرس و جو کنید

pg_study=# select doc from ts where doc_tsv @@ to_tsquery('slit:*');
                          doc
--------------------------------------------------------
 Can a sheet slitter slit sheets?
 How many sheets could a sheet slitter slit?
 I slit a sheet, a sheet I slit.
 Upon a slitted sheet I sit.
 Whoever slit the sheets is a good sheet slitter.
 I am a sheet slitter.
 I slit sheets.
 I am the sleekest sheet slitter that ever slit sheets.
 She slits the sheet she sits on.
(9 rows)
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

از این شاخص همچنین می توان برای تسریع استفاده کرد:

pg_study=# explain (costs off)
select doc from ts where doc_tsv @@ to_tsquery('slit:*');
                    QUERY PLAN
---------------------------------------------------
 Seq Scan on ts
   Filter: (doc_tsv @@ to_tsquery('slit:*'::text))
(2 rows)
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

فراوانی کلمات کلیدی

برخی از داده ها را تولید کنید:

fts=# alter table mail_messages add column tsv tsvector;
fts=# update mail_messages set tsv = to_tsvector(body_plain);
fts=# create index on mail_messages using gin(tsv);
fts=# \timing on

-- A total of 466125 pieces of data
fts=# select count(*) from mail_messages;
 count
--------
 356125
(1 row)

-- Here, instead of using unnest to count the number of times a word appears in a row, because the amount of data is relatively large, we use the ts_stat function to calculate
fts=# select word, ndoc
fts-# from ts_stat('select tsv from mail_messages')
fts-# order by ndoc desc limit 3;
 word  |  ndoc  
-------+--------
 wrote | 231174
 use   | 173833
 would | 157169
(3 rows)

Time: 11556.976 ms
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

به عنوان مثال ، ما کلمه ای را که به ندرت در اطلاعات ایمیل مانند “خال کوبی” ظاهر می شود ، پرس و جو می کنیم:

fts=# select word, ndoc from ts_stat('select tsv from mail_messages') where word = 'tattoo';
  word  | ndoc 
--------+------
 tattoo |    2
(1 row)

Time: 11236.340 ms
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

تعداد دفعات دو کلمه در یک ردیف ظاهر می شود. فقط یک ردیف وجود دارد که در همان زمان نوشت و خال کوبی ظاهر می شود

fts=# select count(*) from mail_messages where tsv @@ to_tsquery('wrote & tattoo');
 count 
-------
     1
(1 row)

Time: 0.550 ms
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

بیایید ببینیم که چگونه اجرا می شود. همانطور که در بالا ذکر شد ، اگر می خواهیم لیست های TID دو کلمه را بدست آوریم ، بازده جستجو کاملاً کم است: زیرا ما باید بیش از 200،000 مقدار را طی کنیم و فقط یک مقدار گرفته می شود. با این حال ، از طریق اطلاعات آماری ، الگوریتم می تواند بداند که “نوشت” به طور مکرر ظاهر می شود ، در حالی که “خال کوبی” به ندرت ظاهر می شود. بنابراین ، جستجوی کلمه ای که اغلب مورد استفاده قرار نمی گیرد اجرا می شود ، و سپس بررسی می کند که آیا “نوشت” در دو ردیف بازیابی شده وجود دارد یا خیر. به این ترتیب ، نتیجه پرس و جو را می توان به سرعت بدست آورد:

fts=# select count(*) from mail_messages where tsv @@ to_tsquery('wrote & tattoo');
 count 
-------
     1
(1 row)

Time: 0.419 ms
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

محدود کردن نتایج پرس و جو

یکی از ویژگی های شاخص جین این است که فقط می تواند یک مپ کوچک را برگرداند و نمی تواند هر یک را یک به یک برگرداند. بنابراین ، تمام برنامه های موجود در این مقاله از اسکن Bitmap استفاده می کنند.

روش فشرده سازی

یکی از مزایای جین ویژگی فشرده سازی آن است. در مرحله اول ، اگر همان کلمه در چندین ردیف ظاهر شود ، فقط یک بار در فهرست ذخیره می شود. ثانیا ، TID ها به صورت مرتب شده در فهرست ذخیره می شوند ، که ما را قادر می سازد از یک روش فشرده سازی ساده استفاده کنیم: نکته بعدی در لیست در واقع با TID قبلی متفاوت است. این تعداد معمولاً بسیار اندک است و در مقایسه با کامل کامل شش بایت ، تعداد بیت های مورد نیاز بسیار کوچکتر است.

اندازه شاخص های مختلف را با یکدیگر مقایسه کنید:

یک فهرست B-Tree ایجاد کنید:
جین بر روی یک نوع داده متفاوت (tsveector به جای متن) ساخته شده است ، و این نوع داده کوچکتر است.
در همان زمان ، درخت B پیام را به 2K کاهش می دهد.

fts=# create index mail_messages_btree on mail_messages(substring(body_plain for 2048));
CREATE INDEX
Time: 32709.807 ms
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

یک فهرست GIST ایجاد کنید:

fts=# create index mail_messages_gist on mail_messages using gist(tsv);
CREATE INDEX
Time: 14651.884 ms
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

به ترتیب به اندازه های جین ، gist و btree نگاه کنید:

fts=# select pg_size_pretty(pg_relation_size('mail_messages_tsv_idx')) as gin,
fts-#              pg_size_pretty(pg_relation_size('mail_messages_gist')) as gist,
fts-#              pg_size_pretty(pg_relation_size('mail_messages_btree')) as btree;
  gin   |  gist  | btree  
--------+--------+--------
 189 MB | 111 MB | 526 MB
(1 row)

Time: 2.961 ms
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

از آنجا که شاخص GIN فضای بیشتری را ذخیره می کند ، می توانیم به جای شاخص Bitmap در طی فرآیند مهاجرت از Oracle به PostgreSQL از شاخص GIN استفاده کنیم. به طور کلی ، شاخص Bitmap برای زمینه هایی با مقادیر بسیار کمی منحصر به فرد استفاده می شود ، که برای جین نیز بسیار مؤثر است. علاوه بر این ، PostgreSQL می تواند به صورت پویا بر اساس هر شاخص (از جمله جین) یک bitmap بسازد.

سرانجام ، ما یک بستر مناسب برای استقرار خدمات وب را توصیه می کنیم: جهش

شرح تصویر

🚀 با زبان مورد علاقه خود بسازید

با زحمت در JavaScript ، Python ، Go یا Rust.

🌍 پروژه های نامحدود را به صورت رایگان مستقر کنید

فقط آنچه را که استفاده می کنید بپردازید – بدون درخواست ، بدون هزینه.

⚡ پرداخت به عنوان شما ، بدون هزینه های پنهان

بدون هزینه بیکار ، فقط مقیاس پذیری بدون درز.

شرح تصویر

📖 اسناد ما را کاوش کنید

🔹 ما را در توییتر دنبال کنید: leapcellhq

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا