پرش به محتوای اصلی
پرش به محتوای مقاله

۲ شرط حیاتی برای بهینه‌سازی جست‌وجوی برداری در PostgreSQL

·۲۱ مرداد ۱۴۰۵۹ دقیقه مطالعه۲ بازدید
راهنما
نصب pgvector و اولین کوئری: راهنمای گام‌به‌گام
نصب pgvector و اولین کوئری: راهنمای گام‌به‌گام
اشتراک‌گذاری
واقعاً چه چیز جدید است؟

تمرکز این گزارش بر شناسایی «شکست‌های خاموش» در pgvector است؛ جایی که دیتابیس بدون خطا کار می‌کند اما به‌دلیل عدم تطابق کلاس عملگر، به‌جای ایندکس از اسکن ترتیبی استفاده کرده و عملکرد را به‌شدت کاهش می‌دهد.

یک اشتباه کوچک در انتخاب کلاس عملگر در pgvector می‌تواند به‌طور خاموش عملکرد پایگاه‌داده شما را نابود کند و سیستم را مجبور به اسکن ترتیبی میلیون‌ها ردیف نماید. در ۱۲ اوت ۲۰۲۶، یک راهنمای فنی جامع در وب‌سایت dev.to این ریسک را بررسی کرد و نقشه راهی برای تبدیل یک نصب خام به یک سیستم جست‌وجوی بهینه در PostgreSQL ارائه داد.

بسیاری از توسعه‌دهندگان با پایگاه‌داده‌های برداری مثل یک جعبه سیاه برخورد می‌کنند، اما pgvector مستقیماً در اکوسیستم پستگرس ادغام شده است. این یعنی شما تراکنش‌ها، پشتیبان‌ها و Joinهای فعلی خود را حفظ می‌کنید و هم‌زمان قابلیت ذخیره بردار معنایی (Embedding) — که مثل یک کارت معرفی عددی برای هر واژه است و می‌گوید این کلمه همسایه چه کلمات دیگری است — را به دست می‌آورید. برای کسانی که سیستم‌های تولید بازیابی‌افزا (RAG) — شبیه دانش‌آموزی که قبل از جواب دادن، اول کتاب درسی را باز می‌کند و از آن نقل می‌آورد — می‌سازند، این ادغام نیاز به یک پایگاه‌داده برداری مجزا را از بین می‌برد.

همان‌طور که در تحلیل‌های قبلی ما درباره‌ی زیرساخت‌های داده برای هوش مصنوعی اشاره کردیم، ساده‌سازی لایه ذخیره‌سازی، کلید مقیاس‌پذیری است.

نصب و مدیریت نسخه‌ها

طبق مستندات، نصب این افزونه مستلزم آن است که کتابخانه مشترک پیش از اجرای دستورات SQL روی دیسک موجود باشد. در سرویس‌های مدیریت‌شده، این مورد معمولاً پیش‌نصب است و تنها با دستور CREATE EXTENSION IF NOT EXISTS vector; فعال می‌شود.

در محیط‌های شخصی، باید بسته مربوط به پلتفرم خود را نصب کنید؛ مثلاً postgresql-17-pgvector در دبیان و اوبونتو، یا نصب pgvector از طریق Homebrew در مک. نکته حیاتی این است که این افزونه در سطح هر پایگاه‌داده فعال می‌شود، نه در سطح کل خوشه (Cluster). اگر افزونه را در دیتابیس postgres بسازید و سپس به دیتابیس app متصل شوید، با خطای ERROR: type "vector" does not exist مواجه می‌شوید. این یک مسئله مربوط به محدوده دسترسی (Scoping) است و به معنای شکست در نصب نیست.

ردیابی نسخه به‌دلیل تکامل سریع pgvector حیاتی است. شما می‌توانید نسخه خود را با دستور SELECT extversion FROM pg_extension WHERE extname = 'vector'; بررسی کنید. بر اساس گزارش dev.to، نسخه ۰.۵.۰ ایندکس‌های HNSW را معرفی کرد، نسخه ۰.۶.۰ قابلیت ساخت موازی ایندکس‌ها را اضافه کرد و نسخه ۰.۷.۰ انواع halfvec و sparsevec را به همراه عملگر L1 (<+>) آورد. نسخه ۰.۸.۰ نیز اسکن‌های تکرار شونده ایندکس (Iterative Index Scans) را معرفی کرد. اجرای دستورات نسخه ۰.۸.۰ روی نسخه ۰.۴.۴ یکی از رایج‌ترین منابع خطای پیکربندی است.

طراحی طرح‌واره و محدودیت‌ها

