← Kursa Dön
📄 Text · 35 min

Inverted Index — Elasticsearch Nasıl Bu Kadar Hızlı?

Elasticsearch Nasıl Bu Kadar Hızlı Arar?

Bir kitabın sonundaki "Dizin" (index) bölümünü düşün. "Elasticsearch" kelimesini bulmak istiyorsan, kitabın 500 sayfasını tek tek okumak yerine arka sayfadaki dizine bakarsın: "Elasticsearch — sayfa 12, 45, 78, 156". Üç saniyede buldun.

İşte Inverted Index, Elasticsearch'ün arkasındaki Apache Lucene'in kullandığı tam olarak bu mekanizma. Geleneksel veritabanları "döküman → içindeki kelimeler" şeklinde saklar. Inverted index ters çevirir: "kelime → hangi dökümanlarda geçiyor". Bu ters çevirme (inversion), arama işlemini inanılmaz hızlı yapar.


Forward Index vs Inverted Index

Forward Index (Geleneksel Yaklaşım)

Geleneksel veritabanlarının yaklaşımı — "döküman → kelimeleri" eşlemesi:

Forward Index:
┌──────────────────────────────────────────┐
│ Doc ID │ İçerik                          │
├────────┼─────────────────────────────────┤
│   1    │ "Elasticsearch hızlı arama"      │
│   2    │ "Elasticsearch dağıtık sistem"   │
│   3    │ "Hızlı ve güvenilir arama"       │
└──────────────────────────────────────────┘

"hızlı" kelimesini aramak için ne yapmalı? Her dökümanı tek tek kontrol et:

  • Doc 1: "Elasticsearch hızlı arama" → ✅ "hızlı" var

  • Doc 2: "Elasticsearch dağıtık sistem" → ❌ "hızlı" yok

  • Doc 3: "Hızlı ve güvenilir arama" → ✅ "hızlı" var (ama büyük H!)

  • ...

  • Doc 1.000.000: kontrol et...

1 milyon doküman varsa 1 milyon kontrol. Bu O(n) karmaşıklık — doğrusal tarama. Veri büyüdükçe arama lineeer olarak yavaşlar.

Inverted Index (Elasticsearch Yaklaşımı)

Tabloyu ters çevir — "kelime → dökümanlar" eşlemesi:

Inverted Index:
┌──────────────────────────────────────────────────┐
│ Term           │ Doc IDs (Posting List)           │
├────────────────┼─────────────────────────────────┤
│ arama          │ [1, 3]                          │
│ dağıtık        │ [2]                             │
│ elasticsearch  │ [1, 2]                          │
│ güvenilir      │ [3]                             │
│ hızlı          │ [1, 3]                          │
│ sistem         │ [2]                             │
│ ve             │ [3]                             │
└──────────────────────────────────────────────────┘

"hızlı" kelimesini aramak: Doğrudan hızlı → [1, 3] satırına bak. Bitti!

1 milyon döküman olsa bile, "hızlı" kelimesini bulmak O(log n) veya O(1) karmaşıklık — sözlükte (dictionary) arama.

Karşılaştırma (1 milyon doküman):
Forward Index:  ~1.000.000 kontrol  → 500ms+
Inverted Index: ~1 sözlük araması   → <1ms

Inverted Index Yapısı — Derinlemesine

Inverted index üç ana bileşenden oluşur:

1. Term Dictionary (Terim Sözlüğü)

Tüm benzersiz terimlerin sıralı listesi. Binary search ile herhangi bir terim O(log n)'de bulunabilir.

Term Dictionary (sıralı):
arama
dağıtık
elasticsearch
güvenilir
hızlı
sistem
ve

Lucene, term dictionary'yi disk üzerinde FST (Finite State Transducer) veri yapısıyla saklar. FST, sıralı string arama için son derece bellek verimli bir yapıdır. Tüm term dictionary'yi bellekte tutabilir — milyonlarca terim bile birkaç MB yer kaplar.

2. Posting List (Gönderim Listesi)

Her terim için o terimi içeren dökümanların listesi. Ek bilgiler de içerir:

