اگر دیتابیس شما میلیونها سطر داده دارد، تفاوت بین یک جستوجوی میلیثانیهای و چند دقیقهای، تنها در نحوه آدرسدهی دادهها روی دیسک است. در ۱۹ سپتامبر ۲۰۲۶، شرکت PlanetScale افزونه TIN (Text INdex) را برای پایگاهدادههای Postgres و Neki منتشر کرد که ادعا میکند با تغییر بنیادین در نحوه شناسایی اسناد، سرعتی «بهشدت خیرهکننده» ارائه میدهد.
بسیاری از توسعهدهندگان جستوجوی تماممتن را یک موازنه بین سرعت و پایداری میبینند. شما یا از ابزارهای داخلی مثل Postgres GIN استفاده میکنید که در پرسوجوهای پیچیده با کمبود حافظه مواجه میشوند، یا به موتورهای خارجی روی میآورید که دردسرهای همگامسازی دادهها را به همراه دارند. ابزار جدید PlanetScale قصد دارد با نگه داشتن منطق جستوجو در اکوسیستم Postgres و حذف گلوگاههای معماری، این مشکل را حل کند.
زمینه: الزامات یک ایندکس متنی مدرن
شرکت PlanetScale افزونه TIN را بر این باور ساخت که یک ایندکس متنی آماده برای محیط عملیاتی در Postgres باید مجموعهای جامع از ویژگیها را بدون به خطر انداختن پایداری دیتابیس پشتیبانی کند. بهطور خاص، TIN برای مدیریت موارد زیر طراحی شده است:
- منطق پرسوجوی پیچیده: پشتیبانی کامل از عبارات بولی (Boolean)، جستوجوی عبارتی (Phrase queries) و جستوجوهای بازهای (Span queries).
- تطبیق انعطافپذیر: قابلیت تطبیق تقریبی (Fuzzy matching)، جستوجو با کاراکترهای جایگزین (Wildcard) و تطبیق عبارات با استفاده از Regular Expression.
- نرمالسازی: دارای قابلیت داخلی برای Case folding (نادیده گرفتن حروف بزرگ و کوچک) و Accent folding (نادیده گرفتن علامتهای خاص روی حروف).
- رتبهبندی و تحلیل: پشتیبانی از پرسوجوهای
COUNT(*)و استخراج نتایج برتر (Top-k) که از طریق الگوریتم BM25 امتیازدهی میشوند.
فراتر از این ویژگیهای جستوجو، این افزونه باید با عملیات هسته دیتابیس ادغام شود. این شامل مدیریت Joinها، شروط پیچیده WHERE روی انواع مختلف ستونها، بهروزرسانیهای مداوم، تکثیر دادهها (Replication)، نسخهپذیری (Backups) و رعایت سختگیرانه دیدپذیری تراکنشها (Transaction visibility) است.
کاربردهای عملی در اپلیکیشنها
توسعهدهندگان میتوانند از TIN برای پیادهسازی قابلیتهای متنوع استفاده کنند. برای مثال، یک پلتفرم تجارت الکترونیک میتواند ۱۰ محصول برتر حاوی کلمات کلیدی خاص را با دستور زیر پیدا کند:SELECT * FROM products WHERE description ==> 'stretch denim jeans' ORDER BY tin.score(ctid) DESC LIMIT 10.
یک سامانه بررسی اسناد حقوقی (Legal Discovery) ممکن است نیاز داشته باشد تمام اسنادی را که حاوی هر یک از کلمات کلیدی یک مجموعه هستند، بدون رتبهبندی بازگرداند:SELECT * FROM emails WHERE body ==> '[insider trading conspiracy]'.
علاوه بر این، یک پلتفرم تگگذاری عکس میتواند تعداد دقیق عکسهای دارای یک تگ خاص را بهسرعت محاسبه کند:SELECT COUNT(*) FROM photos WHERE tags ==> '"san francisco"';.
بیشتر اپلیکیشنها همچنین به این توانایی نیاز دارند که در حالی که در حال جستوجو در ایندکس هستند، اسناد را درج، بهروزرسانی یا حذف کنند. در TIN، پرسوجوهای جستوجو باید نتایج را بر اساس سطرهایی که بهتازگی تغییر کرده یا درج شدهاند، به محض Commit شدن تراکنش، بازگردانند.
معماری سرعت: حذف لایه نگاشت
نوآوری اصلی TIN استفاده از ctid (شناسه فعلی تاپل) در Postgres بهعنوان شناسهی بومی سند است. در یک جدول استاندارد Postgres، هر سطر یک ctid دارد؛ عددی ۴۸ بیتی که مکان فیزیکی آن را روی صفحه دیسک مشخص میکند.
همانطور که در تحلیلهای قبلی ما درباره بهینهسازی لایههای ذخیرهسازی اشاره کردیم، حذف هر لایه واسط، تأخیر را کاهش میدهد. اکثر افزونههای جستوجو مثل ParadeDB یا pg_textsearch شناسههای ترتیبی (Sequential IDs) خودشان را به اسناد اختصاص میدهند. این کار باعث ایجاد «مشکل نگاشت» (Mapping problem) میشود؛ یعنی هر بار که جستوجویی نتیجه میدهد، سیستم باید ابتدا شناسهی داخلی را در یک جدول جداگانه جستوجو کند تا ctid واقعی را بیابد تا Postgres بتواند سطر را بازیابی کند. اگر یک جستوجو ۱۰ میلیون سطر را تطبیق دهد، این سیستمها باید ۱۰ میلیون عملیات جستوجو در جداول نگاشت ctid انجام دهند.
TIN این مرحله را بهطور کامل حذف میکند. با استفاده مستقیم از ctidها، دسترسی به هر سطر تطبیقیافته در زمان ثابت یا O(1) رخ میدهد. این یعنی حذف جداول عظیم نگاشت و حذف سربار CPU برای ترجمه میلیونها شناسه در طول پرسوجوهای بزرگ.
بیتمپهای برداری و بهینهسازی CPU
برای مدیریت پراکندگی شناسههای ۴۸ بیتی، TIN از یک سیستم کدگذاری بیتمپ دو سطحی استفاده میکند. یک ctid از یک شماره بلوک (صفحه) و یک آفست در آن بلوک تشکیل شده است. از آنجا که یک صفحه ۸ کیلوبایتی هرگز نمیتواند بیش از ۲۹۱ تاپل داشته باشد (۸۱۹۲ بایت منهای ۲۴ بایت برای هدر صفحه، تقسیم بر حداقل ۲۸ بایت برای هر تاپل غیرخالی) و در طرحهای متنی معمولاً ۳۲ تاپل یا کمتر دارد، TIN از استراتژی زیر بهره میبرد:
- بیتمپهای سطح صفحه: ردیابی میکنند که کدام صفحات ۸ کیلوبایتی حاوی یک عبارت هستند. چون یک صفحه در یک ثبات ۲۵۶ بیتی جا میشود، TIN از دستورات برداری AVX2 و AVX-512 برای انجام عملیات AND/OR روی هزاران صفحه بهطور همزمان استفاده میکند.
- بیتمپهای سطح آفست: پس از شناسایی صفحه، یک بیتمپ دوم و کوچکتر، تاپل دقیق را در آن صفحه مشخص میکند.
این رویکرد اجازه میدهد پرسوجوهای COUNT(*) بدون خواندن کامل لیستهای پستینگ (Postings lists) انجام شوند. اگر بیتمپهای سطح صفحه برای دو کلمه هیچ بیت مشترکی نداشته باشند، سیستم بهسادگی مجموع تعداد پستینگهای دقیق آنها را از متادیتای ایندکس میخواند.
فشردهسازی نیز بسیار بهینه است. عبارات با فرکانس بالا به ۱ بیت در هر پستینگ نزدیک میشوند، عبارات با فرکانس متوسط حدود ۷ بیت و عبارات نادر به ۲۵ بیت میرسند. عباراتی که تنها یک بار ظاهر شدهاند، اصلاً بهصورت بیتمپ ذخیره نمیشوند.
نبرد بنچمارک: TIN در برابر رقبا
شرکت PlanetScale افزونه TIN (نسخه v1.0.2) را در برابر یک مجموعه داده ۸۵ گیگابایتی از Stack Exchange حاوی ۱۵۰ میلیون سند آزمایش کرد. برای تولید ردپای پرسوجو (Query trace)، آنها زیررشتههایی بین ۲ تا ۱۵ عبارت را نمونهبرداری کردند و هر کدام را بهعنوان پرسوجوی عطفی (Conjunction)، انفصالی (Disjunction) یا عبارتی تفسیر کردند که در مجموع ۱,۷۱۹ پرسوجو شد.
آنها همچنین TIN را روی مجموعههای داده دیگر آزمایش کردند: تمام ویکیپدیا، مجموعهای ۲.۳ ترابایتی از کامنتهای Reddit و یک مجموعه ۷۹۷ گیگابایتی شامل مقالات پژوهشی دسترسی آزاد، اسناد حقوقی، کتابهای دامنه عمومی و ایمیلهای Enron.
محیط تست شامل یک نمونه AWS i7i.8xlarge EC2 با حافظه محلی NVMe و CPU پشتیبان AVX-512 بود. Postgres نسخه ۱۸.۶ در یک کانتینر محدود به ۸ vCPU و ۳۲ گیگابایت RAM اجرا شد تا محیطهایی شبیهسازی شود که ایندکس بهطور کامل در بافرها جا نمیشود.
برای ایجاد ترافیک، آنها از ParadeDB Benchmarker با معیارهای سفارشی برای بایتهای خوانده شده و بایتهای نوشته شده در WAL استفاده کردند. تنظیمات max_parallel_workers به ۸ (از ۴۰)، shared_buffers به ۲۴ گیگابایت (از ۱۲۸ مگابایت) و maintenance_work_mem به ۲۴ گیگابایت (از ۶۴ مگابایت) تغییر یافت.
جزئیات ساخت و حجم ایندکس
زمان ساخت و حجم ایندکسها در موتورهای مختلف متفاوت بود. در حالی که TIN در محدودیت ۳۲ گیگابایت RAM جا شد، سایر موتورها برای تکمیل ساخت به حافظه بیشتری نیاز داشتند:
- TIN: زمان ساخت ۸ دقیقه و ۱۰ ثانیه | حجم ۵۰.۷ گیگابایت | نیاز به ۳۲ گیگابایت RAM
- ParadeDB: زمان ساخت ۱۹ دقیقه و ۲۰ ثانیه | حجم ۵۲.۱ گیگابایت | نیاز به ۶۴ گیگابایت RAM
- pg_textsearch: زمان ساخت ۲۶ دقیقه و ۴۹ ثانیه | حجم ۴۱.۵ گیگابایت | نیاز به ۱۲۸ گیگابایت RAM
- Postgres GIN: زمان ساخت ۲ ساعت و ۹ دقیقه | حجم ۲۸ گیگابایت | نیاز به ۶۴ گیگابایت RAM
نتایج عملکرد
در یک بار کاری ترکیبی از پرسوجوهای عطفی، انفصالی و عبارتی با رتبهبندی BM25، TIN توانست ۲۵ برابر بیشتر از ParadeDB در هر ثانیه پرسوجو (QPS) را پردازش کند. تأخیر p99 در TIN برابر با ۲۵۶ میلیثانیه بود، در حالی که در ParadeDB این عدد به ۶,۷۶۵ میلیثانیه میرسید. GIN به دلیل اتمام حافظه در طول جستوجوهای انفصالی در این بنچمارک شکست خورد و pg_textsearch نیز به دلیل اینکه فقط از جستوجوهای انفصالی پشتیبانی میکند، حذف شد.
در مقایسه با ایندکس داخلی Postgres GIN برای پرسوجوهای عطفی و عبارتی، شکاف بسیار عمیقتر شد: TIN ۵۴۱ برابر بیشتر پرسوجو را پردازش کرد و تأخیر p99 آن ۱,۳۵۶ برابر کمتر بود (۲۱۲ میلیثانیه در مقابل ۲۸۸,۰۶۶ میلیثانیه).
در یک تست تخصصی با مجموعه داده ۸ گیگابایتی ویکیپدیا که در آن ایندکس بهطور کامل در بافرهای مشترک جا میشد، TIN به عدد خیرهکننده ۱۰,۲۶۰ QPS برای پرسوجوهای COUNT(*) انفصالی با تأخیر p99 تنها ۲ میلیثانیه دست یافت. در مقابل، ParadeDB به ۲۹۱ QPS (۹۵ میلیثانیه) و GIN به ۱.۴ QPS (۳۰,۲۹۲ میلیثانیه) رسید.
مدیریت نوشتنهای همزمان
ایندکسهای جستوجو معمولاً هنگام بهروزرسانی دادهها دچار کندی شدید میشوند. در تستی با یک کلاینت همزمان که ۱,۰۰۰ عملیات UPDATE در ثانیه ارسال میکرد، TIN عملکرد بالای خود را حفظ کرد در حالی که سایر ابزارها متوقف شدند:
- TIN: انجام ۲۷۰,۲۷۹ بهروزرسانی در ۱۰ دقیقه در حالی که ۱۲۵ پرسوجوی خواندن در ثانیه (p99 برابر با ۳۵۴ میلیثانیه) را پشتیبانی میکرد.
- ParadeDB: انجام ۱۸۵,۵۸۴ بهروزرسانی، اما افت شدید سرعت خواندن به ۲.۲ پرسوجو در ثانیه (p99 برابر با ۱۲,۶۳۴ میلیثانیه).
- pg_textsearch: تنها ۷۳۵ بهروزرسانی را تکمیل کرد. ترافیک خواندن مانع از کسب قفلهای لازم برای نوشتن شد و باعث شد عملیات نوشتن پس از چند ثانیه متوقف شود.
حل مشکل ادغام (Merge)
موتورهای جستوجوی سنتی ایندکسها را به بخشهای (Segments) مختلف تقسیم میکنند. هنگام ادغام دو بخش، آنها معمولاً مجبورند تمام شناسههای اسناد را دوباره شمارهگذاری کنند، زیرا شناسهی ۴۲ در بخش ۴ با شناسهی ۴۲ در بخش ۷ متفاوت است. این کار مستلزم بازبستهبندی، فشردهسازی مجدد و بازنویسی کل ایندکس است که منجر به Write Amplification بالا میشود.
اما چون TIN از ctidهای فیزیکی استفاده میکند، هیچ چیزی برای شمارهگذاری مجدد وجود ندارد. یک تاپل در صفحه ۱۹۰، آفست ۱۷، فارغ از اینکه در کدام بخش باشد، همانجا میماند. این به TIN اجازه میدهد مالکیت بیتمپها را روی دیسک بدون نیاز به فشردهسازی یا کپی مجدد منتقل کند و هزینههای I/O و CPU را بهشدت کاهش دهد.
سازگاری با MVCC و دیدپذیری
برای اطمینان از اینکه نتایج با مدل MVCC سازگار هستند، TIN با نقشه دیدپذیری (Visibility Map) در Postgres ادغام شده است تا از بازگرداندن تاپلهایی که برای تراکنش فعلی قابل مشاهده نیستند، جلوگیری کند.
- بررسیهای Heap: برای پرسوجوهایی که دادههای واقعی را بازمیگردانند (مثلاً
SELECT a, b, c)، TIN تاپل را از Heap میگیرد و Postgres دیدپذیری آن را تعیین میکند. - اسکنهای Index-Only: برای پرسوجوهای
COUNT(*)، اگر یک صفحه Heap به عنوان «کاملاً قابل مشاهده» علامتگذاری شده باشد، TIN تعداد را بدون لمس Heap بازمیگرداند. - بیتمپهای زنده بودن (Liveness Bitmaps): TIN یک بیتمپ زنده بودن برای هر بخش نگهداری میکند. وقتی
VACUUMیک ctid را از Heap حذف میکند، TIN بیت زنده بودن مربوطه را پاک میکند. در طول پرسوجوها، TIN بیتمپهای آفست را با این بیتمپ زنده بودن AND میکند تا تاپلهای حذف شده کنار گذاشته شوند.
این انتخاب معماری به این معناست که TIN فقط نتایج را سریعتر باز نمیگرداند، بلکه دادههای بسیار کمتری (مگابایت بر پرسوجو) از دیسک میخواند. برای مثال، در پرسوجوهای Top-10 ترکیبی، TIN مقدار ۶۵ مگابایت در هر پرسوجو خواند، در حالی که این عدد برای ParadeDB برابر با ۵۸۲ مگابایت بود.
تحلیل نهایی
برای یک توسعهدهنده، این تغییر باعث میشود جستوجوی تماممتن از یک «سرویس تخصصی» (مانند Elasticsearch) دوباره به دیتابیس اصلی بازگردد، بدون اینکه جریمههای معمول عملکردی را متحمل شود. انتقال به ایندکسگذاری بومی ctid یک بهرهبرداری هوشمندانه از فیزیک داخلی Postgres است. PlanetScale با همراستا کردن ساختار ایندکس با چیدمان فیزیکی Heap، عملاً یک مشکل نگاشت نرمافزاری را به یک مسئله شتابدهی سختافزاری تبدیل کرده است.
این رویکرد در راستای بهینهسازی بازیابی دادههاست، مشابه آنچه در استراتژیهای جدید بازیابی داده در Amazon Bedrock برای کاهش تأخیر در سیستمهای سازمانی مشاهده میکنیم. اگر این بنچمارکها در مقیاس پتابایت ثابت بمانند، نیاز به کلاسترهای جستوجوی مجزا برای اپلیکیشنهای متوسط ممکن است بهطور کامل از بین برود و زیرساخت سادهتر و تأخیر دادهها کمتر شود.
برای تست این قابلیت روی دادههای خود، اکنون میتوانید یک ایندکس TIN را با این دستور پیاده کنید:CREATE INDEX index_name ON table_name USING tin(column_name);.




گفتگو