هنگام ایجاد جدول، باید بُعد (Dimension) ستون برداری را به‌صورت ثابت تعریف کنید. ستونی که بُعد آن محدود نشده باشد، قابل ایندکس‌گذاری نیست و این موضوع پس از بارگذاری داده‌های حجیم به یک شکست بحرانی تبدیل می‌شود. یک طرح‌واره استاندارد برای تکه‌های متن (Chunks) به این شکل است:

CREATE TABLE chunks (
  id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  document_id bigint NOT NULL,
  ord int NOT NULL,
  content text NOT NULL,
  embedding vector(1536) NOT NULL,
  model text NOT NULL,
  created_at timestamptz NOT NULL DEFAULT now(),
  UNIQUE (document_id, ord)
);
  • محدودیت بُعد: ذخیره‌سازی تا ۱۶,۰۰۰ بُعد را پشتیبانی می‌کند، اما ایندکس‌ها به ۲,۰۰۰ بُعد محدود شده‌اند. در محیط عملیاتی، این محدودیت ایندکس است که در واقع تعیین‌کننده و محدودکننده است.
  • ردیابی مدل: همیشه باید یک ستون متنی برای نام مدل (model) داشته باشید. بردارهای تولید شده توسط مدل‌های مختلف با هم قابل مقایسه نیستند؛ ترکیب آن‌ها نتایجی را برمی‌گرداند که از نظر ریاضی معتبر اما از نظر منطقی کاملاً بی‌معنی هستند. ذخیره نام مدل تضمین می‌کند که یک اشتباه به جای تبدیل شدن به یک معمای حل‌نشده، به کوئری تبدیل شود که بتوانید آن را بنویسید و اصلاح کنید.

اگر مدل شما ۳,۰۷۲ بُعد تولید می‌کند، راهنما پیشنهاد می‌کند از مدل‌های Matryoshka برای برش (Truncation) استفاده کنید یا یک نسخه کامل از بردار را برای بازرتبه‌بندی (Reranking) ذخیره کرده و هم‌زمان یک نسخه برش‌خورده را برای ایندکس‌گذاری نگه دارید.

استراتژی‌های ورود داده

درج بردارها از طریق رشته‌های متنی محصور در کروشه و جدا شده با کاما انجام می‌شود، مانند '[0.0123,-0.0456, ... ,0.0789]'::vector. از آنجا که هر کتابخانه کلاینتی که قادر به ارسال متن باشد می‌تواند این فرمت را ارسال کند، pgvector با تقریباً هر زبانی سازگار است.

برای مجموعه‌های کوچک، دستورات INSERT تک‌به‌تک کار می‌کنند، اما برای بارگذاری انبوه، دستور COPY چندین برابر سریع‌تر است. برای بارگذاری از یک فایل CSV، باید ستون بردار را به همان شکل رشته‌ای محصور در کروشه بنویسید و از دستور COPY chunks (...) FROM '/tmp/chunks.csv' WITH (FORMAT csv); استفاده کنید.

یک نکته کلیدی: ابتدا داده‌ها را بارگذاری کنید و سپس ایندکس بسازید. ساخت ایندکس HNSW روی جدول خالی و سپس درج ردیف‌ها به‌شدت کند است، زیرا هر درج مستلزم یک پیمایش گراف (Graph Traversal) است. در مورد ایندکس‌های IVFFlat، این کار حتی اشتباه است؛ زیرا ایندکس داده‌ای برای خوشه‌بندی ندارد و مرکزهای (Centroids) بی‌فایده‌ای ایجاد می‌کند.

تسلط بر عملگرهای فاصله

pgvector عملگرهایی را فراهم می‌کند که نتایج را به‌صورت صعودی (نزدیک‌ترین ابتدا) مرتب می‌کنند. انتخاب عملگر اشتباه، رایج‌ترین باگ در پیاده‌سازی‌های pgvector است:

  • <->: فاصله اقلیدسی (L2). نزدیک‌ترین بردار کوچک‌ترین مقدار است. زمانی استفاده شود که بردارها نرمال‌سازی نشده‌اند.
  • <=>: فاصله کسینوسی (Cosine Distance). این فاصله به صورت 1 − cosine similarity تعریف شده و بازه‌ای بین ۰ (جهت یکسان) تا ۲ (جهت مخالف) دارد. این همان چیزی است که اکثر APIهای بردار انتظار دارند.
  • <#>: ضرب داخلی منفی (Negative Inner Product). دلیل منفی بودن این است که ترتیب صعودی همچنان به معنای «نزدیک‌ترین ابتدا» باقی بماند. برای به دست آوردن ضرب داخلی واقعی، نتیجه را در ۱- ضرب کنید. برای بردارهای نرمال‌شده (Unit-normalized)، این عملگر دقیقاً مشابه فاصله کسینوسی رتبه‌بندی می‌کند اما از نظر محاسباتی کمی ارزان‌تر است.
  • <+>: فاصله L1 (تاکسی‌کب). در نسخه ۰.۷.۰ اضافه شد، هرچند به‌ندرت معیار مناسبی برای بردارهای متنی است.