Term: "elasticsearch"
Posting List:
┌─────────────────────────────────────────────────┐
│ Doc ID │ Term Frequency │ Positions │ Offsets    │
├────────┼────────────────┼───────────┼────────────┤
│   1    │      1         │ [0]       │ [0, 13]    │
│   2    │      1         │ [0]       │ [0, 13]    │
│   5    │      3         │ [0,5,12]  │ [0,13,...]  │
└─────────────────────────────────────────────────┘
  • Doc ID: Hangi dökümanda geçiyor

  • Term Frequency (TF): O dökümanda kaç kez geçiyor (scoring için)

  • Positions: Kaçıncı kelime pozisyonunda (phrase query için)

  • Offsets: Karakter pozisyonları (highlighting için)

3. Stored Fields & Doc Values

  • Stored Fields (_source): Orijinal JSON dökümanı — sonuç döndürmek için

  • Doc Values: Sorting ve aggregation için optimize edilmiş sütunsal veri yapısı

Tam resim:

┌─────────────────────────────────────────────────────────┐
│                    LUCENE SEGMENT                        │
│                                                         │
│  ┌─────────────┐  ┌──────────────┐  ┌───────────────┐ │
│  │    Term      │  │   Posting    │  │   Doc Values  │ │
│  │  Dictionary  │→│    Lists     │  │  (columnar)   │ │
│  │   (FST)      │  │             │  │               │ │
│  └─────────────┘  └──────────────┘  └───────────────┘ │
│                                                         │
│  ┌─────────────────────────────────────────────────┐   │
│  │              Stored Fields (_source)              │   │
│  └─────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────┘

Text Analysis — Inverted Index'e Giden Yol

Bir metin inverted index'e yazılmadan önce analiz sürecinden geçer. Bu süreç metni terimlere (term) dönüştürür.

Analiz Süreci

Orijinal Metin: "Elasticsearch'te Hızlı Full-Text Arama! 🔍"
                          ↓
              ┌──────────────────────┐
              │  Character Filters   │  → Özel karakterleri dönüştür
              └──────────────────────┘
                          ↓
              "Elasticsearch'te Hızlı Full-Text Arama"
                          ↓
              ┌──────────────────────┐
              │     Tokenizer        │  → Kelimelere ayır
              └──────────────────────┘
                          ↓
              ["Elasticsearch'te", "Hızlı", "Full", "Text", "Arama"]
                          ↓
              ┌──────────────────────┐
              │    Token Filters     │  → Dönüştür, filtrele
              └──────────────────────┘
                          ↓
              ["elasticsearch'te", "hızlı", "full", "text", "arama"]

Analyze API ile Gözlemleme

Elasticsearch'ün bir metni nasıl analiz ettiğini canlı olarak görebilirsin:

// Standard analyzer (varsayılan)
POST /_analyze
{
  "analyzer": "standard",
  "text": "Elasticsearch'te Hızlı Full-Text Arama!"
}

// Yanıt:
{
  "tokens": [
    { "token": "elasticsearch'te", "start_offset": 0,  "end_offset": 17, "position": 0 },
    { "token": "hızlı",            "start_offset": 18, "end_offset": 23, "position": 1 },
    { "token": "full",             "start_offset": 24, "end_offset": 28, "position": 2 },
    { "token": "text",             "start_offset": 29, "end_offset": 33, "position": 3 },
    { "token": "arama",            "start_offset": 34, "end_offset": 39, "position": 4 }
  ]
}

Farklı Analyzer'lar ile Karşılaştırma

// Simple analyzer — sadece harfleri tokenize eder
POST /_analyze
{
  "analyzer": "simple",
  "text": "Elasticsearch v8.17.0 — Hızlı Arama!"
}
// Tokens: ["elasticsearch", "v", "hızlı", "arama"]
// Sayılar atılır, noktalama yok, küçük harf

// Whitespace analyzer — sadece boşluklara göre böler
POST /_analyze
{
  "analyzer": "whitespace",
  "text": "Elasticsearch v8.17.0 — Hızlı Arama!"
}
// Tokens: ["Elasticsearch", "v8.17.0", "—", "Hızlı", "Arama!"]
// Olduğu gibi, küçük harf bile yapmaz

