IlmHamroh
JavaScript Full-stack/29-qism. Backend integratsiyalar: fayl, to'lov, bot, realtime54/69-dars18 daqiqa
Mundarija (26)

Elasticsearch va OpenSearch: mapping, analyzer, query DSL va aggregation

Qisqacha: Elasticsearch — katta hajmdagi matn qidiruvi va loglar analitikasi uchun sanoat standarti, OpenSearch — uning 2021-yildagi ochiq kodli ayrilmasi (fork). Ikkalasida ham har maydonning turi mapping da beriladi (text — qidiruv uchun tahlil qilinadi, keyword — aniq qiymat), so'rovlar JSON ko'rinishidagi query DSL da yoziladi (match, bool, fuzzy), sanoq va statistika esa aggregation lar bilan olinadi. Kuchli, lekin og'ir: kamida bir necha gigabayt xotira va jiddiy operatsion ish talab qiladi.

Bu darsda

  • Mapping'da text va keyword farqini va analyzer nima qilishini tushuntirasiz.
  • Query DSL'da match, bool (must, filter, should, must_not) va fuzziness ni yozasiz.
  • terms va date_histogram aggregation'lari nimani qaytarishini bilasiz.
  • @elastic/elasticsearch klienti bilan indekslash va qidiruv kodini o'qiysiz.
  • Elasticsearch, OpenSearch va Meilisearch orasida resurs va litsenziya bo'yicha tanlay olasiz.

Oldin bilishingiz kerak: Qidiruv tizimlari qanday ishlaydi, Meilisearch va Typesense, JSONB.

1. Nega bu kerak?

Meilisearch kichik va o'rta katalog uchun ajoyib. Lekin uchta vaziyatda boshqa vosita kerak bo'ladi.

Birinchisi — hajm. Yuz millionlab hujjat, bir nechta serverga bo'lingan indeks, server ishdan chiqsa ham ma'lumot yo'qolmasligi. Ikkinchisi — analitika. "Oxirgi 30 kunda har kuni nechta xato logi bo'ldi, qaysi servisdan?" — bu qidiruv emas, hisob-kitob. Uchinchisi — tahlil ustidan to'liq nazorat: har maydon uchun o'z tokenizatori, sinonimlar lug'ati, til qoidalari.

Bu ishlar uchun eng ko'p ishlatiladigan vosita — Elasticsearch. Uning ochiq kodli ayrilmasi — OpenSearch. Ikkalasi ham Apache Lucene kutubxonasi ustiga qurilgan — oldingi darslardagi inverted index va BM25'ning sanoat versiyasi.

Halol eslatma: bu dars sinalmagan. Kanon Elasticsearch va OpenSearch'ni ishga tushirmagan: image ~1 GB va 1–2 GB heap xotira talab qiladi, kanon mashinasida esa diskda joy kam edi. Shuning uchun darsdagi Elasticsearch kodi nocheck — u rasmiy hujjat bo'yicha yozilgan (2026-10), natijasi o'ylab topilmaydi. Brauzerda ishlaydigan bloklar esa — dvigatel ichidagi g'oyalarni JavaScript'da takrorlaydi, ularning chiqishi haqiqiy.

2. Asosiy tushunchalar

Elasticsearch Ma'nosi Meilisearch'da
indeks (index) hujjatlar to'plami indeks
hujjat (document) JSON obyekt, _id bilan hujjat
mapping maydonlar turi va tahlili sozlamalar
shard indeksning bo'lagi (alohida Lucene indeksi) yo'q
replica shard nusxasi, boshqa serverda yo'q
node, cluster bitta server, serverlar guruhi asosan bitta

Shard va replica — Elasticsearch'ning masshtablash g'oyasi. Indeks bir nechta bo'lakka bo'linadi, bo'laklar turli serverlarga tarqatiladi. So'rov hamma bo'laklarga parallel yuboriladi, natijalar birlashtiriladi. Replica esa bo'lakning nusxasi: bitta server o'chsa, nusxasi javob beradi.

