PostgreSQL ফুল-টেক্সট সার্চ: কখন এটি আপনার অ্যাপে যথেষ্ট
PostgreSQL full-text search অনেক অ্যাপের জন্য যথেষ্ট হতে পারে। সহজ সিদ্ধান্ত নিয়ম, স্টার্টার কুয়েরি এবং ইনডেক্সিং চেকলিস্ট ব্যবহার করে জানুন কখন আলাদা সার্চ ইঞ্জিন যোগ করা দরকার।

বাস্তব অ্যাপে “যথেষ্ট সার্চ” কেমন লাগে
অধিকাংশ মানুষ "ফুল-টেক্সট সার্চ" চায় না—তারা একটি সার্চ বক্স চায় যা দ্রুত অনুভূত হয় এবং প্রথম পৃষ্ঠাতেই তারা যা চেয়েছিলো সেটি খুঁজে পায়। যদি ফলাফল ধীর, গোলমেলে, বা অদ্ভুতভাবে সাজানো হয়, ব্যবহারকারীরা কেয়ার করে না আপনি Postgres ব্যবহার করেছেন কি না অথবা আলাদা ইঞ্জিন। তারা কেবল সার্চে আস্থা হারিয়ে ফেলে।
এটি এক ধরনের সিদ্ধান্ত: সার্চ কি Postgres-ই রাখতে হবে, না কি ডেডিকেটেড সার্চ ইঞ্জিন যুক্ত করা উচিত। লক্ষ্য পারফেকশন নয়—বরং এমন একটি শক্তিশালী বেসলাইন যা দ্রুত ডেলিভারি করা যায়, চালানো সহজ এবং আপনার অ্যাপের ব্যবহার অনুযায়ী যথেষ্ট ভাল।
অনেক অ্যাপের জন্য PostgreSQL full-text search দীর্ঘ সময়ের জন্য যথেষ্ট। যদি আপনার কয়েকটি টেক্সট ফিল্ড থাকে (title, description, notes), বেসিক র্যাঙ্কিং এবং একটি-দুইটি ফিল্টার (status, category, tenant), তাহলে Postgres অতিরিক্ত ইনফ্রাস্ট্রাকচার ছাড়াই হ্যান্ডেল করতে পারে। এর ফলে কম মোবাইল অংশ, সহজ ব্যাকআপ এবং কম "কেন সার্চ ডাউন কিন্তু অ্যাপ চলছে?" ধরণের ঘটনা দেখা যায়।
“যথেষ্ট” মানে সাধারণত একই সময়ে তিনটি লক্ষ্য পূরণ করতে পারা:
- গুণমান: সাধারণ কুয়েরির ক্ষেত্রে সঠিক আইটেমগুলো শীর্ষে আসে এবং স্পষ্ট মিল গুলো মিস হয় না।
- গতি: স্বাভাবিক লোডে ফলাফল দ্রুত আসে (শুধু আপনার ল্যাপটপে নয়)।
- অপারেশনাল পরিশ্রম: আপনার টিম এটি বজায় রাখতে পারে কোনো দ্বিতীয় ক্লাস্টার, অতিরিক্ত মনিটরিং বা আলাদা ইনডেক্সিং পাইপলাইন ছাড়া।
একটি কংক্রিট উদাহরণ: একটি SaaS ড্যাশবোর্ড যেখানে ব্যবহারকারীরা প্রজেক্ট নাম ও নোট দ্বারা সার্চ করে। যদি "onboarding checklist" ধাঁচের কুয়েরি যথাযথ প্রজেক্টকে টপ 5-এর মধ্যে, এক সেকেন্ডের কম সময়ে ফিরিয়ে দেয় এবং আপনি বারবার অ্যানালাইজার টিউন বা রিইনডেক্সিং করছেন না, সেটাই "যথেষ্ট"। যখন আপনি ওই লক্ষ্যগুলো পূরণ করতে ক পরিমাণ জটিলতা বাড়াতে হচ্ছিল, তখনই "built-in search vs search engine" বাস্তবে গুরুত্বপূর্ণ প্রশ্ন হয়ে দাঁড়ায়।
সাধারণ সার্চ চাহিদা এবং তারা আসলে কি ইঙ্গিত দেয়
টিমগুলো প্রায়শই ফিচার হিসেবে সার্চ বর্ণনা করে, আউটকাম নয়। কার্যকর পদ্ধতি হলো প্রতিটি ফিচারকে এভাবে অনুবাদ করা যে সেটি বানাতে, টিউন করতে এবং বিশ্বাসযোগ্য রাখতে কত খরচ হবে।
শুরুতেই অনুরোধগুলো সাধারণত শুনতে এমন: টাইপো সহনশীলতা, ফ্যাসেট ও ফিল্টার, হাইলাইটস, “স্মার্ট” র্যাঙ্কিং, এবং অটোকমপ্লিট। প্রথম সংস্করণের জন্য অবশ্যকতা আর নাএর মধ্যে আলাদা করুন। একটি বেসিক সার্চ বক্স সাধারণত কেবল প্রাসঙ্গিক আইটেম খুঁজে পেতে হবে, সাধারণ শব্দরূপ (প্লুরাল, টেনস) হ্যান্ডেল করতে হবে, সরল ফিল্টার মেনে চলতে হবে এবং টেবিল বাড়ার সাথেও দ্রুত থাকা উচিত। ঠিক এখানেই PostgreSQL full-text search ঠিকঠাক ফিট করে।
Postgres উজ্জ্বল যখন আপনার কন্টেন্ট স্বাভাবিক টেক্সট ফিল্ডে থাকে এবং আপনি ডেটার কাছে সার্চ রাখতে চান: হেল্প আর্টিকেল, ব্লগ পোস্ট, সাপোর্ট টিকিট, ইন্টারনাল ডকুমেন্টস, পণ্য শিরোনাম ও বর্ণনা, অথবা কাস্টমার রেকর্ডের নোট। এগুলো বেশিরভাগই “সঠিক রেকর্ডটা খুঁজে পাও” সমস্যা, না যে “একটি সার্চ প্রোডাক্ট বানাতে হবে” সমস্যা।
নিস-টু-হ্যাভেস সেই জায়গা যেখানে জটিলতা ক্রিপ করে আসে। টাইপো সহনশীলতা ও সমৃদ্ধ অটোকমপ্লিট সাধারণত অতিরিক্ত টুলিং-এর দিকে ঠেলে দেয়। ফ্যাসেটগুলি Postgres-এ সম্ভব, কিন্তু যদি আপনি অনেক ফ্যাসেট, গভীর এনালিটিক্স এবং বিশাল ডেটাসেটে ইনস্ট্যান্ট কাউন্ট চান, তখন ডেডিকেটেড ইঞ্জিন আরও আকর্ষণীয় লাগে।
গোপন খরচ সাধারণত লাইসেন্স ফি নয়। সেটা হলো দ্বিতীয় সিস্টেম। একবার আপনি সার্চ ইঞ্জিন যোগ করলে, আপনাকে ডাটা সিংকিং ও ব্যাকফিল (এবং যেগুলো বাগ তৈরি করে), মনিটরিং ও আপগ্রেড, “কেন সার্চ পুরানো ডাটা দেখাচ্ছে?” ধরণের সাপোর্ট কাজ, এবং দুই সেট রিলেভ্যান্স নোবের পরিচালনা যোগ করতে হবে।
যদি অনিশ্চিত হন, Postgres দিয়ে শুরু করুন, কিছু সহজ শিপ করুন, এবং কেবল তখনই অন্য ইঞ্জিন যোগ করুন যখন একটি স্পষ্ট চাহিদা মেটতে না পারে।
একটি সহজ সিদ্ধান্ত নিয়ম: বিল্ট-ইন সার্চ বনাম ডেডিকেটেড ইঞ্জিন
তিন-চেক নিয়ম ব্যবহার করুন। আপনি যদি সব তিনটি পাস করেন, PostgreSQL full-text search রাখুন। যদি একটায়ই খুব খারাপভাবে ফেল করেন, ডেডিকেটেড সার্চ ইঞ্জিন বিবেচনা করুন।
তিনটি চেক
-
রিলেভ্যান্স চাহিদা: “ভাল-ই” ফলাফল গ্রহণযোগ্য নাকি আপনি প্রায়-পারফেক্ট র্যাঙ্কিং চান বহু এজ-কেস (টাইপো, সাইনোনিম, “লোকেরা আরো কি সার্চ করেছে”, পার্সোনালাইজড ফলাফল)? যদি মাঝে মাঝে অদক্ষ অর্ডারিং মেনে নিতে পারেন, Postgres সাধারণত কাজ করে।
-
কুয়েরি ভলিউম ও লেটেন্সি: পিক-এ কতটি সার্চ/সেকেন্ড আশা করছেন, এবং বাস্তব লেটেন্সি বাজেট কত? যদি সার্চ ট্রাফিক ছোট অংশ এবং ঠিক ইনডেক্সিং দিয়ে কুয়েরি দ্রুত রাখা যায়, Postgres ঠিক আছে। যদি সার্চ প্রধান ওয়ার্কলোড হয়ে উঠে এবং কোর রিড/রাইটের সাথে প্রতিযোগিতা শুরু করে, সেটা সতর্ক সংকেত।
-
জটিলতা: আপনি কি এক বা দুই টেক্সট ফিল্ড সার্চ করছেন, না কি অনেক সিগনাল (ট্যাগস, ফিল্টার, টাইম ডিসে, পপুলারিটি, পারমিশন) ও একাধিক ভাষা মিলিয়ে? লজিক যত জটিল হবে, SQL-এ তত বেশি ঘর্ষণ অনুভব করবেন।
নিরাপদ শুরু: Postgres-এ একটি বেসলাইন শিপ করুন, ধীর কুয়েরি ও “কোন ফলাফল নেই” কেস লগ করুন, এবং তারপর সিদ্ধান্ত নিন। অনেক অ্যাপ কখনোই বড় হয়ে উঠেনা, এবং আপনি অপ্রয়োজনীয়ভাবে দ্বিতীয় সিস্টেম চালানোর ঝামেলা এড়িয়ে যান।
লাল পতাকা যা সাধারণত ডেডিকেটেড ইঞ্জিনের দিকে নির্দেশ করে:
- আপনাকে উচ্চ মানের টাইপো, সাইনোনিম বা অটোকমপ্লিট হ্যান্ডল করতে হবে।
- আপনি অনেক ফিল্ড জুড়ে ফ্যাসেটিং ও জটিল ফিল্টার চান।
- সার্চ ট্র্যাফিক এত বেশি যে এটি প্রাইমারি DB-কে চাপ দেয়।
- বহু ভাষায় রিলেভ্যান্স দরকার যেখানে কেবল কয়েকটি ডিকশনারি কার্যকর হবে না।
- উন্নত র্যাংকিং ফিচার দরকার (learning-to-rank, ক্লিক ফিডব্যাক)।
সবুজ পতাকা যা Postgres রাখার পরামর্শ দেয়:
- সার্চ হলো “ঠিক পেজ খুঁজে পাও”, না “কনটেন্ট ডিস্কভার করা”।
- অপারেশন ও রিলায়িবিলিটির জন্য একটি ডেটাবেস রাখা গুরুত্বপূর্ণ।
- আপনার কন্টেন্ট কয়েকটি কলামে ভাল মাপিয়ে যায় (title, body, tags)।
- আপনি সহজ রিলেভ্যান্স টিউনিং গ্রহণ করতে পারেন ও ধীরে ধীরে ইটারেট করতে চান।
- আপনার টিম কম মুভিং পার্টস চায় (কোন সিঙ্ক পাইপলাইন, কোন ডুয়াল রাইট নেই)।
PostgreSQL full-text search কিভাবে কাজ করে (প্রয়োজনীয় জিনিসগুলোই)
PostgreSQL full-text search হলো একটি বিল্ট-ইন উপায় টেক্সটকে এমন কিছুতে পরিণত করার যার উপর ডাটাবেস দ্রুত সার্চ করতে পারে, প্রতিটি রো স্ক্যান না করেই। এটি সবচেয়ে ভালো কাজ করে যখন আপনার কনটেন্ট ইতিমধ্যেই Postgres-এ থাকে এবং আপনি দ্রুত, সন্তোষজনক সার্চ চান যার অপস প্রেডিক্টেবল।
জানার মতো তিনটি অংশ আছে:
tsvector: আপনার টেক্সটের “সার্চেবল ফর্ম”। Postgres টেক্সটকে টোকেন করে, নরমালাইজ করে, এবং ফলাফল সংরক্ষণ করে।tsquery: ব্যবহারকারী যা খুঁজছে, একটি স্ট্রাকচার্ড ভাবে (শব্দ, AND/OR, ফ্রেজ-সদৃশ অপারেটর)।- র্যাঙ্কিং: একটি স্কোর যা ফলাফলগুলোকে অর্ডার করতে সাহায্য করে। আপনি
ts_rank(বাts_rank_cd) ব্যবহার করতে পারেন বেশি প্রাসঙ্গিক রো উপরে আনার জন্য।
ভাষা কনফিগারেশন গুরুত্বপূর্ণ কারণ এটি Postgres কীভাবে শব্দগুলোকে বিবেচনা করে তা পরিবর্তন করে। সঠিক কনফিগ থাকলে “running” এবং “run” ম্যাচ করবে (stemming), এবং সাধারণ ফিলার শব্দগুলো উপেক্ষা করা হবে (stop words)। ভুল কনফিগ থাকলে সার্চ ভাঙা মনে হতে পারে কারণ সাধারণ ইউজার শব্দাবলি আরindexed শব্দের সাথে মেলে না।
প্রিফিক্স ম্যাচিং হল সেই ফিচার যা মানুষ টাইপ-এহেড-ধাঁচের আচরণ চাইলে খোঁজে, যেমন “dev” কে “developer” সাথে মিলানো। Postgres FTS-এ এটি সাধারণত একটি প্রিফিক্স অপারেটর দিয়ে করা হয় (উদাহরণ term:*)। এটা অনুভূত মান উন্নত করতে পারে, কিন্তু প্রায়শই কুয়েরির কাজ বাড়ায়—তাই এটি ডিফল্ট নয় বরং একটি বিকল্প উন্নয়ন হিসেবে বিবেচনা করুন।
Postgres যা হতে চায় না: একটি পূর্ণ সার্চ প্ল্যাটফর্ম যেখানে প্রতিটি ফিচার আছে। যদি আপনি fuzzy বানান সংশোধন, উন্নত অটোকমপ্লিট, learning-to-rank, ফিল্ড-ভিত্তিক জটিল analyzers, বা বহু-নোডে ভাঙা ইনডেক্সিং চান, তখন আপনি বিল্ট-ইন কমফোর্ট জোনের বাইরে। তবু অনেক অ্যাপের জন্য PostgreSQL full-text search ব্যবহারকারীরা যা আশা করে তার বেশিরভাগই কম মুভিং পার্টস রেখে দিতে পারে।
কপি করে ব্যবহার করার জন্য স্টার্টার কুয়েরি
নীচে একটি ছোট, বাস্তবসম্মত কাঠামো সে কন্টেন্টের জন্য যা আপনি সার্চ করতে চান:
-- Minimal example table
CREATE TABLE articles (
id bigserial PRIMARY KEY,
title text NOT NULL,
body text NOT NULL,
updated_at timestamptz NOT NULL DEFAULT now()
);
PostgreSQL full-text search-এর জন্য একটি ভাল বেসলাইন হলো: ব্যবহারকারী যা টাইপ করেছে তা থেকে একটি কুয়েরি তৈরি করুন, প্রথমে রো ফিল্টার করুন (যতটা সম্ভব), তারপর বাকি ম্যাচগুলো র্যাঙ্ক করুন।
-- $1 = user search text, $2 = limit, $3 = offset
WITH q AS (
SELECT websearch_to_tsquery('english', $1) AS query
)
SELECT
a.id,
a.title,
a.updated_at,
ts_rank_cd(
setweight(to_tsvector('english', coalesce(a.title, '')), 'A') ||
setweight(to_tsvector('english', coalesce(a.body, '')), 'B'),
q.query
) AS rank
FROM articles a
CROSS JOIN q
WHERE
a.updated_at >= now() - interval '2 years' -- example safe filter
AND (
setweight(to_tsvector('english', coalesce(a.title, '')), 'A') ||
setweight(to_tsvector('english', coalesce(a.body, '')), 'B')
) @@ q.query
ORDER BY rank DESC, a.updated_at DESC, a.id DESC
LIMIT $2 OFFSET $3;
কিছু বিশদ জিনিস যা পরে সময় বাঁচায়:
- সস্তা ফিল্টারগুলো
WHERE-তে রাখুন (status, tenant_id, date ranges)। আপনি কম রো র্যাঙ্ক করবেন, তাই তা দ্রুত থাকে। ORDER BY-এ এক ধরণের টাই-ব্রেকার যোগ করুন (যেমনupdated_at, তারপরid)। এতে পেজিনেশন স্থিতিশীল থাকে যখন অনেক রেজাল্ট একই র্যাঙ্ক পায়।- ব্যবহারকারীর ইনপুটের জন্য
websearch_to_tsqueryব্যবহার করুন। এটি কোট ও সহজ অপারেটরগুলো মানুষের প্রত্যাশা মতো হ্যান্ডেল করে।
একবার এই বেসলাইন কাজ করলে, to_tsvector(...) এক্সপ্রেশনটিকে একটি স্টোরড কলামে সরান। এতে প্রতিটি কুয়েরিতে এটি পুনর্গণনা হওয়া থেকে রক্ষা পাবে এবং ইনডেক্স করা সহজ হবে।
ইনডেক্সিং সেটআপ যা সাধারণত ফল দেয়
অধিকাংশ “PostgreSQL full-text search ধীর” কাহিনি মূলত এক জিনিসেই নেমে আসে: ডাটাবেস প্রতিটি কুয়েরিতে সার্চ ডকুমেন্ট বানাচ্ছে। প্রথমে সেটা ঠিক করে নিন—একটি প্রি-বিল্ট tsvector সংরক্ষণ করে এবং সেটিতে ইনডেক্স তৈরি করুন।
tsvector সংরক্ষণ: generated column না ট্রিগার?
যখন আপনার সার্চ ডকুমেন্ট একই সারির কলাম থেকে তৈরি হয় তখন generated column সবচেয়ে সহজ অপশন। এটি আপডেটে স্বয়ংক্রিয়ভাবে সঠিক থাকে এবং ভুলে যাওয়া কঠিন।
ডকুমেন্ট যদি সম্পর্কিত টেবিলগুলোর উপর নির্ভর করে (উদাহরণ: একটি প্রোডাক্ট রো এবং তার ক্যাটাগরি নাম), অথবা যদি আপনি কাস্টম লজিক চান যা এক এক্সপ্রেশনে সহজে প্রকাশ করা যায় না, তখন ট্রিগার-মেইন্টেইন্ড tsvector ব্যবহার করুন। ট্রিগারগুলো মুভিং পার্টস যোগ করে, তাই সেগুলোকে ছোট রাখুন এবং টেস্ট করুন।
প্রায়ই যেই ইনডেক্সটি চান
tsvector কলামের উপর একটি GIN ইনডেক্স তৈরি করুন। সেটাই বেসলাইন যা PostgreSQL full-text search-কে সাধারণ অ্যাপ সার্চের জন্য মুহূর্তের মত দ্রুত করে তোলে।
বহু অ্যাপের জন্য কাজের একটি সেটআপ:
- সার্চেবল
tsvectorএকই টেবিলেই রাখুন যা আপনি বেশি করে সার্চ করেন। - ঐ
tsvector-এ GIN ইনডেক্স যোগ করুন। - নিশ্চিত করুন আপনার কুয়েরি সংরক্ষিত
tsvector-এর বিরুদ্ধে@@ব্যবহার করে,to_tsvector(...)অন-দ্য-ফ্লাই না করে। - বড় ব্যাকফিলের পরে
VACUUM (ANALYZE)বিবেচনা করুন যাতে প্ল্যানার নতুন ইনডেক্স বুঝে নিতে পারে।
ভেক্টর একই টেবিলে রাখা সাধারণত দ্রুত ও সহজ। আলাদা সার্চ টেবিল তখন বোধগম্য হতে পারে যখন বেস টেবিল খুব লিখন-ভারী, অথবা আপনি অনেক টেবিল জুড়ে সম্মিশ্র ডকুমেন্ট ইনডেক্স করছেন এবং নিজের সময়সূচি মতে আপডেট করতে চান।
পার্শিয়াল ইনডেক্সগুলো সাহায্য করতে পারে যখন আপনি কেবল একটি সাবসেট সার্চ করেন, যেমন status = 'active', একক টেন্যান্ট, বা নির্দিষ্ট ভাষা। এগুলো ইনডেক্স সাইজ কমায় এবং সার্চ দ্রুত করতে পারে—কিন্তু কেবল যদি আপনার কোয়েরি সবসময় একই ফিল্টার অন্তর্ভুক্ত করে।
অতিরিক্ত জটিলতা ছাড়া গ্রহণযোগ্য রিলেভ্যান্স পাওয়া
আপনি যদি রিলেভ্যান্স নিয়মগুলো সরল ও প্রেডিক্টেবল রাখেন, PostgreSQL full-text search দিয়ে চমকে যাওয়ার মত ভালো ফল পেতে পারেন।
সহজতম জয় হলো ফিল্ড ওয়েটিং: শিরোনামে ম্যাচ বডির গভীরে ম্যাচের চেয়ে বেশি গণ্য হওয়া উচিত। একটি সম্মিশ্র tsvector তৈরি করুন যেখানে title-কে বেশি ওয়েট দেয়া হয়, তারপর ts_rank বা ts_rank_cd দিয়ে র্যাঙ্ক করুন।
যদি আপনি “তাজা” বা “জনপ্রিয়” আইটেমকে উপরে আনতে চান, সেটি সাবধানে করুন। একটি ছোট বুস্ট ঠিক আছে, কিন্তু সেটা টেক্সট রিলেভ্যান্স ওভাররাইট করে না। প্রায়োগিক প্যাটার্ন: প্রথমে টেক্সট-বাই টেক্সট র্যাঙ্ক, তারপর টাই-ব্রেকার হিসেবে রিসেন্সি ব্যবহার করুন, অথবা এমন একটি ক্যাপড বোনাস যোগ করুন যাতে অপ্রাসঙ্গিক নতুন আইটেম একটি পুরানো একেবারে উপযুক্ত ম্যাচকে টপ না করে।
সাইনোনিমস ও ফ্রেজ ম্যাচিং আশা যেখানে প্রায়ই বিভ্রান্তি হয়। সাইনোনিমস স্বয়ংক্রিয় নয়; আপনি কেবল একটি থিসরাস বা কাস্টম ডিকশনারি যোগ করলে পাবেন, অথবা কুয়েরির টার্ম নিজেই প্রসারিত করবেন (উদাহরণ: “auth” কে “authentication” হিসেবে বিবেচনা করা)। ফ্রেজ ম্যাচিংও ডিফল্ট নয়: সাধারণ কুয়েরি শব্দগুলোকে যেকোন জায়গায় ম্যাচ করে, না যে “এই ঠিক ফ্রেজ” হিসাবে। যদি ব্যবহারকারীরা কোটেড ফ্রেজ বা দীর্ঘ প্রশ্ন টাইপ করে, তাহলে phraseto_tsquery বা websearch_to_tsquery বিবেচনা করুন যাতে সার্চ লোকেরা কিভাবে সার্চ করে তার সাথে ভাল মেলে।
মিশ্র-ভাষার কনটেন্টে একটি সিদ্ধান্ত দরকার। যদি আপনি প্রতিটি ডকুমেন্টের ভাষা জানেন, সেটি স্টোর করুন এবং সঠিক কনফিগ দিয়ে tsvector জেনারেট করুন (English, Russian ইত্যাদি)। যদি না জানেন, একটি নিরাপদ বিকল্প হলো simple কনফিগ দিয়ে ইনডেক্স করা (কোনো stemming নেই), অথবা দুইটি ভেক্টর রাখা: জানা হলে ভাষা-নির্দিষ্ট একটি, এবং সবার জন্য একটি simple।
রিলেভ্যান্স যাচাই করতে, ছোট ও কংক্রিট রাখুন:
- 10–20টি বাস্তব কুয়েরি সংগ্রহ করুন (অথবা সাপোর্ট চ্যাট থেকে)।
- টপ 1–3 ফলাফল লিখে রাখুন যা শীর্ষে থাকা উচিত।
- প্রতিটি টিউন পরিবর্তনের পরে এগুলো চালান এবং নোট করুন কী ভালো বা খারাপ হয়েছে।
- যখন এটি আপনার অ্যাপের জন্য ঠিকঠাক লাগে তখন থামুন।
এইটি সাধারণত Postgres FTS-কে “টেমপ্লেট”, “ডকস” বা “প্রজেক্ট” ধাঁচের অ্যাপ সার্চ বক্সে যথেষ্ট করে।
সাধারণ ভুল যা Postgres সার্চকে খারাপ করে দেখায়
অধিকাংশ “PostgreSQL full-text search ধীর বা অপ্রাসঙ্গিক” গল্প কয়েকটি এড়ানো যোগ্য ভুল থেকেই আসে। এগুলো ঠিক করা সাধারণত একটি নতুন সার্চ সিস্টেম যোগ করার চেয়ে সহজ।
একটি সাধারণ ফাঁদ হলো tsvector-কে একটি গণিতযোগ্য মান মনে করা যা নিজে থেকেই সঠিক থাকে। আপনি যদি tsvector একটি কলামে সংরক্ষণ করে রাখেন কিন্তু প্রতিটি ইনসার্ট ও আপডেটে তা আপডেট না করেন, ফলাফল র্যান্ডম দেখাবে কারণ ইনডেক্স আর টেক্সটের সাথে মেলে না। যদি আপনি to_tsvector(...) অন-দ্য-ফ্লাই কুয়েরিতে গণনা করেন, ফলাফল সঠিক থাকতে পারে কিন্তু ধীর হয়ে যায় এবং ইনডেক্সের সুবিধা মিস করবেন।
আরেকটি সহজভাবে পারফরম্যান্স নষ্ট করার উপায় হলো র্যাঙ্কিং করার আগে ক্যান্ডিডেট সেটকে সংকুচিত না করা। ts_rank দরকারী, কিন্তু সাধারণত এটি তখন চালানো উচিত যখন Postgres ইনডেক্স ব্যবহার করে ম্যাচিং রো খুঁজে এনে দিয়েছে। যদি আপনি হাজার হাজার রো-র জন্য র্যাঙ্ক ক্যালকুলেট করে তারপর পরিত্যাগ করেন, আপনি দ্রুত সার্চকে টেবিল-স্ক্যানে পরিণত করতে পারেন।
লোকজন প্রায়শই আশা করে “contains” সার্চ LIKE '%term%' মত আচরণ করবে। লিডিং ওয়াইল্ডকার্ডগুলি FTS-এ ভাল মাপেনা কারণ FTS শব্দ (lexemes)-ভিত্তিক, নয় যে যেকোন সাবস্ট্রিং। যদি আপনি সাবস্ট্রিং সার্চ চান (প্রোডাক্ট কোড বা পার্শিয়াল আইডি), সেই কেসে আলাদা উপায় (যেমন trigram ইনডেক্সিং) ব্যবহার করুন।
পারফরম্যান্স ইস্যুগুলোর বেশিরভাগই মিল এবং রেজাল্ট হ্যান্ডলিং থেকে আসে, ম্যাচিং থেকে নয়। লক্ষ্য করতে হবে:
- বড়
OFFSETপেজিনেশন, যা পেজ বাড়ার সাথে সাথে Postgres-কে আরো রো স্কিপ করায়। - অপ্রতিবন্ধিত রিজাল্ট সেট, যেখানে কুয়েরি তিসলাই হাজার হাজার রো ফিরিয়ে দেয়।
অপারেশনাল বিষয়ও গুরুত্বপূর্ণ। অনেক আপডেটের পরে ইনডেক্স-ব্লোট তৈরি হতে পারে, এবং সময়মত রিইনডেক্স না করলে ব্যয়বহুল হতে পারে। পরিবর্তনের আগে ও পরে বাস্তব কুয়েরি টাইম পরিমাপ করুন (এবং EXPLAIN ANALYZE চেক করুন)। সংখ্যাবিহীন হলে সহজেই ভুল “ফিক্স” করে আপনি Postgres FTS-কে অন্যভাবে খারাপ করতে পারেন।
দ্রুত চেকলিস্ট: কুয়েরি ও ইনডেক্সিং স্বাস্থ্য পরীক্ষা
আপনি PostgreSQL full-text searchকে দোষারোপ করার আগে এই পরীক্ষাগুলো চালান। বেশিরভাগ সমস্যা মুলত মৌলিক বিষয়গুলোর অভাব থেকে, ফিচারের কারণে নয়।
ডেটা + ইনডেক্স স্যানিটি চেক
বাস্তব tsvector তৈরি করুন: সেটা generated অথবা maintained কলামে রাখুন (প্রতি কুয়েরিতে গণনা করবেন না), সঠিক ভাষা কনফিগ ব্যবহার করুন (english, simple ইত্যাদি), এবং ফিল্ড মিশালে ওয়েট ব্যবহার করুন (title > subtitle > body)।
আপনি যা ইনডেক্স করেন তা নরমালাইজ করুন: গোলমেলে ফিল্ড (IDs, বয়লারপ্লেট, ন্যাভিগেশন টেক্সট) tsvector থেকে বাদ দিন, এবং বড় ব্লবগুলো ট্রিম করুন যদি ব্যবহারকারীরা কখনো তা সার্চ না করে।
ঠিক ইনডেক্স তৈরি করুন: tsvector কলামে GIN ইনডেক্স যোগ করুন এবং EXPLAIN-এ নিশ্চিত করুন এটি ব্যবহার হচ্ছে। যদি কেবল একটি সাবসেট searchable (উদাহরণ status = 'published'), একটি partial index সাইজ কমায় এবং রিড দ্রুত করে।
টেবিলকে সুস্থ রাখুন: dead tuples ইনডেক্স স্ক্যানকে ধীর করে। নিয়মিত vacuuming জরুরি, বিশেষ করে ঘন আপডেটেড কন্টেন্টে।
একটি reindex পরিকল্পনা রাখুন: বড় মাইগ্রেশন বা ব্লটেড ইনডেক্স gelegentlich reindex দরকার হতে পারে—নিয়ন্ত্রিত উইন্ডোতে করুন।
ডেটা ও ইনডেক্স ঠিক থাকলে এখন কুয়েরি শেইপে ফোকাস করুন। PostgreSQL full-text search দ্রুত যখন এটি প্রাথমিকভাবে ক্যান্ডিডেট সেট সংকুচিত করতে পারে।
কুয়েরি + রানটাইম স্যানিটি চেক
প্রথমে ফিল্টার করুন, তারপর র্যাঙ্ক: কঠোর ফিল্টার (tenant, language, published, category) র্যাঙ্কিংয়ের আগে অ্যাপ্লাই করুন। হাজার হাজার রো র্যাঙ্ক করা যা পরে বাতিল করবেন—এটা অপচয়।
স্থিতিশীল অর্ডারিং ব্যবহার করুন: র্যাঙ্ক এবং তারপর একটি টাই-ব্রেকার যেমন updated_at বা id দিয়ে অর্ডার করুন যাতে রিফ্রেশের সময় ফলাফল কাফসাঁসল না করে।
“কুয়েরি সবকিছু করে” এড়িয়ে চলুন: যদি আপনি ফাজি ম্যাচিং বা টাইপো সহনশীলতা চান, সেটা ইচ্ছাকৃতভাবে করুন (এবং পরিমাপ করুন)। দুর্ঘটনাক্রমে sequential scans বাধ্য করুন না।
বাস্তব কুয়েরি পরীক্ষা করুন: শীর্ষ 20 টি সার্চ সংগ্রহ করুন, ম্যানুয়ালি রিলেভ্যান্স চেক করুন, এবং একটি ছোট প্রত্যাশিত-ফলাফল তালিকা রাখুন রিগ্রেশন ধরতে।
নির্দিষ্ট ধীর পথগুলো দেখুন: ধীর কুয়েরি লগ করুন, EXPLAIN (ANALYZE, BUFFERS) রিভিউ করুন, এবং ইনডেক্স সাইজ ও ক্যাশ হিট রেট মনিটর করুন যাতে বৃদ্ধির ফলে আচরণ কিভাবে বদলে যায় তা দেখতে পারেন।
উদাহরণ পরিস্থিতি: বেসিক সাইট সার্চ থেকে বাড়তে থাকা চাহিদা
একটি SaaS হেল্প সেন্টার একটি ভাল শুরু—লক্ষ্য সহজ: মানুষকে সেই আর্টিকেলটি খুঁজে দিতে যা তাদের প্রশ্নের উত্তর দেয়। আপনার কাছে কয়েক হাজার আর্টিকেল, প্রতিটির একটি শিরোনাম, শর্ট সামারি এবং বডি টেক্সট আছে। বেশিরভাগ ভিজিটর 2–5 শব্দ টাইপ করে যেমন “reset password” অথবা “billing invoice”।
PostgreSQL full-text search দিয়ে এটি খুব দ্রুত সমাধান মনে হতে পারে। আপনি সমন্বিত ফিল্ডগুলোর জন্য একটি tsvector সংরক্ষণ করেন, একটি GIN ইনডেক্স যোগ করেন, এবং রিলেভ্যান্স দ্বারা র্যাঙ্ক করেন। সাফল্য অর্থাৎ: ফলাফল 100 ms-এর মধ্যে আসে, শীর্ষ 3 ফলাফল সাধারণত সঠিক, এবং আপনাকে সিস্টেম বারবার দেখভাল করতে হয় না।
তারপর প্রোডাক্ট বড় হয়। সাপোর্ট চায় ফিল্টার করতে product area, platform (web, iOS, Android), এবং plan (free, pro, business)। ডকস রাইটাররা সাইনোনিম, “did you mean”, এবং টাইপো হ্যান্ডলিং চায়। মার্কেটিং চায় “শূন্য ফলাফল সহ শীর্ষ সার্চ” ধরনের অ্যানালিটিক্স। ট্রাফিক বাড়ে এবং সার্চ সবচেয়ে ব্যস্ত এন্ডপয়েন্টগুলোর মধ্যে একটি হয়ে ওঠে।
এসবইই সংকেত যখন ডেডিকেটেড সার্চ ইঞ্জিন যোগ করা ভাল করে:
- আপনি একই পৃষ্ঠায় অনেক ফিল্টার ও ফ্যাসেট চান ফুলি-টেক্সট সার্চের সঙ্গে।
- আপনি ফাজি ম্যাচিং, টাইপো সহনশীলতা, বা অটোকমপ্লিটকে প্রথম-শ্রেণির ফিচার চান।
- আপনি সার্চ অ্যানালিটিক্স ও রিলেভ্যান্সের জন্য ফিডব্যাক লুপ চান।
- সার্চ ট্রাফিক এতই বড় যে এটিকে প্রাইমারি ডাটাবেস থেকে আলাদা করা দরকার।
প্রায়োগিক মাইগ্রেশন পথ: Postgres-কে truth হিসেবে রাখুন, এমনকি যখন আপনি সার্চ ইঞ্জিন যোগ করেন। প্রথমে সার্চ কুয়েরি ও নো-রেজাল্ট কেস লগ করুন, তারপর একটি async সিঙ্ক জব চালান যা কেবল সার্চেবল ফিল্ডগুলো নতুন ইনডেক্সে কপি করে। কিছু সময় দুইটি সিস্টেম প্যারালাল চালান এবং ধীরে ধীরে সুইচ করুন—একদিনে সবকিছু বাজি না রাখাই ভালো।
পরবর্তী পদক্ষেপ: একটি বেসলাইন শিপ করুন, পরিমাপ করুন, তারপর সিদ্ধান্ত নিন
যদি আপনার সার্চ মূলত “এই শব্দগুলো ধারণ করে এমন ডকুমেন্ট খুঁজুন” এবং আপনার ডেটাসেট বিশাল না, PostgreSQL full-text search সাধারণত যথেষ্ট। সেখানে শুরু করুন, কাজ করছে কিনা সেটি নিয়ে পরীক্ষা করুন, এবং কেবল তখনই ডেডিকেটেড ইঞ্জিন যোগ করুন যখন আপনি সম্মুখীন স্পষ্ট ফিচার বা স্কেল সমস্যা নাম রাখতে পারেন।
সংক্ষেপ:
- Postgres FTS ব্যবহার করুন যখন আপনি একটি
tsvectorসংরক্ষণ করতে পারেন, একটি GIN ইনডেক্স যোগ করতে পারেন, এবং আপনার র্যাঙ্কিং চাহিদা বেসিক। - একটি স্টার্টার কুয়েরি ও একটি ইনডেক্স সেটআপ শিপ করুন, তারপর বাস্তব লেটেন্সি ও “মানুষ কি পেয়েছে?” মাপুন।
- রিলেভ্যান্স টিউন করুন ছোট, স্পষ্ট পরিবর্তন (weights, language config, query parsing) দিয়ে—বড় রি-রাইট নয়।
- কেবল যখন স্পষ্ট ফাঁক দেখা যায় (autocomplete, উচ্চমান টাইপো সহনশীলতা, ফ্যাসেটিং) বা বৃদ্ধি-সংকেত (সাইজ, লোড) তখন সার্চ ইঞ্জিন পরিকল্পনা করুন।
প্র্যাকটিক্যাল পরবর্তী ধাপ: আগের সেকশনগুলো থেকে স্টার্টার কুয়েরি ও ইনডেক্স ইমপ্লেমেন্ট করুন, তারপর এক সপ্তাহের জন্য কয়েকটি সহজ মেট্রিক লগ করুন। p95 কুয়েরি টাইম, ধীর কুয়েরি এবং একটি সাধারণ সাকসেস সিগনাল (উদাহরণ: “search -> click -> no immediate bounce”—একটি বেসিক ইভেন্ট কাউন্টারও সাহায্য করে) ট্র্যাক করুন। আপনি দ্রুত দেখতে পাবেন যে আপনাকে ভাল র্যাঙ্কিং দরকার নাকি কেবল ভাল UX (ফিল্টার, হাইলাইটিং, ভালো স্নিপেট)।
একটি বাস্তবিক শর্তে ডেডিকেটেড সার্চ ইঞ্জিন পরিকল্পনা করুন যখন নিম্নলিখিতগুলোর একটি বাস্তব চাহিদা হয়ে ওঠে (নিছক নাস-টু-হ্যাভ নয়): প্রতিটি কীস্ট্রোক-এ স্কেলে শক্ত অটোকমপ্লিট, শক্ত টাইপো সহনশীলতা ও বানান সংশোধন, অনেক ফিল্ড জুড়ে দ্রুত ফ্যাসেট ও কাউন্ট, উন্নত রিলেভ্যান্স টুলিং (synonym sets, learning-to-rank, per-query boosts), অথবা দীর্ঘ সময় ধরে বড় লোড ও বিশাল ইনডেক্স যা দ্রুত রাখা কঠিন।
যদি আপনি অ্যাপ সাইডে দ্রুত এগোতে চান, Koder.ai (koder.ai) প্রোটোটাইপ সার্চ UI ও API-র জন্য ব্যবহার করতে পারেন—চ্যাটের মাধ্যমে দ্রুত প্রোটোটাইপ করে স্ন্যাপশট ও রোলব্যাক দিয়ে নিরাপদভাবে মাপা যাবে ব্যবহারকারীরা আসলে কি করে।
সাধারণ প্রশ্ন
What does “enough search” actually mean for a real app?
PostgreSQL full-text search তখনই “পূরক” বলে গণ্য যে আপনি একই সময়ে এই তিনটি লক্ষ্য মেনে নিতে পারেন:
- প্রাসঙ্গিক ফলাফল: সাধারণ ম্যাচগুলো শীর্ষের নিকট تظهر হয়।
- দ্রুত প্রতিক্রিয়া: স্বাভাবিক লোডে ফলাফল দ্রুত আসে।
- কম অপারেশনাল ঝামেলা: আপনি আলাদা কোনো সিস্টেম বা সিঙ্ক পাইপলাইন চালাচ্ছেন না।
যদি আপনি একটি সংরক্ষিত tsvector + একটি GIN ইনডেক্স দিয়ে এইগুলো পূরণ করতে পারেন, তাহলে সাধারণত আপনি ভালো অবস্থায় আছেন।
Should I start with Postgres search or add a search engine right away?
প্রাথমিকভাবে PostgreSQL full-text search নিবেন। এটি দ্রুত ডেলিভার করা যায়, ডেটা ও সার্চ একই স্থানে থাকে, এবং আলাদা ইনডেক্সিং পাইপলাইন তৈরি ও বজায় রাখার ঝামেলা থাকে না।
তবে আপনি তখনই আলাদা সার্চ ইঞ্জিনে যাবেন যখন একটি পরিষ্কার প্রয়োজন থাকবে যা Postgres ভালভাবে মেটাতে পারে না (উচ্চমানের টাইপো সহনশীলতা, সমৃদ্ধ অটোকমপ্লিট, ভারী ফ্যাসেটিং, বা এমন সার্চ লোড যা মূল ডাটাবেসের সাথে প্রতিযোগিতা করে)।
What’s the quickest decision rule for Postgres FTS vs a dedicated engine?
একটি সহজ নিয়ম: আপনি যদি নিচের তিনটি চেক পাস করেন তবে Postgres-ই রাখুন; যদি একটায়ই বড়ভাবে ব্যর্থ হন, আলাদা সার্চ ইঞ্জিন বিবেচনা করুন।
- প্রাসঙ্গিকতার চাহিদা: “পর্যাপ্ত” র্যাঙ্কিং গ্রহণযোগ্য কি?
- লোড + লেটেন্সি: সার্চ কি আপনার প্রধান DB-কে ওভারলোড করবে?
- জটিলতা: আপনি কি কয়েকটি টেক্সট ফিল্ড সার্চ করছেন, নাকি অনেক সিগনাল ও ভাষা মিশ্রিত লজিক?
বিশেষ করে টাইপো/অটোকমপ্লিট বা উচ্চ সার্চ ট্র্যাফিক থাকলে আলাদা ইঞ্জিন বিবেচনা করুন।
What kinds of search problems are a good fit for PostgreSQL full-text search?
Postgres FTS ব্যবহার করুন যখন আপনার সার্চ মূলত “ঠিক রেকর্ডটি খুঁজে পাওয়া”—কয়েকটি ফিল্ড (title/body/notes) এবং সহজ ফিল্টার (tenant, status, category) নিয়ে।
এটি help centers, internal docs, টিকিট, ব্লগ/আর্টিকেল সার্চ এবং SaaS ড্যাশবোর্ডের জন্য ভালো ফিট যেখানে মানুষ প্রজেক্ট নাম বা নোটস দিয়ে খোঁজ করে।
What’s a good “starter query” shape for Postgres full-text search?
একটি ভাল বেসলাইন প্রায়ই:
- ইউজার ইনপুট
websearch_to_tsqueryদিয়ে পার্স করে। - সস্তা কনস্ট্রেইন্টগুলো প্রথমে ফিল্টার করে (tenant/status/date)।
- মিলে
@@ব্যবহার করে সংরক্ষিতtsvector-এর বিরুদ্ধে ম্যাচ করে। ts_rank/ts_rank_cdএবং একটি স্থিতিশীল টাই-ব্রেকার (updated_at, id) দিয়ে অর্ডার করে।
এটি ফলাফলকে প্রাসঙ্গিক, দ্রুত ও পেজিনেশনের জন্য স্থিতিশীল রাখে।
How do I make Postgres search fast without adding more infrastructure?
একটি প্রি-বিল্ট tsvector সংরক্ষণ করুন এবং একটি GIN ইনডেক্স যোগ করুন। এটা প্রতিটি অনুরোধে to_tsvector(...) পুনর্গণনা থেকে রক্ষা করে।
প্র্যাকটিক্যাল সেটআপ:
- সার্চেবল
tsvector-টি সেই একই টেবিলেই রাখুন যা আপনি কোয়েরি করেন। - ঐ কলামে GIN ইনডেক্স তৈরি করুন।
- নিশ্চিত করুন আপনার কোয়েরি
tsvector_column @@ tsqueryব্যবহার করছে।
এটাই সাধারণত সার্চ ধীর হলে সবচেয়ে সহজ ও কার্যকর সমাধান।
Should I use a generated column or a trigger to maintain the tsvector?
যখন সার্চ ডকুমেন্ট একই সারির কলাম থেকে তৈরি হয় তখন generated column সবচেয়ে সহজ অপশন—এটি আপডেটের সাথে স্বয়ংক্রিয়ভাবে ঠিক থাকে।
আপনি যদি সম্পর্কিত টেবিল থেকে ডাটা যোগ করেন (যেমন প্রোডাক্ট ও তার ক্যাটাগরি নাম একসাথে), তখন trigger-maintained tsvector ব্যবহার করুন—তবে ট্রিগারগুলো ছোট ও ভালভাবে টেস্ট করা উচিত।
ডিফল্ট: প্রথমে generated column ব্যবহার করুন; শুধু যখন সত্যিই cross-table কম্পোজিশন লাগে তখন ট্রিগার বিবেচনা করুন।
How can I improve relevance without overengineering ranking?
প্রেডিক্টেবল রিলেভ্যান্স দিয়ে শুরু করুন:
- ফিল্ড ওয়েইটিং: title-এ ম্যাচ body-র তুলনায় বেশি ওজন দিন।
- বুস্টগুলো ছোট রাখুন: freshness/popularity যোগ করলে তা টেক্সট রিলেভ্যান্সকে মোটেই অদৃশ্য করে না।
- ভাষা কনফিগারেশন ঠিক করুন: stemming ও stop words সেটিংস গুরুত্বপূর্ণ।
তারপর ছোট সেটের প্রকৃত ইউজার কুয়েরি নিয়ে যাচাই করুন।
Why doesn’t Postgres FTS behave like “contains” search (LIKE %term%)?
Postgres FTS শব্দ-ভিত্তিক, substring-ভিত্তিক নয়—তাই এটি LIKE '%term%'-এর মত আচরণ করবে না।
যদি আপনি substring সার্চ (জুনো product codes বা অংশিক IDs) চান, সেটা আলাদা টুল দিয়ে হ্যান্ডেল করুন (উদাহরণ: trigram ইনডেক্সিং)। FTS-কে ওই কাজটুকু জবরদস্তি করবেন না।
What are the red flags that mean I should add a dedicated search engine?
আপনি যখন পুরোপুরি আউটগ্রো করেন:
- শক্ত টাইপো সহনশীলতা, ব্যাপক synonym সেট, বা সমৃদ্ধ অটোকমপ্লিট আবশ্যক।
- অনেক ফিল্ডে দ্রুত ফ্যাসেট/অ্যাগ্রিগেশন দরকার।
- সার্চ ট্র্যাফিক এত বেশি যে এটি আপনার প্রধান DB-কে চাপ দিচ্ছে।
- উন্নত রিলেভ্যান্স টুলিং দরকার (ফিডব্যাক লুপ, learning-to-rank)।
প্রায়োগিক পথ: Postgres-কে truth রাখুন এবং যখন প্রয়োজন পরিষ্কার হয় তখন async indexing দিয়ে আলাদা ইঞ্জিন যোগ করুন।