// Keyword analyzer — hiç analiz etmez
POST /_analyze
{
  "analyzer": "keyword",
  "text": "Elasticsearch v8.17.0"
}
// Tokens: ["Elasticsearch v8.17.0"]
// Tek token — tüm metin aynen

// Turkish language analyzer
POST /_analyze
{
  "analyzer": "turkish",
  "text": "Türkiye'deki büyük şirketler hızla büyüyor"
}
// Tokens: ["türkiye'deki", "büyük", "şirket", "hızla", "büyü"]
// Stemming: "şirketler" → "şirket", "büyüyor" → "büyü"

Belirli Bir Index'in Analyzer'ını Test Et

// Index'e özgü analyzer ile test
POST /products/_analyze
{
  "field": "description",
  "text": "Tam otomatik espresso kahve makinesi"
}

Inverted Index'in Çalışma Prensibi — Adım Adım

Üç dökümanımız olsun:

// Döküman 1
{ "_id": "1", "title": "Elasticsearch ile Hızlı Arama" }

// Döküman 2
{ "_id": "2", "title": "Hızlı ve Güvenilir Veri Arama" }

// Döküman 3
{ "_id": "3", "title": "Elasticsearch Cluster Kurulumu" }

Adım 1: Analiz

Standard analyzer ile her dökümanın title alanını analiz et:

Doc 1: "Elasticsearch ile Hızlı Arama"
  → ["elasticsearch", "ile", "hızlı", "arama"]

Doc 2: "Hızlı ve Güvenilir Veri Arama"
  → ["hızlı", "ve", "güvenilir", "veri", "arama"]

Doc 3: "Elasticsearch Cluster Kurulumu"
  → ["elasticsearch", "cluster", "kurulumu"]

Adım 2: Inverted Index Oluştur

Term Dictionary      Posting Lists
─────────────        ──────────────
arama           →    [Doc 1, Doc 2]
cluster         →    [Doc 3]
elasticsearch   →    [Doc 1, Doc 3]
güvenilir       →    [Doc 2]
hızlı           →    [Doc 1, Doc 2]
ile             →    [Doc 1]
kurulumu        →    [Doc 3]
ve              →    [Doc 2]
veri            →    [Doc 2]

Adım 3: Arama

Kullanıcı "hızlı elasticsearch" aratıyor:

Sorgu: "hızlı elasticsearch"
Analiz: ["hızlı", "elasticsearch"]

Adım 1: "hızlı" → Posting List = [Doc 1, Doc 2]
Adım 2: "elasticsearch" → Posting List = [Doc 1, Doc 3]
Adım 3: Birleştirme (OR - match query varsayılanı):
  Union: [Doc 1, Doc 2, Doc 3]

Adım 4: Scoring:
  Doc 1: "hızlı" ✅ + "elasticsearch" ✅ → Yüksek skor (ikisi de var)
  Doc 2: "hızlı" ✅ → Orta skor (biri var)
  Doc 3: "elasticsearch" ✅ → Orta skor (biri var)

Sonuç sıralaması: Doc 1 > Doc 2 ≈ Doc 3

Segments — Inverted Index'in Fiziksel Hali

Lucene, inverted index'i segment denilen dosyalarda saklar. Her segment kendi başına küçük bir inverted index'tir.

Segment Yaşam Döngüsü

1. Yeni dökümanlar → In-memory buffer
2. Buffer dolduğunda → Yeni segment yazılır (refresh)
3. Segment immutable (değiştirilemez)
4. Güncelleme = Eski dökümanı "deleted" işaretle + yeni segment'e yaz
5. Arka planda segments merge edilir (küçük segmentler → büyük segment)
Zaman →

t=0:  [Segment 1 (5 doc)]
t=1:  [Segment 1 (5 doc)] [Segment 2 (3 doc)]
t=2:  [Segment 1 (5 doc)] [Segment 2 (3 doc)] [Segment 3 (4 doc)]
t=3:  [Segment A (12 doc)] ← Merge: Segment 1+2+3 birleşti
t=4:  [Segment A (12 doc)] [Segment 4 (2 doc)]
...

