TEMEL
Mapping & Veri Tipleri
Mapping, bir index'teki dokümanların yapısını tanımlar — hangi field var, tipi ne, nasıl indexlenmeli. İlişkisel veritabanlarındaki schema tanımına benzer ama daha esnektir.
Kod örneği tercihiBu sayfadaki istemci örneklerini birlikte değiştirir.
Karar Rehberi
| Durum | Öneri | Örnek veya gerekçe |
|---|---|---|
| Explicit mapping | Uygun: Production index'ler | E-ticaret products index |
| Dynamic mapping | Uygun değil: Production | Sadece prototyping |
| Strict mode | Uygun: Veri tutarlılığı kritik | Fintech işlem kayıtları |
| Runtime fields | Uygun: Geçici analiz, maliyet düşürme | Log analiz sırasında |
| Multi-field | Uygun: Aynı veriyi farklı arama | İsim: text + keyword |
| Flattened | Uygun: Bilinmeyen key yapıları | Kullanıcı metadata |
Temel Veri Tipleri
| Tip | Kullanım | Indexleme | Örnek |
|---|---|---|---|
text |
Full-text arama | Analyzed (tokenize) | Ürün açıklaması |
keyword |
Exact match, sort, aggregation | Not analyzed | Status, email, SKU |
long / integer |
Tam sayılar | Numeric | Stok miktarı |
scaled_float |
Para birimi (scaling_factor) | Numeric | Fiyat (factor: 100) |
date |
Tarih/saat | Date | ISO 8601 formatı |
boolean |
True/false | Boolean | is_active |
object |
İç içe JSON | Flattened internally | Adres bilgisi |
nested |
Bağımsız alt dokümanlar | Separate Lucene docs | Ürün varyantları |
geo_point |
Enlem/boylam | Geo | Mağaza konumu |
dense_vector |
ML embeddings | HNSW/flat | Semantic search |
completion |
Autocomplete | FST | Arama önerileri |
Mapping Tanımlama
# Explicit mapping ile index oluşturma
curl -X PUT "http://localhost:9200/products" -H "Content-Type: application/json" -d'
{
"settings": {
"number_of_shards": 2,
"number_of_replicas": 1,
"analysis": {
"analyzer": {
"turkish_custom": {
"type": "custom",
"tokenizer": "standard",
"filter": ["lowercase", "turkish_stop", "turkish_stemmer"]
}
},
"filter": {
"turkish_stop": { "type": "stop", "stopwords": "_turkish_" },
"turkish_stemmer": { "type": "stemmer", "language": "turkish" }
}
}
},
"mappings": {
"dynamic": "strict",
"properties": {
"name": {
"type": "text",
"analyzer": "turkish_custom",
"fields": {
"keyword": { "type": "keyword", "ignore_above": 256 },
"suggest": { "type": "completion" }
}
},
"description": {
"type": "text",
"analyzer": "turkish_custom"
},
"category": { "type": "keyword" },
"price": { "type": "scaled_float", "scaling_factor": 100 },
"stock": { "type": "integer" },
"is_active": { "type": "boolean" },
"created_at": { "type": "date" },
"location": { "type": "geo_point" },
"tags": { "type": "keyword" },
"variants": {
"type": "nested",
"properties": {
"size": { "type": "keyword" },
"color": { "type": "keyword" },
"sku": { "type": "keyword" },
"stock": { "type": "integer" }
}
}
}
}
}'
# Mapping görüntüleme
curl -s "http://localhost:9200/products/_mapping?pretty"
# Field ekleme (mevcut index'e)
curl -X PUT "http://localhost:9200/products/_mapping" -H "Content-Type: application/json" -d'
{
"properties": {
"brand": { "type": "keyword" }
}
}'
// Index oluşturma + mapping (Fluent API)
public async Task CreateProductsIndexAsync()
{
var response = await _client.Indices.CreateAsync("products", c => c
.Settings(s => s
.NumberOfShards(2)
.NumberOfReplicas(1)
.Analysis(a => a
.Analyzers(an => an
.Custom("turkish_custom", ca => ca
.Tokenizer("standard")
.Filter("lowercase", "turkish_stop", "turkish_stemmer")))
.TokenFilters(tf => tf
.Stop("turkish_stop", st => st.Stopwords("_turkish_"))
.Stemmer("turkish_stemmer", st => st.Language("turkish")))))
.Mappings(m => m
.Dynamic(DynamicMapping.Strict)
.Properties<Product>(p => p
.Text(t => t.Name, t => t
.Analyzer("turkish_custom")
.Fields(f => f
.Keyword(k => k.Name("keyword"), k => k.IgnoreAbove(256))
.Completion(comp => comp.Name("suggest"))))
.Text(t => t.Description, t => t.Analyzer("turkish_custom"))
.Keyword(t => t.Category)
.ScaledFloat(t => t.Price, sf => sf.ScalingFactor(100))
.IntegerNumber(t => t.Stock)
.Boolean(t => t.IsActive)
.Date(t => t.CreatedAt)
.GeoPoint(t => t.Location)
.Keyword(t => t.Tags)
.Nested(t => t.Variants, n => n
.Properties<ProductVariant>(vp => vp
.Keyword(v => v.Size)
.Keyword(v => v.Color)
.Keyword(v => v.Sku)
.IntegerNumber(v => v.Stock))))));
if (!response.IsValidResponse)
throw new InvalidOperationException(
"Index creation failed: " + response.DebugInformation);
}
// Mapping güncelleme (yeni field ekleme)
public async Task AddBrandFieldAsync()
{
await _client.Indices.PutMappingAsync("products", m => m
.Properties<Product>(p => p
.Keyword(t => t.Brand)));
}
text vs keyword Karşılaştırması
| Özellik | text | keyword |
|---|---|---|
| Indexleme | Analyzed (tokenize edilir) | Olduğu gibi saklanır |
| Arama | Full-text (match, fuzzy) | Exact match (term) |
| Sorting | Hayır Doğrudan sort yapılamaz | Evet Sort yapılabilir |
| Aggregation | Hayır Agg yapılamaz | Evet Agg yapılabilir |
| Kullanım | Uzun metin, açıklama | ID, status, email, enum |
| Wildcard | match_phrase_prefix | wildcard query |
Örnek: E-ticaret'te ürün adı genellikle multi-field olarak tanımlanır: text olarak full-text arama için, keyword olarak exact match ve facet filtreleme için, completion olarak autocomplete için. Tek field'dan 3 farklı arama davranışı elde edersiniz.
Mapping değiştirilemez! Bir field'ın tipini değiştiremezsiniz (text → keyword). Tek yol: yeni index oluştur + reindex. Bu yüzden production'da mapping'i en baştan doğru tasarlayın. dynamic: strict kullanarak beklenmeyen field'ların indexlenmesini engelleyin.
Anti-Pattern: Dynamic Mapping in Production
# Mapping tanımlamadan doküman indexleme — TEHLİKELİ!
curl -X POST "http://localhost:9200/products/_doc/1" -H "Content-Type: application/json" -d'
{
"name": "Nike Air",
"price": "3499.99",
"metadata": { "key1": "val1", "key2": "val2", "key3": "val3" }
}'
# Sorunlar:
# 1. price STRING olarak algılanır (text+keyword) → range query çalışmaz!
# 2. metadata altındaki her yeni key yeni field oluşturur → mapping explosion
# 3. 1000 field limiti aşılınca index KIRILIR
curl -X PUT "http://localhost:9200/products" -H "Content-Type: application/json" -d'
{
"mappings": {
"dynamic": "strict",
"properties": {
"name": { "type": "text", "fields": { "keyword": { "type": "keyword" } } },
"price": { "type": "scaled_float", "scaling_factor": 100 },
"metadata": { "type": "flattened" }
}
}
}'
# dynamic:strict → bilinmeyen field gelirse REJECT (400 error)
# price → doğru tip, range query çalışır
# metadata → flattened tip, mapping explosion olmaz