flowchart TB
  Q["So'rov"] --> N1
  Q --> N2
  subgraph N1["Server 1"]
    S0["shard 0"]
    R1["replica 1"]
  end
  subgraph N2["Server 2"]
    S1["shard 1"]
    R0["replica 0"]
  end
  S0 -.nusxa.-> R0
  S1 -.nusxa.-> R1

Diagrammada indeks ikki shard'ga bo'lingan va har shard'ning nusxasi boshqa serverda. 1-server o'chsa, 0-shard ma'lumoti 2-serverdagi replica'dan o'qiladi.

3. Mapping va analyzer

3.1 text va keyword

Mapping — indeksning sxemasi. Eng muhim farq — satr maydonining ikki turi:

  • text — qidiruv uchun. Saqlashdan oldin analyzer'dan o'tadi: so'zlarga bo'linadi, kichik harfga keltiriladi. "Kitob O'qish" → kitob, o'qish. match so'rovi bilan qidiriladi.
  • keyword — aniq qiymat uchun. Tahlil qilinmaydi: "yuqori" qanday bo'lsa shunday. Filtr, saralash va aggregation uchun.

Bitta maydon ikkalasi bo'lishi mumkin — multi-field. Hujjatga ko'ra mapping shunday yoziladi:

js
const mapping = {
  properties: {
    matn: {
      type: "text",
      analyzer: "uz_matn",
      fields: { aniq: { type: "keyword", ignore_above: 256 } },
    },
    egasi: { type: "integer" },
    ustuvorlik: { type: "keyword" },
    bajarildi: { type: "boolean" },
    yaratilgan: { type: "date" },
  },
};

matn — qidiruv uchun, matn.aniq — aniq tenglik va saralash uchun. Mapping'ni oldindan bermasangiz, Elasticsearch birinchi hujjatdan turini o'zi taxmin qiladi (dynamic mapping). Satrni u sukut bo'yicha text + keyword multi-field qiladi. Qulay, lekin xavfli: "42" satri kelsa, maydon satr bo'lib qoladi, keyin son kelsa ham. Mavjud maydon turini o'zgartirib bo'lmaydi — yangi indeks yaratib, ma'lumotni ko'chirish (reindex) kerak.

Diqqat: Production indeksida mapping'ni doim qo'lda bering. Turni tuzatish — butun indeksni qayta yozish degani.

3.2 Analyzer zanjiri

Analyzer — Qidiruv tizimlari qanday ishlaydi darsidagi "tokenizatsiya → normallash → stemming" zanjirining Elasticsearch'dagi nomi. U uch qismdan iborat:

  1. Character filter — matnni tokenizatsiyadan oldin o'zgartiradi (masalan ‘ → ').
  2. Tokenizer — so'zlarga bo'ladi (sukut standard — Unicode qoidalari bo'yicha).
  3. Token filter — har tokenni o'zgartiradi: lowercase, asciifolding (é → e), stop (keraksiz so'zlar), sinonimlar, stemming.

O'zbek tili uchun tayyor analyzer yo'q. Hujjatga ko'ra o'zingiznikini yig'asiz — apostroflarni bitta belgiga keltiradigan mapping character filter va lowercase bilan:

js
const settings = {
  analysis: {
    char_filter: {
      apostrof: {
        type: "mapping",
        mappings: ["‘ => '", "’ => '", "ʻ => '", "ʼ => '"],
      },
    },
    analyzer: {
      uz_matn: {
        type: "custom",
        char_filter: ["apostrof"],
        tokenizer: "standard",
        filter: ["lowercase"],
      },
    },
  },
};

Bu zanjir nima qilishini JavaScript'da takrorlab ko'ramiz. Elasticsearch har tokenning pozitsiyasini ham saqlaydi — "so'zlar yonma-yon turibdimi" (match_phrase) shunga kerak:

js
const APOSTROF = /[‘’ʻʼ]/g;