Segment Bilgisi Sorgulama

// Index'in segment bilgisi
GET /products/_segments

// Yanıt (kısaltılmış):
{
  "_shards": { "total": 1, "successful": 1 },
  "indices": {
    "products": {
      "shards": {
        "0": [{
          "segments": {
            "_0": {
              "generation": 0,
              "num_docs": 150,
              "deleted_docs": 5,
              "size_in_bytes": 52428,
              "committed": true,
              "search": true
            },
            "_1": {
              "generation": 1,
              "num_docs": 50,
              "deleted_docs": 0,
              "size_in_bytes": 18000,
              "committed": true,
              "search": true
            }
          }
        }]
      }
    }
  }
}

Neden Segment'ler Immutable?

  1. Concurrent access güvenli: Okuma ve yazma birbirini engellemez

  2. Cache dostu: OS file system cache segmentleri bellekte tutar

  3. Sıkıştırma verimli: Bir kez yazıldığı için agresif sıkıştırma uygulanabilir

Force Merge — Segment Birleştirme

// Segmentleri birleştir (eski/read-only index'ler için)
POST /products/_forcemerge?max_num_segments=1

// DİKKAT: Bu işlem kaynak yoğundur, aktif yazma olan index'lerde çalıştırma!
// Sadece artık yazılmayan (closed, frozen, warm tier) index'ler için.

Refresh ve Flush — Near Real-Time Aramanın Sırrı

Refresh

Yeni eklenen dökümanlar hemen aranabilir olmaz. Refresh işlemi sırasında bellekteki buffer segment'e yazılır ve aranabilir hale gelir.

┌──────────────┐     Refresh (1sn)     ┌──────────────┐
│  In-Memory   │  ─────────────────→  │  Searchable   │
│   Buffer     │                       │   Segment     │
│  (aranamaz)  │                       │  (aranabilir) │
└──────────────┘                       └──────────────┘
// Refresh interval'i kontrol et
GET /products/_settings?filter_path=**.refresh_interval

// Refresh interval'i değiştir
PUT /products/_settings
{
  "index": {
    "refresh_interval": "5s"    // 5 saniyede bir refresh (varsayılan: 1s)
  }
}

// Toplu yazma sırasında refresh'i kapat (performans)
PUT /products/_settings
{
  "index": {
    "refresh_interval": "-1"    // Refresh kapalı
  }
}

// Manuel refresh tetikle
POST /products/_refresh

// Yazma sonrası hemen aranabilir yap
PUT /products/_doc/1?refresh=true
{
  "name": "Laptop"
}
// veya refresh=wait_for (daha verimli — bir sonraki scheduled refresh'i bekle)
PUT /products/_doc/2?refresh=wait_for
{
  "name": "Mouse"
}

💡 İpucu: Toplu veri yükleme sırasında refresh_interval: -1 yap, yükleme bitince tekrar 1s yap. Bu, bulk indexing performansını dramatik artırır.

Flush (Translog → Disk)

Refresh, segmenti aranabilir yapar ama hâlâ bellekte. Flush işlemi segmenti diske yazar ve translog'u temizler.

┌──────────────┐     Refresh      ┌──────────────┐     Flush       ┌──────────────┐
│  In-Memory   │  ──────────→    │  Searchable   │  ──────────→   │  Committed   │
│   Buffer     │                  │   Segment     │                │  Segment     │
│              │                  │  (memory)     │                │  (disk)      │
└──────────────┘                  └──────────────┘                └──────────────┘
                                         ↑
                                    Translog
                                  (durability)

Translog: Her yazma işlemi translog'a da yazılır. Sunucu çökerse, commit edilmemiş segment'ler translog'dan kurtarılır. WAL (Write-Ahead Log) mantığı — veritabanlarındaki redo log gibi.

// Translog ayarları
PUT /products/_settings
{
  "index": {
    "translog": {
      "durability": "request",      // Her request'te sync (güvenli, varsayılan)
      "sync_interval": "5s",        // Async mode'da sync aralığı
      "flush_threshold_size": "512mb" // Bu boyuta ulaşınca otomatik flush
    }
  }
}