ایندکس‌گذاری با HNSW

برای عبور از جست‌وجوی دقیق — که فاصله را برای تک‌تک ردیف‌ها حساب می‌کند — باید ایندکس HNSW بسازید. عبارت ORDER BY باید دقیقاً از همان عبارت و عملگری استفاده کند که ایندکس شده است. همچنین، ایندکس تنها به کوئری‌های ORDER BY ... LIMIT کمک می‌کند. کوئری‌هایی که فاقد LIMIT هستند یا از یک تابع Wrapper برای مرتب‌سازی استفاده می‌کنند، از ایندکس استفاده نخواهند کرد.

برای بهینه‌سازی ساخت، راهنما توصیه می‌کند maintenance_work_mem را افزایش دهید (مثلاً تا ۴ گیگابایت) و max_parallel_maintenance_workers را تنظیم کنید (که در نسخه ۰.۶.۰ به بعد در دسترس است). نمونه ساخت ایندکس:

CREATE INDEX chunks_embedding_hnsw ON chunks 
USING hnsw (embedding vector_cosine_ops) 
WITH (m = 16, ef_construction = 64);

سه کلاس عملگر وجود دارد: vector_l2_ops ،vector_cosine_ops و vector_ip_ops. اگر با vector_l2_ops ایندکس کنید اما با <=> جست‌وجو کنید، پستگرس به‌طور خاموش ایندکس را نادیده گرفته و اسکن ترتیبی (Sequential Scan) انجام می‌دهد.

دو پیچ تنظیم اصلی برای HNSW وجود دارد: m (پیش‌فرض ۱۶) و ef_construction (پیش‌فرض ۶۴). این‌ها بر مصرف حافظه، زمان ساخت و نرخ بازیابی (Recall) اثر می‌گذارند. پیش از آنکه Recall واقعی خود را اندازه‌گیری کنید، این مقادیر پیش‌فرض را تغییر ندهید.

تحلیل برنامه اجرا (Execution Plan)

تنها راه تایید عملکرد ایندکس، استفاده از EXPLAIN (ANALYZE, BUFFERS) است. یک برنامه سالم باید Index Scan را نشان دهد و گره Order By نباید یک گره Sort مجزا در بالای خود داشته باشد. تخمین rows=1000 در برنامه‌ریز (Planner) صرفاً یک جای‌گذار است؛ آن را نادیده بگیرید و به مقدار actual ... rows=10 توجه کنید.

اگر برنامه Seq Scan و سپس Sort را نشان می‌دهد، یعنی جست‌وجوی دقیق در حال رخ دادن است. این اتفاق به چهار دلیل اصلی می‌افتد:
۱. عدم تطابق کلاس عملگر با عملگر مورد استفاده در کوئری.
۲. نبود عبارت LIMIT در کوئری.
۳. وجود یک عبارت WHERE که آنقدر گزینشی (Selective) است که پستگرس اسکن فیلتر شده را ارزان‌تر می‌بیند.
۴. نامعتبر بودن ایندکس به‌دلیل شکست در دستور CREATE INDEX CONCURRENTLY. می‌توانید ایندکس‌های نامعتبر را با دستور SELECT indexrelid::regclass AS index, indisvalid FROM pg_index WHERE NOT indisvalid; بررسی کنید.

نظارت بر Buffers: shared hit ضروری است. اگر به‌جای hit عبارت shared read را می‌بینید، یعنی ایندکس از دیسک خوانده می‌شود. پیمایش گراف HNSW از روی دیسک، به‌اندازه نسبت تأخیر دیسک به تأخیر حافظه، کندتر است. اگر مقدار read بالا و مداوم است، ایندکس در shared_buffers جا نمی‌شود و هیچ تنظیم پارامتری این مشکل را حل نخواهد کرد.

خطاهای رایج و عیب‌یابی

توسعه‌دهندگان معمولاً با چهار خطای اصلی مواجه می‌شوند:

  • «expected 1536 dimensions, not 768»: عدم تطابق طول بردار هنگام درج، که اغلب به‌دلیل ترکیب مدل‌های مختلف در یک خط لوله (Pipeline) رخ می‌دهد.
  • «column cannot have more than 2000 dimensions for hnsw index»: محدودیت ایندکس HNSW روی ۲,۰۰۰ بُعد است، حتی اگر ذخیره‌سازی تا ۱۶,۰۰۰ بُعد را پشتیبانی کند.
  • «operator class vector_cosine_ops does not exist for access method hnsw»: نشانه قدیمی بودن pgvector (قبل از ۰.۵.۰). باید باینری را ارتقا داده و دستور ALTER EXTENSION vector UPDATE را اجرا کنید.
  • «hnsw graph no longer fits into maintenance_work_mem after X tuples»: این یک NOTICE (هشدار) است، نه خطا. یعنی ساخت ایندکس به استراتژی روی دیسک تغییر یافته و بسیار کندتر می‌شود. اگر پردازشی که باید ۲۰ دقیقه طول بکشد، بعد از دو ساعت هنوز در حال اجراست، این پیام را بررسی کرده و maintenance_work_mem را افزایش دهید.