function tahlil(matn) {
  const tozalangan = matn.replace(APOSTROF, "'");
  const tokenlar = tozalangan.match(/[\p{L}\p{N}']+/gu) ?? [];
  return tokenlar.map((t, pozitsiya) => ({
    token: t.toLowerCase(),
    pozitsiya,
  }));
}

console.log(tahlil("Kitob O‘qish: har kuni 20 bet"));

Konsolda:

text
[
  { token: 'kitob', pozitsiya: 0 },
  { token: "o'qish", pozitsiya: 1 },
  { token: 'har', pozitsiya: 2 },
  { token: 'kuni', pozitsiya: 3 },
  { token: '20', pozitsiya: 4 },
  { token: 'bet', pozitsiya: 5 }
]

\p{L} — har qanday tildagi harf (Unicode regex), shuning uchun kirill so'zlar ham token bo'ladi. Haqiqiy standard tokenizer bundan murakkabroq (Unicode so'z chegaralari qoidasi), lekin g'oya shu.

Elasticsearch'da analyzer'ni _analyze API bilan sinash mumkin — mapping yozishdan oldin har doim shunday qiling. Oldingi darsda ko'rganimizdek, hujjat va so'rov bitta analyzer'dan o'tishi shart. Elasticsearch buni o'zi ta'minlaydi: match so'rovi maydon analyzer'ini so'rov matniga ham qo'llaydi.

4. Query DSL

4.1 match va term

Query DSL — so'rov tili, JSON ko'rinishida. Ikkita asosiy so'rov:

  • match — text maydon uchun. So'rov matnini analyzer'dan o'tkazadi, keyin BM25 bilan saralaydi.
  • term — keyword, son, boolean uchun. Tahlilsiz, aniq tenglik.

Klassik xato: text maydonga term so'rovi. Hujjatda "Kitob" saqlangan, indeksda esa kitob (kichik harf). term: { matn: "Kitob" } hech narsa topmaydi — chunki term so'rov matnini tahlil qilmaydi.

4.2 bool: to'rt bo'lim

Murakkab so'rov bool bilan yig'iladi:

Bo'lim Ma'nosi Ballga ta'siri
must shart, bajarilishi kerak qo'shiladi
filter shart, bajarilishi kerak yo'q, natija keshlanadi
should bajarilsa — yaxshi qo'shiladi
must_not bajarilmasligi kerak yo'q

Qoida: ball kerak bo'lmagan hamma shart — filter ga. "Egasi 7", "bajarilmagan", "ustuvorlik yuqori" — bular "qanchalik mos" emas, "mos yoki yo'q". filter ball hisoblamaydi va natijasini keshlaydi — tezroq. Foydalanuvchi so'zi esa must ga, chunki u saralashni belgilaydi.

So'rovni yig'adigan funksiya — oddiy JavaScript, uni brauzerda sinaymiz:

js
const USTUVORLIK = new Set(["past", "orta", "yuqori"]);

function sorovYasa(egasiId, { q, ustuvorlik, bajarildi }) {
  const filter = [{ term: { egasi: egasiId } }];
  if (ustuvorlik && USTUVORLIK.has(ustuvorlik)) {
    filter.push({ term: { ustuvorlik } });
  }
  if (bajarildi === "true" || bajarildi === "false") {
    filter.push({ term: { bajarildi: bajarildi === "true" } });
  }
  const must = q
    ? [{ match: { matn: { query: q, fuzziness: "AUTO" } } }]
    : [{ match_all: {} }];
  return { query: { bool: { must, filter } } };
}

const s = sorovYasa(7, { q: "kitb oqish", bajarildi: "false" });
console.log(JSON.stringify(s, null, 1));

Konsolda:

text
{
 "query": {
  "bool": {
   "must": [
    {
     "match": {
      "matn": {
       "query": "kitb oqish",
       "fuzziness": "AUTO"
      }
     }
    }
   ],
   "filter": [
    {
     "term": {
      "egasi": 7
     }
    },
    {
     "term": {
      "bajarildi": false
     }
    }
   ]
  }
 }
}

Meilisearch'dagi filtr matnidan farqli, bu yerda so'rov — JSON obyekt. Foydalanuvchi qiymati satr ichiga yopishtirilmaydi, alohida maydon bo'lib ketadi — matnli in'eksiya xavfi kamroq. Lekin ruxsat ro'yxati baribir kerak: aks holda foydalanuvchi mavjud bo'lmagan qiymat bilan bo'sh natija oladi yoki siz kutmagan maydonga filtr qo'yadi.

4.3 fuzziness: "AUTO"

fuzziness — typo tolerance. "AUTO" qiymati hujjatga ko'ra so'z uzunligiga qarab ruxsat beradi: 1–2 belgi — xato yo'q, 3–5 belgi — bitta, 6 va undan ko'p — ikkita. Oldingi darsdagi ruxsat() funksiyasining o'zi, faqat chegaralari boshqa:

js
function auto(soz) {
  if (soz.length <= 2) return 0;
  if (soz.length <= 5) return 1;
  return 2;
}

for (const s of ["ok", "kitb", "oqish", "hisobt", "dorixna"]) {
  console.log(s, s.length, "→", auto(s), "ta xato");
}

Konsolda:

text
ok 2 → 0 ta xato
kitb 4 → 1 ta xato
oqish 5 → 1 ta xato
hisobt 6 → 2 ta xato
dorixna 7 → 2 ta xato

Alohida fuzzy so'rovi ham bor — u bitta termni tahlilsiz, belgilangan masofa bilan qidiradi. Amalda match + fuzziness qulayroq: tahlil ham, typo ham bir joyda.

5. Aggregation'lar

5.1 terms — facet'ning katta akasi

Aggregation — natija ustidan hisob: sanoq, yig'indi, o'rtacha, guruhlash. Qidiruv va hisob bitta so'rovda keladi. Hujjatga ko'ra so'rov shunday:

js
const javob = await es.search({
  index: "vazifalar",
  query: { bool: { filter: [{ term: { egasi: 7 } }] } },
  size: 0,
  aggs: {
    ustuvorlik: { terms: { field: "ustuvorlik" } },
    oylar: {
      date_histogram: {
        field: "yaratilgan",
        calendar_interval: "month",
      },
    },
  },
});

size: 0 — hujjatlarning o'zi kerak emas, faqat hisob. terms — maydon qiymatlari bo'yicha guruh va har guruhdagi hujjatlar soni (Meilisearch'dagi facet). date_histogram — vaqt oralig'i bo'yicha guruh. Aggregation faqat keyword, son va sana maydonlarida ishlaydi — text da emas. Mapping'da ustuvorlik ni keyword qilganimiz shuning uchun.

Natija tuzilmasi — aggregations.ustuvorlik.buckets massivi, har bucket'da key va doc_count. Mantiqni JavaScript'da takrorlaymiz:

js
const hujjatlar = [
  { ustuvorlik: "yuqori", yaratilgan: "2026-09-14" },
  { ustuvorlik: "orta", yaratilgan: "2026-09-30" },
  { ustuvorlik: "yuqori", yaratilgan: "2026-10-02" },
  { ustuvorlik: "past", yaratilgan: "2026-10-11" },
  { ustuvorlik: "yuqori", yaratilgan: "2026-10-27" },
];

function terms(royxat, maydon) {
  const sanoq = new Map();
  for (const h of royxat) {
    sanoq.set(h[maydon], (sanoq.get(h[maydon]) ?? 0) + 1);
  }
  return [...sanoq]
    .map(([key, doc_count]) => ({ key, doc_count }))
    .sort((a, b) => b.doc_count - a.doc_count);
}

console.log(terms(hujjatlar, "ustuvorlik"));

Konsolda:

text
[
  { key: 'yuqori', doc_count: 3 },
  { key: 'orta', doc_count: 1 },
  { key: 'past', doc_count: 1 }
]

Elasticsearch ham bucket'larni sukut bo'yicha doc_count kamayishi tartibida va faqat birinchi 10 tasini qaytaradi (size bilan o'zgaradi).

5.2 Taqsimlangan sanoqning narxi

Indeks shard'larga bo'lingan bo'lsa, terms har shard'da alohida hisoblanadi, keyin birlashtiriladi. Har shard faqat o'zining "eng ko'p" qiymatlarini yuboradi. Natijada kam uchraydigan qiymat sanog'i taxminiy bo'lishi mumkin — hujjat bu xatoni doc_count_error_upper_bound maydonida ko'rsatadi. Bitta shard'li indeksda bu muammo yo'q. Bu — taqsimlangan tizimning odatiy narxi: tezlik uchun aniqlikdan biroz voz kechish.

Tekshirib ko'ring: Nega matn (text) maydoni bo'yicha terms aggregation qilib bo'lmaydi?

Javob

text maydon so'zlarga bo'lingan — indeksda butun qiymat emas, alohida tokenlar bor. Elasticsearch sukut bo'yicha text uchun aggregation'ni rad etadi (fielddata o'chiq), chunki tokenlar bo'yicha sanoq ko'p xotira oladi va odatda kerakli narsa emas. Butun qiymat bo'yicha sanash kerak bo'lsa — matn.aniq (keyword multi-field).

6. Node.js klienti

Rasmiy klient — @elastic/elasticsearch. Uning katta versiyasi server versiyasiga mos bo'lishi kerak (9.x server — 9.x klient). Hujjat bo'yicha indeks yaratish, hujjat yuklash va qidirish:

js
import { Client } from "@elastic/elasticsearch";

const es = new Client({
  node: process.env.ES_URL,
  auth: { apiKey: process.env.ES_API_KEY },
});

await es.indices.create({
  index: "vazifalar",
  settings,
  mappings: mapping,
});

await es.helpers.bulk({
  datasource: vazifalar,
  onDocument: (v) => ({
    index: { _index: "vazifalar", _id: String(v.id) },
  }),
});

const r = await es.search({
  index: "vazifalar",
  ...sorovYasa(7, { q: "kitob" }),
});
for (const hit of r.hits.hits) {
  console.log(hit._score, hit._source.matn);
}

Uch narsa e'tiborga loyiq:

  • helpers.bulk — minglab hujjatni bitta-bitta emas, to'plab yuboradi (_bulk API). 50 000 hujjatni bittadan yuborish — 50 000 HTTP so'rov.
  • Refresh — yuklangan hujjat qidiruvda darhol ko'rinmaydi. Elasticsearch indeksni sukut bo'yicha har soniyada yangilaydi (refresh_interval). Bu — Meilisearch'dagi asinxron task bilan bir xil g'oya: "near real-time".
  • API kalit — ES_API_KEY server env'ida. Elasticsearch brauzerga ochiladigan xizmat emas, unda Meilisearch'dagi kabi tenant token yo'q. Qidiruv doim backend orqali.

OpenSearch'ning klienti alohida — @opensearch-project/opensearch. Asosiy API o'xshash, lekin ikkala klient almashinmaydi: har biri o'z serveri bilan ishlaydi.

7. Elasticsearch, OpenSearch va litsenziya

Tarixni qisqa aytamiz — u tanlovga ta'sir qiladi:

  1. Elasticsearch uzoq vaqt Apache 2.0 litsenziyasida edi.
  2. 2021-yilda Elastic litsenziyani SSPL va Elastic License 2.0 ga almashtirdi. Sabab — bulut provayderlari uni xizmat sifatida sotayotgan edi.
  3. Shunga javoban AWS oxirgi Apache versiyasidan (7.10.2) ayrilma yaratdi — OpenSearch, Apache 2.0 litsenziyasida.
  4. 2024-yil 29-avgustda Elastic AGPL'ni uchinchi litsenziya varianti sifatida qo'shdi.
  5. 2024-yil sentyabrda OpenSearch Linux Foundation qoshidagi OpenSearch Software Foundation'ga o'tkazildi.

Bugun ikkalasi alohida rivojlanmoqda: API'larning asosiy qismi mos, lekin yangi imkoniyatlar (vektor qidiruv, xavfsizlik, ML) farq qiladi. Kodni ikkalasiga birdan yozmang — bittasini tanlang.

Elasticsearch OpenSearch Meilisearch
Litsenziya ELv2 / SSPL / AGPL Apache 2.0 MIT (asosiy versiya)
Kuchli tomoni qidiruv + analitika, ekotizim (Kibana) AWS'da boshqariladigan xizmat soddalik, typo tolerance
Xotira gigabaytlar (JVM heap) gigabaytlar (JVM heap) kamroq
O'rganish qiyin qiyin oson

8. Resurs va operatsiya

Elasticsearch — JVM (Java) ilovasi. Hujjatga ko'ra heap hajmi mashina xotirasining yarmidan oshmasligi kerak — qolgani operatsion tizimning fayl keshiga (Lucene unga tayanadi). Linux'da Docker bilan ishga tushirishda vm.max_map_count sozlamasini oshirish talab qilinadi. Kanon aynan resurs sababli sinamadi: image ~1 GB, heap 1–2 GB.

Bundan tashqari: klaster sog'lig'ini kuzatish (green/yellow/red), shard sonini rejalashtirish (keyin o'zgartirish qiyin), versiya yangilash, snapshot (zaxira). Bularning hammasi — operatsion ish. Kichik jamoa uchun bu ko'pincha hal qiluvchi: Elastic Cloud yoki Amazon OpenSearch Service kabi boshqariladigan xizmat — yoki soddaroq dvigatel.

9. Hujumchi nigohi

  1. Ochiq port. Internetda autentifikatsiyasiz Elasticsearch — eng ko'p uchraydigan ma'lumot sizishi sabablaridan biri: 9200-port ochiq qolgan klasterlardan millionlab yozuvlar o'g'irlangan. Himoya: 8-versiyadan beri xavfsizlik sukut bo'yicha yoqilgan — uni o'chirmang; portni faqat ichki tarmoqda oching.
  2. Foydalanuvchi bergan query DSL. Frontend tayyor JSON so'rov yuborsa va backend uni o'zgartirmasdan uzatsa — hujumchi filter ni olib tashlaydi yoki og'ir aggregation bilan klasterni yuklaydi. Himoya: so'rovni server sorovYasa kabi funksiya bilan o'zi yig'adi.
  3. query_string so'rovi. U Lucene sintaksisini tushunadi (egasi:8 OR *). Foydalanuvchi matniga ishlatsangiz — filtrni chetlab o'tish va xato sintaksis bilan 400. Himoya: foydalanuvchi matni uchun match yoki simple_query_string.
  4. Chuqur sahifalash. from: 100000 — har shard 100 000+ hujjatni saralaydi. Elasticsearch sukut bo'yicha from + size ni 10 000 bilan cheklaydi (index.max_result_window). Chuqur o'tish uchun search_after — Pagination: offset, cursor, keyset darsidagi keyset g'oyasi.

10. Ko'p uchraydigan xatolar

Belgi Sabab Davosi
term hech narsa topmaydi text maydon tahlil qilingan, term esa yo'q match yoki keyword maydon
Aggregation text maydonda xato fielddata o'chiq keyword multi-field
Maydon turi noto'g'ri, tuzatib bo'lmaydi dynamic mapping taxmini yangi indeks + reindex
Yuklangan hujjat darhol topilmaydi refresh hali bo'lmagan kuting yoki testda refresh: true
Result window is too large from + size > 10 000 search_after
Klient xatosi versiya haqida klient va server katta versiyasi farqli bir xil major versiya

11. Mashqlar

Mashqlar kurs/mashqlar/29/54-elasticsearch/ da.

1-mashq (oson): bo'shliqlarni to'ldiring

  • Tahlil qilinmaydigan, filtr va aggregation uchun satr turi —
  • Ballga ta'sir qilmaydigan va keshlanadigan bool bo'limi —
  • fuzziness: "AUTO" 4 harfli so'zga beradigan xato soni —
  • Indeksning alohida serverga joylashadigan bo'lagi —
  • Elasticsearch'dan 2021-yilda ayrilgan Apache 2.0 loyiha —
Yechim
  • keyword — text esa analyzer'dan o'tadi.
  • filter — "mos yoki yo'q" shartlari uchun.
  • 1 — 3–5 belgi — bitta xato.
  • shard — nusxasi esa replica.
  • OpenSearch — hozir OpenSearch Software Foundation qoshida.

2-mashq (o'rta): date_histogram oyma-oy

Darsdagi hujjatlar massivi uchun oylar(royxat) funksiyasini yozing: u { key: "2026-09", doc_count } ko'rinishidagi bucket'larni vaqt tartibida qaytarsin. Bo'sh oy bo'lsa (oraliqda hujjat yo'q oy), u ham doc_count: 0 bilan chiqsin — Elasticsearch'dagi min_doc_count: 0 kabi. Sinash uchun hujjatlarga 2026-12-05 sanali yana bittasini qo'shing.

Yechim
js
const hujjatlar = [
  { yaratilgan: "2026-09-14" },
  { yaratilgan: "2026-09-30" },
  { yaratilgan: "2026-10-02" },
  { yaratilgan: "2026-10-11" },
  { yaratilgan: "2026-10-27" },
  { yaratilgan: "2026-12-05" },
];

function oylar(royxat) {
  const sanoq = new Map();
  for (const h of royxat) {
    const oy = h.yaratilgan.slice(0, 7);
    sanoq.set(oy, (sanoq.get(oy) ?? 0) + 1);
  }
  const kalitlar = [...sanoq.keys()].sort();
  let [y, m] = kalitlar[0].split("-").map(Number);
  const oxirgi = kalitlar.at(-1);
  const natija = [];
  while (true) {
    const key = `${y}-${String(m).padStart(2, "0")}`;
    natija.push({ key, doc_count: sanoq.get(key) ?? 0 });
    if (key === oxirgi) break;
    m += 1;
    if (m === 13) {
      m = 1;
      y += 1;
    }
  }
  return natija;
}

console.log(oylar(hujjatlar));

Konsolda:

text
[
  { key: '2026-09', doc_count: 2 },
  { key: '2026-10', doc_count: 3 },
  { key: '2026-11', doc_count: 0 },
  { key: '2026-12', doc_count: 1 }
]

Kalit YYYY-MM satri bo'lgani uchun oddiy sort() vaqt tartibini beradi. Noyabr ro'yxatda 0 bilan bor — grafik chizganda bo'sh oy "yo'qolib qolmasligi" uchun shunday qilinadi. Oyni Date bilan emas, raqam bilan oshirdik — vaqt zonasi tuzog'idan qochish uchun (Backend'da sana va vaqt).

3-mashq (qiyin): must_not va should qo'shing

sorovYasa ni kengaytiring. Yangi parametrlar: istisno — vergul bilan ajratilgan ustuvorliklar (masalan "past,orta"), ular must_not ga terms so'rovi bo'lib tushsin (faqat ruxsat ro'yxatidagilar); muhim: "1" bo'lsa — should ga { term: { ustuvorlik: "yuqori" } } qo'shilsin (yuqori ustuvorlikdagilar ballni oshiradi, lekin shart emas). Bo'sh massivli bo'limlar natijaga kirmasin.

Yechim
js
const USTUVORLIK = new Set(["past", "orta", "yuqori"]);

function sorovYasa(egasiId, { q, istisno, muhim }) {
  const bool = {
    must: q
      ? [{ match: { matn: { query: q, fuzziness: "AUTO" } } }]
      : [{ match_all: {} }],
    filter: [{ term: { egasi: egasiId } }],
    must_not: [],
    should: [],
  };
  const chiqar = (istisno ?? "")
    .split(",")
    .map((x) => x.trim())
    .filter((x) => USTUVORLIK.has(x));
  if (chiqar.length > 0) {
    bool.must_not.push({ terms: { ustuvorlik: chiqar } });
  }
  if (muhim === "1") {
    bool.should.push({ term: { ustuvorlik: "yuqori" } });
  }
  for (const k of Object.keys(bool)) {
    if (bool[k].length === 0) delete bool[k];
  }
  return { query: { bool } };
}

const a = sorovYasa(7, { istisno: "past, juda, orta" });
console.log(JSON.stringify(a));
const b = sorovYasa(7, { q: "hisobot", muhim: "1" });
console.log(JSON.stringify(b));

Konsolda:

text
{"query":{"bool":{"must":[{"match_all":{}}],"filter":[{"term":{"egasi":7}}],"must_not":[{"terms":{"ustuvorlik":["past","orta"]}}]}}}
{"query":{"bool":{"must":[{"match":{"matn":{"query":"hisobot","fuzziness":"AUTO"}}}],"filter":[{"term":{"egasi":7}}],"should":[{"term":{"ustuvorlik":"yuqori"}}]}}}

juda ruxsat ro'yxatida yo'q — jim tashlandi. Xato qaytarish ham to'g'ri yo'l bo'lardi: foydalanuvchi qiymatni noto'g'ri yozganini bilardi. Bu — API dizayn qarori. should must bor bo'lganda faqat ballni oshiradi; must va filter siz yolg'iz qolsa, hujjatga ko'ra kamida bittasi bajarilishi shart bo'ladi. Bizda filter doim bor — shuning uchun should ixtiyoriy bo'lib qoladi.

12. Loyiha qadami: sinalmagan, loyihaga kirmaydi

Bu darsda vazifalar-api ga kod qo'shilmaydi va kanonda Elasticsearch tajribasi yo'q. Sabab — resurs: shaxsiy vazifalar uchun gigabaytli JVM xizmatini ko'tarish o'rinsiz, Postgres FTS (27-qism) yetadi. Elasticsearch kerak bo'ladigan joy — loglar va katta katalog. Loglar mavzusi 34-qismda monitoring bilan qaytadi.

O'zingiz sinab ko'ring: kompyuteringizda kamida 4 GB bo'sh xotira bo'lsa, rasmiy hujjatdagi "local development" yo'riqnomasi bo'yicha Elasticsearch'ni ishga tushiring. _analyze API bilan darsdagi uz_matn analyzer'ini sinang va natijani tahlil() funksiyasi chiqishi bilan solishtiring.

13. Real ishda

  • Loglar va kuzatuv — ELK/Elastic Stack (Elasticsearch + Logstash/Beats + Kibana) yoki OpenSearch + Dashboards: serverlar loglarini yig'ib, qidirish va grafik.
  • Katta e-commerce katalogi — facet, sinonim, til analyzer'lari, shaxsiy saralash.
  • Xavfsizlik (SIEM) — hodisalarni qidirish va korrelyatsiya.
  • Boshqariladigan xizmatlar — Elastic Cloud, Amazon OpenSearch Service: klasterni o'zingiz boshqarmaysiz.
  • Intervyuda: "text va keyword farqi?", "must va filter farqi?", "Shard va replica nima?", "Nega OpenSearch paydo bo'ldi?"

Xulosa

  • Mapping — indeks sxemasi: text qidiruv uchun tahlil qilinadi, keyword aniq qiymat, filtr va aggregation uchun.
  • Analyzer = character filter + tokenizer + token filter'lar; o'zbekcha uchun o'zingiz yig'asiz.
  • bool: so'z — must, ballsiz shartlar — filter; so'rovni server JSON qilib o'zi yig'adi.
  • fuzziness: "AUTO" — so'z uzunligiga qarab 0, 1 yoki 2 xato.
  • Aggregation — qidiruv bilan birga hisob; shard'li indeksda terms sanog'i taxminiy bo'lishi mumkin.
  • Elasticsearch va OpenSearch — kuchli, lekin og'ir; litsenziya va resursni hisobga olib tanlanadi. Bu dars sinalmagan — hujjat bo'yicha.

Keyingi dars: Backend'da ko'p tillilik (i18n) — foydalanuvchi tilini aniqlash, ko'plik shakllari, ICU xabarlari, lotin-kirill transliteratsiyasi va bazada ko'p tilli kontent.

Manbalar

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!