// Manuel flush tetikle
POST /products/_flush

Doc Values — Sorting ve Aggregation İçin

Inverted index arama için optimize. Peki sıralama ve aggregation (gruplama) nasıl çalışır?

"Fiyata göre sırala" dediğinde, Elasticsearch her dökümanın fiyat değerine erişmeli. Inverted index'te bunu yapmak verimsiz:

Inverted Index (price field):
"100" → [Doc 3, Doc 7]
"200" → [Doc 1, Doc 5]
"300" → [Doc 2, Doc 4, Doc 6]

Sıralama için tüm terimleri tarayıp döküman bazında değer çıkarmak gerekir → YAVAŞ

Çözüm: Doc Values — sütunsal (columnar) veri yapısı:

Doc Values (price field):
Doc 1 → 200
Doc 2 → 300
Doc 3 → 100
Doc 4 → 300
Doc 5 → 200
Doc 6 → 300
Doc 7 → 100

Sıralama: Sadece bu sütunu oku ve sırala → HIZLI
// Doc values varsayılan olarak açıktır (keyword, numeric, date, boolean, ip)
// Text field'larda yoktur (fielddata kullanılır — önerilmez)

// Doc values'u kapat (disk tasarrufu, ama sıralama/aggregation yapılamaz)
PUT /logs
{
  "mappings": {
    "properties": {
      "message_id": {
        "type": "keyword",
        "doc_values": false    // Sıralama/aggregation gerekmiyorsa kapat
      }
    }
  }
}

Fielddata — Text Field'larda Sıralama

Text field'larda doc values yoktur. Sıralama için fielddata açılabilir ama önerilmez — çok fazla bellek tüketir:

// ÖNERİLMEZ — ama bilmen gerekir
PUT /products/_mapping
{
  "properties": {
    "name": {
      "type": "text",
      "fielddata": true    // Bellekte inverted index'in tersini oluşturur
    }
  }
}

// ÖNERİLEN — Multi-field kullan
PUT /products/_mapping
{
  "properties": {
    "name": {
      "type": "text",
      "fields": {
        "keyword": { "type": "keyword" }    // Sıralama için bunu kullan
      }
    }
  }
}

// Sıralama:
GET /products/_search
{
  "sort": [{ "name.keyword": "asc" }]
}

BM25 Scoring — Relevance Hesaplama

Inverted index'teki bilgilerle (TF, IDF, field length) relevance skoru hesaplanır.

BM25 Formülü (Basitleştirilmiş)

score(q, d) = Σ IDF(qi) × (TF(qi, d) × (k1 + 1)) / (TF(qi, d) + k1 × (1 - b + b × |d| / avgdl))

Parametreler:
- k1 = 1.2 (varsayılan) — TF doygunluk hızı
- b = 0.75 (varsayılan) — field length normalization etkisi
- |d| = dökümanın field uzunluğu
- avgdl = tüm dökümanların ortalama field uzunluğu

Basitleştirirsek üç bileşen:

1. TF (Term Frequency): Kelime dökümanda kaç kez geçiyor?

"kahve" kelimesi Doc 1'de 3 kez, Doc 2'de 1 kez geçiyor
→ Doc 1 daha yüksek TF skoru
Ama logaritmik — 3 kez ile 30 kez arasındaki fark küçük

2. IDF (Inverse Document Frequency): Kelime ne kadar nadir?

"ve" kelimesi 1000 dökümandan 900'ünde geçiyor → düşük IDF
"elasticsearch" kelimesi 1000'den 5'inde geçiyor → yüksek IDF
Nadir kelimeler daha değerli

3. Field Length Norm: Alan ne kadar kısa?

"Elasticsearch" başlıkta (3 kelime): yüksek norm skoru
"Elasticsearch" 5000 kelimelik makalede: düşük norm skoru
Kısa alanda eşleşme daha anlamlı

Explain API ile Skoru Anlama

// Belirli bir dökümanın skorunu açıkla
GET /products/_explain/1
{
  "query": {
    "match": {
      "name": "kahve makinesi"
    }
  }
}