این چرخش به سمت قابلیت‌های برداری یکپارچه در پستگرس، این فرض معماری را که RAG نیازمند یک پایگاه‌داده تخصصی است، تغییر می‌دهد. با بهره‌گیری از ابزارهای SQL موجود، توسعه‌دهندگان می‌توانند بدهی فنی را کاهش داده و خط لوله داده‌های خود را ساده کنند.

گام بعدی شما

  • اگر از pgvector استفاده می‌کنید، همین امروز با EXPLAIN ANALYZE بررسی کنید که آیا کوئری‌های شما واقعاً از Index Scan استفاده می‌کنند یا در تله Seq Scan افتاده‌اند.
  • در صورت استفاده از مدل‌های با بُعد بالا (بیش از ۲۰۰۰)، استراتژی Matryoshka یا ذخیره‌سازی دوگانه (بردار کوتاه برای جست‌وجو و بردار کامل برای بازرتبه‌بندی) را پیاده کنید.
  • حافظه maintenance_work_mem را هنگام ساخت ایندکس‌های حجیم افزایش دهید تا از کندی شدید ناشی از نوشتن روی دیسک جلوگیری کنید.

اما داستان سخت‌افزاری این تحول حتی شگفت‌انگیزتر است — به تحلیل ما درباره‌ی تراشه‌های Blackwell مراجعه کنید.

چرا این موضوع مهم است؟

این بهینه‌سازی‌ها بر اساس تجربه عملی در مقیاس بالا نشان می‌دهد که تفاوت بین یک سیستم سریع و یک سیستم کند در RAG، نه در مدل زبانی، بلکه در جزئیات پیاده‌سازی ایندکس‌های برداری است. اعتبار این متدولوژی از ادغام استانداردهای صنعتی PostgreSQL با الگوریتم‌های مدرن جست‌وجوی نزدیک‌ترین همسایه می‌آید.

تأثیر برای ایران

برای توسعه‌دهندگان ایرانی که به‌دلیل هزینه‌های ارزی یا محدودیت‌های دسترسی به Vector DBهای ابری (مانند Pinecone) به دنبال جایگزین‌های Open-source هستند، تسلط بر pgvector ارزان‌ترین و پایدارترین مسیر برای پیاده‌سازی RAG در مقیاس صنعتی است.

·نگاه ما
تحریریه دات‌هوش

یکپارچگی جست‌وجوی برداری در PostgreSQL نشان می‌دهد که ما از عصر «پایگاه‌داده‌های تخصصی برای هر کار» به سمت «پایگاه‌داده‌های چندمنظوره با قابلیت‌های تخصصی» حرکت می‌کنیم. این رویکرد باعث می‌شود لایه داده‌ها از پیچیدگی توزیع‌شده بین یک DB رابطه‌ای و یک Vector DB رها شود و مدیریت سازگاری (Consistency) داده‌ها بسیار ساده‌تر گردد. در واقع، قدرت SQL در ترکیب با HNSW، جست‌وجوی معنایی را به یک قابلیت استاندارد تبدیل می‌کند، نه یک ابزار جانبی.

منابع

این گزارش با خط‌لولهٔ خودکار دات‌هوش از منابع معتبر جهانی تدوین و زیر نظر تحریریه منتشر شده است. روش کار ما

گفتگو

پنج‌شنبه‌های هوش‌محور

بسته‌ی هفتگی دات‌هوش

۵ خبر، ۲ ابزار، ۱ پرامپت در هر شماره. به‌زودی راه‌اندازی می‌شود — هر پنج‌شنبه صبح.

خبر کلیدی
ابزار کاربردی
پرامپت حرفه‌ای
تحلیل پژوهش
به‌زودی
زاویه‌ی ایرانی
به‌زودی
تمرین این هفته
به‌زودی

راهنماهای دات‌هوش

راهنماهای کاربردیِ دات‌هوش برای کار با هوش مصنوعی — از همین‌جا شروع کنید:

دات‌هوش

راهنمای فارسی هوش مصنوعی — با نگاه به ایران

اخبار روزانه، معرفی ابزارها و مدل‌ها، و آموزشِ کار با هوش مصنوعی؛ همیشه با این پرسش که از ایران چه چیزی کار می‌کند و چه چیزی نه.