تخیل کنید سیستم هوش مصنوعی شما برای یادآوری یک خاطره ساده، بیش از یک ثانیه مکث کند؛ این تأخیر در مقیاس واقعی، تجربه کاربر را نابود میکند. کاهش زمان پاسخدهی از ۱۲۰۰ به ۶۵ میلیثانیه برای یک مجموعه داده شامل ۵۰۰۰ بردار، با اعمال پنج بهینهسازی خاص در pgvector، دقیقاً همان نقطهای است که یک پروژه جانبی به یک محصول تجاری تبدیل میشود.
به نقل از گزارش فنی منتشر شده در وبسایت dev.to، سیستم حافظه شخصی Vibe-Memory نشان داد که دستیابی به جستوجوی معنایی با تأخیر زیر ۱۰۰ میلیثانیه در یک سرور مجازی (VPS) ارزانقیمت ۵ دلاری در ماه، بدون نیاز به مهاجرت به دیتابیسهای تخصصی برداری، کاملاً ممکن است. بسیاری از برنامهنویسان تصور میکنند وقتی یک مجموعه داده از چند هزار مورد فراتر میرود، PostgreSQL دیگر برای جستوجوی برداری کارآمد نیست. این باور غلط اغلب منجر به پذیرش زودهنگام زیرساختهای پیچیدهای مانند Pinecone یا Weaviate میشود. اما آزمایشهای Vibe-Memory ثابت کرد که برای مجموعههای داده کوچک تا متوسط (زیر ۱۰۰ هزار بردار)، گلوگاه اصلی بهندرت خودِ دیتابیس است، بلکه مشکل اصلی فقدان استراتژیهای خاص ایندکسگذاری و پرسوجو است. نویسنده این پروژه سه هفته را صرف جستوجو، آزمون و خطا و شکستن سیستم کرد تا در نهایت به پرسوجوهایی ۱۰ برابر سریعتر دست یابد و متوجه شد که چند ترفند ساده میتواند سرعت خیره کنندهای ایجاد کند، بدون اینکه نیاز باشد افزونههای PostgreSQL را از سورس کد کامپایل کند.
برای درک بهتر، تصور کنید میخواهید صفحهای خاص را در یک کتاب پیدا کنید اما مجبور باشید تکتک کلمات را از صفحه اول تا انتها بخوانید؛ این دقیقاً همان روش جستوجوی ساده یا Naive است. در این حالت، سیستم یک جستوجوی دقیق «نزدیکترین همسایه» (Exact Nearest Neighbor) با پیچیدگی زمانی O(n) انجام میدهد. در حالی که این روش برای ۱۰۰ خاطره عالی عمل میکند، اما با رسیدن به ۵۰۰۰ بردار، بهشدت کند شده و پدیده «فراموشی هوش مصنوعی» را ایجاد میکند؛ وضعیتی که در آن سیستم بیش از حد دیر زمینه (Context) مرتبط را بازیابی میکند. هدف Vibe-Memory رفع این مشکل در ChatGPT از طریق ذخیره گفتگوها به شکل بردار معنایی (Embedding) در PostgreSQL و بازیابی مرتبطترین خاطرات از نظر معنایی هنگام پرسش کاربر بود.
چرا جستوجوی ساده شکست میخورد؟
در مراحل اولیه توسعه Vibe-Memory، زمانی که تعداد خاطرات بین ۱۰۰ تا ۱۰۰۰ مورد بود، عملکرد سیستم قابل قبول به نظر میرسید. اما با رسیدن به ۵۰۰۰ بردار، پرسوجوهایی که پیش از این ۱۰۰ میلیثانیه زمان میبردند، ناگهان به ۱.۵ ثانیه جهش کردند. مقصر اصلی، پیادهسازی استاندارد جستوجوی Naive بود که در اکثر آموزشهای آنلاین دیده میشود. کد مورد استفاده به این صورت بود:
func (s *Storage) SearchSimilarVectors(queryVector []float32, limit int) ([]Memory, error) {
var memories []Memory
err := s.db.Select(&memories, ` SELECT id, content, embedding <-> $1 AS distance FROM memories ORDER BY distance LIMIT $2 `, queryVector, limit)
return memories, err
}
این رویکرد دیتابیس را مجبور میکند در هر بار پرسوجو، تکتک بردارها را بررسی کند. برای توسعهدهندهای که از یک VPS ارزان استفاده میکند، هدف این بود که از افزودن قطعات متحرک جدید به زیرساخت اجتناب کند و در عوض، تنظیمات موجود PostgreSQL را بهینه نماید. نویسنده خاطرنشان کرد که چون هزینه VPS پرداخت شده و PostgreSQL هم از پیش نصب بود، هیچ دلیل منطقی برای افزودن پیچیدگیهای معماری به یک پروژه جانبی وجود نداشت. او تعمداً از افزودن یک دیتابیس برداری مجزا اجتناب کرد تا پروژه سبک و قابل نگهداری باقی بماند.
استراتژی ایندکسگذاری
اولین پیروزی بزرگ با گذشتن از جستوجوهای دقیق و حرکت به سمت ایندکسهای «نزدیکترین همسایهی تقریبی» (ANN) بهدست آمد. نویسنده سه گزینه اصلی را مورد آزمایش قرار داد:
- IVFFlat (Inverted File Index): این گزینه به دلیل تعادل بین سرعت ساخت و بهرهوری حافظه، انتخاب اصلی بود. ویژگی آن پرسوجوهای سریع اما ساخت کندتر است. این روش انتخاب شد چون «تمام رم را نمیبلعید» و زمان ساخت آن بیپایان نبود.
- HNSW (Hierarchical Navigable Small World): این ایندکس سریعترین زمان پاسخدهی (حدود ۵۰ تا ۶۰ میلیثانیه) را ارائه داد، اما زمان ساخت آن بسیار بیشتر بود (۴۵ ثانیه در مقابل ۵ ثانیه برای IVFFlat) و مصرف حافظه بالاتری داشت (۳۵ مگابایت در برابر ۲۰ مگابایت برای مجموعه ۵۰۰۰ بردار با ابعاد ۵۱۲). این روش عموماً برای مجموعههای داده بزرگتر مناسبتر است.
- بدون ایندکس: همان جستوجوی دقیق پیشفرض است. با وجود دقت ۱۰۰٪، برای تعداد دادههای بالا (N بزرگ) بهشدت کند است.
برای پیادهسازی ایندکس IVFFlat، نویسنده در ابتدا اشتباهی کرد و بر اساس منطق ساده rows / 1000 تنها ۵ لیست ایجاد کرد. این کار منجر به Recall (میزان بازیابی) بسیار بدی شد و بسیاری از بردارهای مرتبط پیدا نشدند. فرمول اصلاحشده برای تعیین تعداد لیستها این است: number_of_lists = rows / 1000 یا number_of_lists = 4 * sqrt(rows)، هر کدام که مقدارش بزرگتر باشد. برای ۵۰۰۰ ردیف، محاسبه 4 * sqrt(5000) عدد ۲۸۲ را پیشنهاد میدهد، اما نویسنده برای رسیدن به تعادل مناسب، روی ۱۰۰ لیست توافق کرد.
دستور SQL زیر برای ایجاد ایندکس استفاده شد:CREATE INDEX ON memories USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100);
استفاده از ایندکس IVFFlat به تنهایی زمان پرسوجو را از ۱۲۰۰ میلیثانیه به ۱۵۰ میلیثانیه رساند که یک بهبود ۸ برابری است. هرچند IVFFlat تقریبی است و ممکن است برخی نتایج را حذف کند، اما نویسنده دریافت که در مورد استفاده از حافظه شخصی، بازیابی ۹۵٪ نتایج، بهای پذیرفتنی برای این افزایش سرعت است. در یک حافظه معنایی شخصی، هوش مصنوعی فقط نیاز دارد «لب کلام» یا مفهوم کلی اطلاعات را بگیرد، نه اینکه تکتک خاطرات موجود را بازیابی کند.
پارتیشنبندی زمانی
تمام خاطرات ارزش یکسانی ندارند. توسعهدهنده متوجه شد که خاطرات مربوط به دو سال پیش بهندرت به اندازه خاطرات دیروز مرتبط هستند. برای بهرهبرداری از این نکته، فیلترینگ زمانی پیادهسازی شد تا سیستم بهطور پیشفرض فقط دادههای N ماه اخیر را جستوجو کند. این اقدام هم یک ترفند عملکردی است و هم یک بهبود معنایی، زیرا زمینه (Context) اخیر معمولاً با گفتگوهای جاری کاربر مرتبطتر است.
جزئیات پیادهسازی:
- فیلتر پیشفرض: سیستم بهطور پیشفرض ۶ ماه اخیر را جستوجو میکند.
- کاهش حجم داده: در یک جستوجوی معمولی، این کار تعداد بردارهای قابل جستوجو را از ۵۰۰۰ مورد به حدود ۱۲۰۰ مورد کاهش داد.
- مقیاسپذیری پویا: اگر نتایج به دست آمده کافی نباشند، بازه زمانی جستوجو را میتوان بهصورت دستی به ۲۴ ماه افزایش داد.
- بهبود عملکرد: این مرحله زمان پرسوجو را به ۸۰ میلیثانیه رساند که به معنای بهبود ۲ برابری دیگر نسبت به حالت دارای ایندکس بود.
کد برنامه برای شامل شدن برچسب زمانی قطع (cutoff timestamp) بهروزرسانی شد:
func (s *Storage) SearchSimilarVectors(queryVector []float32, limit int, months int) ([]Memory, error) {
var memories []Memory
cutoff := time.Now().AddDate(0, -months, 0)
err := s.db.Select(&memories, ` SELECT id, content, created_at, embedding <-> $1 AS distance FROM memories WHERE created_at >= $3 ORDER BY distance LIMIT $2 `, queryVector, limit, cutoff)
return memories, err
}
بهداشت دیتابیس و پرسوجوها
سه تغییر فنی نهایی، آخرین قطرات عملکرد را از سیستم استخراج کردند:
۱. حذف ستونهای بلااستفاده: در پرسوجوی اولیه، ستون embedding در عبارت SELECT گنجانده شده بود. از آنجایی که pgvector فاصله را با استفاده از ایندکس محاسبه میکند، بازگرداندن بردار (شامل ۵۱۲ عدد اعشاری برای هر ردیف) از طریق شبکه غیرضروری بود. برای ۱۰ نتیجه، این کار تقریباً ۲۰ کیلوبایت انتقال داده اضافی ایجاد میکرد (۱۰ نتیجه * ۵۱۲ عدد * ۴ بایت). حذف این ستون، ۵ تا ۱۰ میلیثانیه دیگر از زمان پاسخدهی کم کرد.
۲. Vacuum دستی: پس از درج دستهای ۵۰۰۰ خاطره پس از ایندکسگذاری، سیستم همچنان کند بود. دلیل این اتفاق این بود که برنامهریز پرسوجو (Query Planner) برای انتخاب درستترین مسیر، به آمارهای بهروزرسانی شده نیاز داشت. اجرای دستی دستور VACUUM ANALYZE memories; باعث شد برنامهریز مسیر صحیح را انتخاب کند و ۱۰ تا ۱۵ میلیثانیه سرعت افزایش یابد. نویسنده اشاره میکند که اگرچه PostgreSQL بهطور خودکار و تدریجی Vacuum میکند، اما دخالت دستی پس از بارگذاریهای حجیم (Bulk Load) حیاتی است.
۳. کاهش ابعاد: نویسنده از مدل text-embedding-3-small استفاده کرد. با استفاده از ۵۱۲ بُعد به جای ۱۵۳۶ بُعد، سیستم به یکسوم نیازهای ذخیرهسازی دست یافت و سرعت جستوجو ۳ برابر شد، در حالی که کیفیت نتایج تقریباً یکسان ماند. ابعاد کوچکتر منجر به سرعت بیشتر در همه مراحل میشود.
۴. انتخاب معیار فاصله: نویسنده از فاصله کسینوسی (عملگر <->) استفاده کرد، زیرا Embeddings شرکت OpenAI برای شباهت کسینوسی آموزش دیدهاند. اگرچه فاصله L2 گاهی اوقات میتواند کمی سریعتر باشد، اما فاصله کسینوسی دقت معنایی لازم را برای حافظه هوش مصنوعی فراهم میکرد.
پلههای نهایی عملکرد
طبق گزارش، بهبودهای تجمعی منجر به جهشی عظیم در کارایی شد:
- پایه (بدون ایندکس، جستوجوی کامل): ۱۲۰۰ میلیثانیه
- ایندکس IVFFlat: ۱۵۰ میلیثانیه (۸ برابر سریعتر)
- فیلتر زمانی (۶ ماه اخیر): ۸۰ میلیثانیه (۱.۹ برابر سریعتر)
- پاکسازی ستونها (حذف embedding از SELECT): ۷۰ میلیثانیه (۱.۱ برابر سریعتر)
- Vacuum Analyze (بهروزرسانی آمار): ۶۵ میلیثانیه (۱.۱ برابر سریعتر)
- ایندکس HNSW (ارتقای اختیاری): ۵۰ میلیثانیه (۱.۳ برابر سریعتر)
این مسیر از ۱۲۰۰ میلیثانیه و پایان در حدود ۶۵ میلیثانیه، نشاندهنده یک بهبود کلی ۱۸ برابری است. در برخی موارد، جستوجوی تنها چند صد بردار اخیر، تأخیر را حتی بیشتر کاهش داده و به ۲۰ تا ۳۰ میلیثانیه رساند که باعث میشود بازیابی اطلاعات کاملاً لحظهای به نظر برسد.
تحلیل برای توسعهدهندگان
این نتیجه، فرضهای اولیه برای «هکرهای مستقل» (Indie-hackers) و تیمهای کوچک هوش مصنوعی را تغییر میدهد. این ثابت میکند که «مالیات دیتابیس برداری» — یعنی سربار مدیریت یک سرویس مجزا — برای اکثریت قریب به اتفاق پروژههای جانبی غیرضروری است.
چارچوب تصمیمگیری برای ایندکسگذاری:
- کمتر از ۱۰ هزار بردار: IVFFlat کافی است، ساخت آن آسانتر است و سرعتش مناسب است.
- ۱۰ هزار تا ۱۰۰ هزار بردار: احتمالاً HNSW به دلیل سرعت پرسوجو، ارزش هزینه حافظه (RAM) بیشتر را دارد.
- بیش از ۱۰۰ هزار بردار: دیتابیسهای برداری تخصصی یا HNSW ضروری میشوند.
برای خواننده، این بدان معناست که میتوانید یک حافظه پیشرفته و بلندمدت برای هوش مصنوعی بسازید، بدون اینکه قبض ماهانه ابری یا پیچیدگی معماری خود را افزایش دهید. هزینه این کار، کاهش اندک در دقت بازیابی (Recall) به دلیل ماهیت تقریبی ایندکسهای ANN است، اما برای حافظه معنایی شخصی، بازیابی ۹۵٪ در برابر چنین افزایش سرعت چشمگیری، یک معامله عادلانه است.
خلاصه مزایا و معایب:
- مزایا: همه چیز در PostgreSQL باقی میماند؛ عدم نیاز به سرویسهای اضافی؛ افزایش سرعت عظیم با تغییرات حداقلی؛ عملکرد خوب برای مجموعههای تا ۱۰۰ هزار بردار؛ افزودن ارتباط معنایی از طریق فیلتر زمانی.
- معایب: ایندکسهای تقریبی بخشی از دقت بازیابی را فدا میکنند؛ IVFFlat در صورت تغییر شدید توزیع دادهها نیاز به بازسازی دستی دارد؛ HNSW مصرف حافظه بیشتر و زمان ساخت طولانیتری دارد.
اگر در حال حاضر در خط لوله تولید بازیابیافزا (RAG) — شبیه دانشآموزی که قبل از جواب دادن، اول کتاب درسی را باز میکند و از آن نقل میآورد — دچار تأخیر هستید، ابتدا نوع ایندکس خود را بررسی کرده و دستورات SELECT خود را اصلاح کنید، پیش از آنکه به دنبال ارائهدهنده دیتابیس جدید بگردید. همانطور که پروژه Vibe-Memory نشان داد، بهینهسازی آنچه در اختیار دارید، اغلب مؤثرترین راه رسیدن به عملکرد مطلوب است. این پروژه در حال حاضر در گیتهاب بهصورت متنباز در دسترس است و به عنوان یک پیادهسازی عملی از این بهینهسازیها برای هر کسی که با «فراموشی هوش مصنوعی» میجنگد، عمل میکند. اما داستان سختافزاری این تحولات حتی شگفتانگیزتر است — به تحلیل ما دربارهی تأثیر GPUهای لبه بر سرعت استنتاج مراجعه کنید.




گفتگو