// Yanıt (kısaltılmış):
{
  "matched": true,
  "explanation": {
    "value": 2.456,
    "description": "sum of:",
    "details": [
      {
        "value": 1.234,
        "description": "weight(name:kahve in 0) [PerFieldSimilarity]",
        "details": [
          { "value": 0.693, "description": "idf, computed as ..." },
          { "value": 1.780, "description": "tf, freq=1.0 ..." },
          { "value": 1.000, "description": "fieldNorm ..." }
        ]
      },
      {
        "value": 1.222,
        "description": "weight(name:makinesi in 0) [PerFieldSimilarity]"
      }
    ]
  }
}
// Tüm arama sonuçlarında explain görmek için
GET /products/_search
{
  "explain": true,
  "query": {
    "match": {
      "name": "kahve makinesi"
    }
  }
}

Inverted Index ve Veri Tipleri

Her field tipi için farklı bir index yapısı kullanılır:

Text Fields → Inverted Index

Field: "description" (text)
Değer: "Hızlı ve güvenilir arama motoru"

Inverted Index:
hızlı     → [Doc 1]
ve        → [Doc 1]
güvenilir → [Doc 1]
arama     → [Doc 1]
motoru    → [Doc 1]

Keyword Fields → Inverted Index (ama analiz yok)

Field: "status" (keyword)
Değer: "ACTIVE"

Inverted Index:
ACTIVE → [Doc 1, Doc 5, Doc 8]    // Tam değer, analiz yok

Numeric Fields → BKD Tree (Block K-Dimensional Tree)

Field: "price" (float)
Değerler: 100, 200, 150, 300, 250

BKD Tree:
     [200]
    /     \
 [100,150] [250,300]

Range query: price >= 150 AND price <= 300
→ Ağaçta hızlı arama → [150, 200, 250, 300]

Date Fields → BKD Tree (epoch millis olarak)

Field: "created_at" (date)
"2025-01-15" → 1736899200000 (epoch millis)

Range query: created_at >= "2025-01-01" AND created_at <= "2025-01-31"
→ Numeric range query gibi çalışır

Geo Fields → BKD Tree (geohash/quadtree)

Field: "location" (geo_point)
{lat: 41.01, lon: 28.97} → encoded value

Geo distance query → BKD tree'de uzaklık hesaplama

Java ile Analyze API

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch.indices.AnalyzeResponse;
import co.elastic.clients.elasticsearch.indices.analyze.AnalyzeToken;
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
import co.elastic.clients.transport.rest_client.RestClientTransport;
import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;

class Main {
    public static void main(String[] args) throws Exception {
        RestClient restClient = RestClient.builder(
            new HttpHost("localhost", 9200)
        ).build();
        ElasticsearchClient client = new ElasticsearchClient(
            new RestClientTransport(restClient, new JacksonJsonpMapper())
        );

        // Metni analiz et — standard analyzer
        AnalyzeResponse response = client.indices().analyze(a -> a
            .analyzer("standard")
            .text("Elasticsearch ile Hızlı Full-Text Arama!")
        );

        System.out.println("=== Standard Analyzer ===");
        for (AnalyzeToken token : response.tokens()) {
            System.out.printf("Token: %-20s | Position: %d | Offset: [%d, %d]%n",
                token.token(), token.position(),
                token.startOffset(), token.endOffset()
            );
        }

        // Turkish analyzer ile karşılaştır
        AnalyzeResponse turkishResponse = client.indices().analyze(a -> a
            .analyzer("turkish")
            .text("Türkiye'deki şirketler hızla büyüyor")
        );

        System.out.println("\n=== Turkish Analyzer ===");
        for (AnalyzeToken token : turkishResponse.tokens()) {
            System.out.printf("Token: %-20s | Position: %d%n",
                token.token(), token.position()
            );
        }

        // Belirli index'in field analyzer'ını kullan
        AnalyzeResponse fieldResponse = client.indices().analyze(a -> a
            .index("products")
            .field("description")
            .text("Tam otomatik espresso kahve makinesi")
        );

        System.out.println("\n=== Products/description Analyzer ===");
        for (AnalyzeToken token : fieldResponse.tokens()) {
            System.out.printf("Token: %-20s%n", token.token());
        }

        restClient.close();
    }
}

