နားလည်ထားရမယ့် အချက်
ဒီ project က course တစ်ခုလုံးကနေ learn ခဲ့တဲ့ concept တွေအားလုံးကို—mapping design (Lesson 4, 8), bulk indexing (Lesson 5, 14), `bool` query (Lesson 10), Node.js client (Lesson 15)—တစ်ခုတည်း working system အဖြစ် ပေါင်းစည်းတဲ့ capstone project ဖြစ်ပါတယ်။ Real-world search backend တစ်ခုကို ဆောက်တဲ့အခါ layer သုံးထပ်ကို သီးခြားစီ design ရပါတယ်—(1) mapping layer: `tutorials` index ရဲ့ field structure ကို explicit ဆုံးဖြတ်ခြင်း၊ (2) sync layer: PostgreSQL ရဲ့ tutorials/lessons tables ကနေ data ကို read ပြီး Elasticsearch document shape ဆီ transform (denormalize) လုပ်ပြီး `_bulk` API ဖြင့် index ချသော one-time initial sync script၊ (3) query layer: search UI request ကို Query DSL အဖြစ် translate လုပ်ပေးသော API endpoint။ ဒီလို layer ခွဲထားခြင်းရဲ့ အကျိုးက sync logic ကို query logic ကနေ decouple ထားခြင်းဖြစ်ပြီး—sync script ကို schedule (cron) သို့ event-driven (database trigger, change data capture) မည်သည့်နည်းနဲ့မဆို run လို့ရပြီး query layer ကတော့ underlying sync mechanism ကို လုံးဝ သိစရာမလိုပါဘူး။ Initial sync ကို run တဲ့အခါ PostgreSQL ဘက်က data volume အလိုက် bulk chunk size ကို tune ရမယ်၊ Elasticsearch ဘက်က mapping ကို sync run ခင် ဦးစွာ create ချထားရမယ် (dynamic mapping ကို trust မလုပ်ဘဲ)—ဒီ project ကို hands-on run ပြီးမှသာ theory အားလုံးက production-shaped system တစ်ခုထဲမှာ ဘယ်လို fit ဖြစ်လဲ ခံစားရနိုင်ပါတယ်။
လက်တွေ့ scenario နဲ့ ချိတ်ကြည့်မယ်
`tutorials` index ကို Lesson 4/8 ကနေ ဆက်တည်ဆောက်ထားခဲ့တဲ့ mapping (title text+keyword, tags keyword, body text with english analyzer, publishedAt date) နှင့် create ချမယ်—dynamic mapping ကို trust မလုပ်ဘဲ explicit ဆက်ရေးမယ်။ Sync script (Node.js) ကို PostgreSQL ကနေ `tutorials` JOIN `lessons` query run ပြီး tutorial တစ်ခုစီရဲ့ lesson content များကို document တစ်ခုတည်းအောက် nested array အဖြစ် denormalize လုပ်ကာ `_bulk` API (500-document chunks) ဖြင့် index ချမယ်—chunk တစ်ခုချင်းစီရဲ့ `errors` field ကို check ပြီး failed items ကို retry လုပ်မယ်။ Query layer (Lesson 15 ရဲ့ `esClient`) ကို search endpoint (`/api/search?q=redis&difficulty=beginner`) အဖြစ် ဆောက်မယ်—`bool` query ထဲ `must: match(body)` + `filter: term(difficulty)` ကို ပေါင်းသုံးမယ်။
အတူတူ စမ်းရေးကြည့်မယ်
async function syncTutorials() {
const rows = await db.query(
'SELECT t.id, t.title, t.published_at, t.difficulty, array_agg(l.body) AS lesson_bodies FROM tutorials t JOIN lessons l ON l.tutorial_id = t.id GROUP BY t.id'
);
for (let i = 0; i < rows.length; i += 500) {
const chunk = rows.slice(i, i + 500);
const operations = chunk.flatMap((row) => [
{ index: { _index: 'tutorials', _id: String(row.id) } },
{
title: row.title,
body: row.lesson_bodies.join('\n'),
difficulty: row.difficulty,
publishedAt: row.published_at,
},
]);
const result = await esClient.bulk({ operations });
if (result.errors) {
console.error('Some documents failed to index', result.items);
}
}
}PostgreSQL ရဲ့ tutorials data အားလုံး Elasticsearch `tutorials` index ထဲ sync ဖြစ်ပြီး search endpoint ကနေ query လုပ်နိုင်မည်။၅ မိနစ် စမ်းကြည့်
အပေါ်က sync script ကို `authorName` field ပါ ထည့်နိုင်အောင် PostgreSQL query ကို extend လုပ်ပြီး mapping ထဲ `authorName` (keyword) field ထည့်ပါ—search endpoint ထဲမှာ `authorName` ပေါ် filter နိုင်သော parameter တစ်ခုလည်း ထပ်ထည့်ပါ။
သတိလေးတစ်ချက်
Sync script ကို mapping create ချခြင်းမပြုဘဲ run ခြင်း—dynamic mapping ကနေ auto-detect ဖြစ်သွားပြီး `difficulty` field ကို `text` လို့ auto-detect ဖြစ်သွားရင် filter query များ silently ပျက်နိုင်ပါတယ်။
Bulk chunk response ရဲ့ `errors`/item-level status ကို check မလုပ်ဘဲ sync script ကို "success" လို့ log ချခြင်း—document အချို့ fail ဖြစ်ခဲ့ရင်တောင် search index ထဲ data missing ဖြစ်နေတာကို ဘယ်တော့မှ တွေ့မှာ မဟုတ်ပါ။
Elasticsearch Guide — Bulk API — Elastic