Performans İpuçları

1. Gereksiz Analiz Yapma

// E-posta adresi analiz edilmemeli
{
  "email": {
    "type": "keyword"    // ✅ Analiz yok, exact match
  }
}
// "keyword" yerine "text" yaparsan:
// "user@example.com" → ["user", "example.com"] — bölünür, arama zorlaşır

2. _source Boyutunu Kontrol Et

// Büyük field'ları _source'tan hariç tut
PUT /products
{
  "mappings": {
    "_source": {
      "excludes": ["full_description", "raw_html"]
    },
    "properties": {
      "full_description": { "type": "text" },
      "raw_html": { "type": "text", "index": false }
    }
  }
}

3. Norms'u Kapat

Scoring gerekmiyorsa (filter context'te kullanılan alanlar) norms kapatılarak bellek tasarrufu sağlanır:

{
  "tags": {
    "type": "text",
    "norms": false    // Field length normalization devre dışı
  }
}

Best Practices

Analyze API'yi kullan — Field'ların nasıl analiz edildiğini mutlaka test et

Doğru field tipi seç — Exact match = keyword, full-text search = text

Toplu yazma sırasında refresh'i kapatrefresh_interval: -1, bitince aç

Force merge'ü read-only index'lerde çalıştır — Aktif yazılan index'lerde çalıştırma

Explain API ile scoring debug et — Beklenmedik sıralama sonuçlarında explain kullan

Doc values'u gereksiz yere kapatma — Disk tasarrufu az, ama sorting/aggregation kaybedersin


Yaygın Hatalar

❌ "Neden exact match çalışmıyor?"

// YANLIŞ — text field'da term sorgusu
GET /products/_search
{
  "query": { "term": { "name": "Kahve Makinesi" } }
}
// "name" text field → "kahve makinesi" olarak analiz edilmiş
// "Kahve Makinesi" terimi inverted index'te YOK → sonuç 0

// DOĞRU — keyword field'da veya match sorgusu
GET /products/_search
{
  "query": { "match": { "name": "Kahve Makinesi" } }
}
// Sorgu da analiz edilir → "kahve" + "makinesi" → eşleşir ✅

❌ "Güncelleme yaptım ama eski sonuç geliyor"

Near real-time: Güncelleme sonrası ~1 saniye bekle veya ?refresh=true kullan. Refresh interval'den önce arama yaparsan eski sonuç döner.

❌ "Bellek şişiyor — fielddata açtım"

Text field'da fielddata açmak tüm inverted index'i belleğe alır. Milyonlarca terim = GB'larca bellek. Multi-field keyword kullan.

❌ "Segment sayısı çok fazla, performans düşük"

Çok fazla küçük segment arama performansını düşürür. ILM warm phase'de forcemerge yaparak segment sayısını azalt.


Özet

  • Inverted Index, "kelime → dökümanlar" eşlemesidir — geleneksel "döküman → kelimeler" yaklaşımının tersi

  • Term Dictionary (sıralı terim listesi) + Posting Lists (her terimin döküman listesi) = Inverted Index

  • Metin, inverted index'e yazılmadan önce analiz sürecinden geçer: Character Filter → Tokenizer → Token Filter

  • Segments immutable dosyalardır — güncelleme = silme + yeniden yazma

  • Refresh (~1sn) dökümanı aranabilir yapar, Flush diske kalıcı yazar

  • Doc Values sütunsal yapıdır — sorting ve aggregation için optimize

  • BM25 algoritması TF (kelime sıklığı), IDF (kelime nadirliği) ve Field Length'i kullanarak skor hesaplar

  • Farklı field tipleri farklı veri yapıları kullanır: text → inverted index, numeric/date/geo → BKD Tree

Bir sonraki bölümde CRUD İşlemlerine geçeceğiz — döküman ekleme, okuma, güncelleme ve silme operasyonlarını derinlemesine öğreneceğiz!