Document Güncelleme — Update, Partial Update, Upsert
Update, Partial Update, Scripted Update, Upsert
Bir defterdeki yazıyı düzeltmeyi düşün. Kurşun kalemle yazdıysan silgisiyle silip yeniden yazarsın — ama kağıt hâlâ aynı. Tükenmez kalemle yazdıysan? O sayfayı yırtıp yenisine yazarsın. Elasticsearch ikinci yöntemi kullanır: dökümanlar immutable'dır — güncelleme aslında eski dökümanı silip yenisini yazmak demektir.
Ama endişelenme — bu "sil-yeniden yaz" işlemi perde arkasında olur ve sana şeffaf şekilde sunulur. Bu derste partial update, scripted update, upsert ve toplu güncelleme tekniklerini öğreneceğiz.
Güncelleme Nasıl Çalışır? — Perde Arkası
Elasticsearch'te güncelleme aslında üç adımlı bir işlemdir:
1. Mevcut dökümanı oku (_source'tan)
2. Değişiklikleri uygula (merge, script, replace)
3. Eski dökümanı "deleted" olarak işaretle + yeni dökümanı index'le
_version artar, _seq_no artar
Eski döküman segment merge sırasında fiziksel olarak temizlenir ┌─────────────────┐
Eski: │ _version: 1 │ ← "deleted" işaretlenir
│ name: "Laptop" │ (merge'de temizlenecek)
│ price: 15000 │
└─────────────────┘
↓ Güncelleme: price → 14000
┌─────────────────┐
Yeni: │ _version: 2 │ ← Yeni döküman yazılır
│ name: "Laptop" │
│ price: 14000 │
└─────────────────┘Bu mekanizmayı bilmek önemli çünkü:
Her güncelleme aslında bir tam yazma işlemi — performans maliyeti var
Sık güncellenen dökümanlar segment'lerde "deleted" döküman birikimine yol açar
_versionher güncellemede artar
Yöntem 1: PUT ile Tam Değiştirme (Replace)
PUT ile aynı ID'ye yeni döküman gönderdiğinde, tüm döküman değişir:
// Mevcut döküman
GET /products/_doc/1
// {"name": "Laptop", "price": 15000, "category": "Elektronik", "in_stock": true}
// PUT ile güncelleme — TÜM DÖKÜMAN DEĞİŞİR
PUT /products/_doc/1
{
"name": "Laptop Pro",
"price": 17000
}
// Sonuç:
GET /products/_doc/1
// {"name": "Laptop Pro", "price": 17000}
// ⚠️ "category" ve "in_stock" alanları KAYBOLDU!PUT, kısmi güncelleme yapmaz — tüm dökümanı replace eder. Bu genellikle istenen davranış değildir.
Yöntem 2: _update API ile Partial Update (Kısmi Güncelleme)
En yaygın güncelleme yöntemi. Sadece belirttiğin alanları günceller, geri kalanına dokunmaz:
// Mevcut döküman
// {"name": "Laptop", "price": 15000, "category": "Elektronik", "in_stock": true}
// Sadece fiyatı güncelle
POST /products/_update/1
{
"doc": {
"price": 14000
}
}
// Yanıt:
{
"_index": "products",
"_id": "1",
"_version": 2,
"result": "updated",
"_shards": { "total": 2, "successful": 2, "failed": 0 }
}
// Sonuç kontrol:
GET /products/_doc/1
// {"name": "Laptop", "price": 14000, "category": "Elektronik", "in_stock": true}
// ✅ Sadece price değişti, diğerleri aynen kaldıBirden Fazla Alanı Güncelle
POST /products/_update/1
{
"doc": {
"price": 13500,
"in_stock": false,
"updated_at": "2025-01-20T10:00:00Z"
}
}Yeni Alan Ekle
// Mevcut dökümana yeni alan ekle
POST /products/_update/1
{
"doc": {
"discount_percentage": 10,
"tags": ["indirim", "kampanya"]
}
}
// Döküman artık discount_percentage ve tags alanlarını da içerirİç İçe Obje Güncelleme
// Mevcut: {"specs": {"cpu": "i7", "ram": 16, "storage": 512}}
// İç objeyi güncelle
POST /products/_update/1
{
"doc": {
"specs": {
"ram": 32
}
}
}
// ⚠️ DİKKAT: Bu tüm "specs" objesini replace eder!
// Sonuç: {"specs": {"ram": 32}}
// cpu ve storage KAYBOLDU!
// Doğru yaklaşım: Tüm objeyi gönder
POST /products/_update/1
{
"doc": {
"specs": {
"cpu": "i7",
"ram": 32,
"storage": 512
}
}
}
// Veya scripted update kullan (aşağıda)detect_noop — Gereksiz Güncellemeyi Önle
Eğer gönderdiğin değer mevcut değerle aynıysa, Elasticsearch güncelleme yapmaz:
// Mevcut fiyat: 14000
POST /products/_update/1
{
"doc": {
"price": 14000 // Aynı değer
}
}
// Yanıt:
{
"result": "noop" // Hiçbir şey değişmedi — gereksiz yazma yapılmadı
}
// detect_noop'u kapat (her zaman yaz)
POST /products/_update/1
{
"doc": {
"price": 14000
},
"detect_noop": false // Aynı olsa bile yaz (_version artar)
}Yöntem 3: Scripted Update
Basit alan güncellemenin ötesinde — hesaplama, koşul, array manipülasyonu gerektiren güncellemeler için Painless script kullanılır.
Painless Script Nedir?
Elasticsearch'ün yerleşik scripting dili. Java'ya benzer syntax, güvenli ve hızlı.
Temel Scripted Update
// Fiyatı %10 artır
POST /products/_update/1
{
"script": {
"source": "ctx._source.price *= 1.10"
}
}
// Fiyattan 500 TL düş
POST /products/_update/1
{
"script": {
"source": "ctx._source.price -= 500"
}
}Parametreli Script
// Parametreli script (güvenli ve performanslı — script derleme cache'lenir)
POST /products/_update/1
{
"script": {
"source": "ctx._source.price -= params.discount",
"params": {
"discount": 1000
}
}
}
// Stok sayısını güncelle
POST /products/_update/1
{
"script": {
"source": "ctx._source.stock_count -= params.quantity",
"params": {
"quantity": 2
}
}
}💡 İpucu: Script'lerde hardcoded değer yerine params kullan. Elasticsearch script'leri derler ve cache'ler. Parametre değişse bile aynı derleme kullanılır — parametre inline olursa her farklı değer yeni derleme gerektirir.
Koşullu Güncelleme
// Stok sıfırsa "in_stock" false yap
POST /products/_update/1
{
"script": {
"source": """
if (ctx._source.stock_count <= 0) {
ctx._source.in_stock = false;
ctx._source.stock_count = 0;
}
"""
}
}
// Fiyat belirli bir değerin altına düşmesin
POST /products/_update/1
{
"script": {
"source": """
ctx._source.price -= params.discount;
if (ctx._source.price < params.min_price) {
ctx._source.price = params.min_price;
}
""",
"params": {
"discount": 5000,
"min_price": 1000
}
}
}Array Manipülasyonu
// Array'e eleman ekle
POST /products/_update/1
{
"script": {
"source": "ctx._source.tags.add(params.tag)",
"params": { "tag": "yeni-sezon" }
}
}
// Array'den eleman çıkar
POST /products/_update/1
{
"script": {
"source": "ctx._source.tags.remove(ctx._source.tags.indexOf(params.tag))",
"params": { "tag": "eski-sezon" }
}
}
// Array'de eleman yoksa ekle (duplicate engelleme)
POST /products/_update/1
{
"script": {
"source": """
if (!ctx._source.tags.contains(params.tag)) {
ctx._source.tags.add(params.tag);
}
""",
"params": { "tag": "premium" }
}
}
// Birden fazla eleman ekle
POST /products/_update/1
{
"script": {
"source": "ctx._source.tags.addAll(params.new_tags)",
"params": {
"new_tags": ["2025", "yeni-koleksiyon"]
}
}
}İleri Script Örnekleri
// Tarih alanını güncelle
POST /products/_update/1
{
"script": {
"source": "ctx._source.updated_at = params.now",
"params": {
"now": "2025-01-20T15:30:00Z"
}
}
}
// Yeni bir iç obje alanı ekle
POST /products/_update/1
{
"script": {
"source": """
if (ctx._source.containsKey('stats') == false) {
ctx._source.stats = new HashMap();
}
ctx._source.stats.view_count = (ctx._source.stats.view_count ?: 0) + 1;
"""
}
}
// Koşula göre güncelle veya silme
POST /products/_update/1
{
"script": {
"source": """
if (ctx._source.stock_count <= 0) {
ctx.op = 'delete'; // Dökümanı sil!
} else {
ctx.op = 'none'; // Hiçbir şey yapma (noop)
}
"""
}
}
// ctx.op seçenekleri: 'index' (güncelle), 'delete' (sil), 'none' (noop)Yöntem 4: Upsert — Varsa Güncelle, Yoksa Oluştur
Çok yaygın bir pattern: döküman varsa güncelle, yoksa yenisini oluştur.
doc_as_upsert
// Döküman varsa güncelle, yoksa bu dökümanı oluştur
POST /products/_update/1
{
"doc": {
"name": "Laptop",
"price": 15000,
"category": "Elektronik"
},
"doc_as_upsert": true
}
// ID=1 yoksa: Yeni döküman oluşturulur (result: "created")
// ID=1 varsa: Mevcut döküman güncellenir (result: "updated")upsert + script
// Varsa: Script çalıştır (view_count artır)
// Yoksa: upsert dökümanını oluştur
POST /products/_update/1
{
"script": {
"source": "ctx._source.view_count += params.increment",
"params": { "increment": 1 }
},
"upsert": {
"name": "Yeni Ürün",
"price": 0,
"view_count": 1
}
}
// İlk çağrı (döküman yok): upsert dökümanı oluşturulur (view_count: 1)
// Sonraki çağrılar: view_count her seferinde 1 artarscripted_upsert
// Her durumda (var/yok) script çalıştır
POST /products/_update/1
{
"scripted_upsert": true,
"script": {
"source": """
if (ctx.op == 'create') {
ctx._source.name = params.name;
ctx._source.view_count = 1;
} else {
ctx._source.view_count += 1;
}
""",
"params": { "name": "Dinamik Ürün" }
},
"upsert": {}
}Update By Query — Toplu Güncelleme
Belirli kriterlere uyan tüm dökümanları güncelle:
// Tüm "Elektronik" kategorisindeki ürünlere %10 indirim
POST /products/_update_by_query
{
"query": {
"term": { "category.keyword": "Elektronik" }
},
"script": {
"source": "ctx._source.price = (long)(ctx._source.price * 0.90)",
"lang": "painless"
}
}
// Yanıt:
{
"took": 150,
"timed_out": false,
"total": 250, // Toplam eşleşen
"updated": 250, // Güncellenen
"deleted": 0,
"batches": 1,
"version_conflicts": 0, // Çakışma
"noops": 0,
"failures": []
}Update By Query — Pratik Örnekler
// Stokta olmayan ürünleri işaretle
POST /products/_update_by_query
{
"query": {
"term": { "in_stock": false }
},
"script": {
"source": """
ctx._source.status = 'UNAVAILABLE';
ctx._source.updated_at = params.now;
""",
"params": {
"now": "2025-01-20T10:00:00Z"
}
}
}
// Eski ürünlere "archive" tag'i ekle
POST /products/_update_by_query
{
"query": {
"range": {
"created_at": {
"lt": "2023-01-01"
}
}
},
"script": {
"source": """
if (ctx._source.tags == null) {
ctx._source.tags = new ArrayList();
}
if (!ctx._source.tags.contains('archived')) {
ctx._source.tags.add('archived');
}
"""
}
}Update By Query — Parametreler
// Sadece ilk 100 dökümanı güncelle
POST /products/_update_by_query?max_docs=100
{
"query": { "match_all": {} },
"script": { "source": "ctx._source.batch = 1" }
}
// Çakışmalarda devam et (proceed) veya dur (abort)
POST /products/_update_by_query?conflicts=proceed
{
"query": { "match_all": {} },
"script": { "source": "ctx._source.migrated = true" }
}
// Asenkron çalıştır (büyük veri setleri için)
POST /products/_update_by_query?wait_for_completion=false
{
"query": { "match_all": {} },
"script": { "source": "ctx._source.reprocessed = true" }
}
// Yanıt: {"task": "node-1:12345"}
// Task durumunu kontrol et:
GET /_tasks/node-1:12345Update By Query — Slicing (Paralel İşleme)
// 5 dilime böl — paralel güncelleme
POST /products/_update_by_query?slices=5
{
"query": { "match_all": {} },
"script": { "source": "ctx._source.version = 2" }
}
// 5 paralel iş parçası oluşturulur — büyük veri setlerinde çok daha hızlı
// Otomatik dilim sayısı (shard sayısı kadar)
POST /products/_update_by_query?slices=auto
{
"query": { "match_all": {} },
"script": { "source": "ctx._source.processed = true" }
}Retry on Conflict
Concurrent güncelleme durumlarında çakışma olabilir:
// _update API'de retry
POST /products/_update/1?retry_on_conflict=3
{
"doc": {
"price": 14000
}
}
// Çakışma olursa 3 kez daha dener
// _update_by_query'de
POST /products/_update_by_query?conflicts=proceed
{
"query": { "match_all": {} },
"script": { "source": "ctx._source.count += 1" }
}
// Çakışan dökümanları atlayarak devam ederJava ile Document Güncelleme
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch.core.*;
import co.elastic.clients.elasticsearch._types.Script;
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
import co.elastic.clients.json.JsonData;
import co.elastic.clients.transport.rest_client.RestClientTransport;
import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import java.util.Map;
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())
);
// 1. Partial Update
UpdateResponse<Map> updateResponse = client.update(u -> u
.index("products")
.id("1")
.doc(Map.of("price", 14000, "in_stock", false)),
Map.class
);
System.out.println("Result: " + updateResponse.result());
// 2. Scripted Update
UpdateResponse<Map> scriptResponse = client.update(u -> u
.index("products")
.id("1")
.script(s -> s
.inline(i -> i
.source("ctx._source.price -= params.discount")
.params("discount", JsonData.of(500))
)
),
Map.class
);
System.out.println("Scripted: " + scriptResponse.result());
// 3. Upsert
UpdateResponse<Map> upsertResponse = client.update(u -> u
.index("products")
.id("999")
.doc(Map.of("view_count", 1))
.upsert(Map.of(
"name", "Yeni Ürün",
"price", 0,
"view_count", 1
)),
Map.class
);
System.out.println("Upsert: " + upsertResponse.result());
// İlk çağrı: "created", sonraki: "updated"
// 4. Retry on conflict
UpdateResponse<Map> retryResponse = client.update(u -> u
.index("products")
.id("1")
.doc(Map.of("price", 13500))
.retryOnConflict(3),
Map.class
);
// 5. Update by query
var ubqResponse = client.updateByQuery(u -> u
.index("products")
.query(q -> q
.term(t -> t
.field("category.keyword")
.value("Elektronik")
)
)
.script(s -> s
.inline(i -> i
.source("ctx._source.sale = true")
)
)
);
System.out.println("Updated: " + ubqResponse.updated() + " docs");
restClient.close();
}
}Best Practices
✅ Partial update kullan — PUT ile replace yerine _update API ile kısmi güncelleme
✅ Script'lerde `params` kullan — Hardcoded değer yerine parametreli script (cache dostu)
✅ `retry_on_conflict` ekle — Concurrent güncelleme senaryolarında
✅ Upsert pattern'ı kullan — Varsa güncelle yoksa oluştur — doc_as_upsert: true
✅ Büyük toplu güncellemelerde `slices` kullan — Paralel işleme ile hızlandır
✅ `detect_noop: true` varsayılanı koru — Gereksiz yazma işlemlerini önler
Gerçek Dünya Senaryosu: E-Ticaret Fiyat Güncelleme
Bir e-ticaret platformunda toplu fiyat güncelleme senaryosu:
// Senaryo: Yeni yıl kampanyası — tüm elektronik ürünlere %15 indirim
// Ama minimum fiyat 500 TL'nin altına düşmemeli
// 1. Kaç ürün etkilenecek kontrol et
GET /products/_count
{
"query": {
"bool": {
"must": [
{ "term": { "category.keyword": "Elektronik" } },
{ "term": { "in_stock": true } }
]
}
}
}
// 2. Toplu güncelleme — slices ile paralel
POST /products/_update_by_query?slices=auto&conflicts=proceed
{
"query": {
"bool": {
"must": [
{ "term": { "category.keyword": "Elektronik" } },
{ "term": { "in_stock": true } }
]
}
},
"script": {
"source": """
// Orijinal fiyatı sakla
if (!ctx._source.containsKey('original_price')) {
ctx._source.original_price = ctx._source.price;
}
// %15 indirim uygula
def discounted = (long)(ctx._source.original_price * (1 - params.discount_rate));
// Minimum fiyat kontrolü
ctx._source.price = Math.max(discounted, params.min_price);
// Kampanya bilgisi ekle
ctx._source.campaign = params.campaign_name;
ctx._source.campaign_end = params.campaign_end;
""",
"params": {
"discount_rate": 0.15,
"min_price": 500,
"campaign_name": "yeni-yil-2025",
"campaign_end": "2025-01-31T23:59:59Z"
}
}
}
// 3. Kampanya bitince fiyatları geri al
POST /products/_update_by_query
{
"query": {
"term": { "campaign": "yeni-yil-2025" }
},
"script": {
"source": """
if (ctx._source.containsKey('original_price')) {
ctx._source.price = ctx._source.original_price;
ctx._source.remove('original_price');
ctx._source.remove('campaign');
ctx._source.remove('campaign_end');
}
"""
}
}Yaygın Hatalar
❌ "PUT ile partial update yapıyorum"
PUT tüm dökümanı replace eder. Sadece fiyatı güncellemek isterken diğer alanları kaybedersin. POST /_update kullan.
❌ "İç objeyi partial update ile güncelliyorum"
doc ile iç obje güncelleme, o objenin tamamını replace eder. Script kullan veya tüm objeyi gönder.
❌ "Script'te inline değer kullanıyorum"
// ❌ Her farklı değer yeni derleme gerektirir
"source": "ctx._source.price -= 500"
"source": "ctx._source.price -= 1000"
// ✅ Aynı script, farklı parametre — cache'ten çalışır
"source": "ctx._source.price -= params.discount"❌ "update_by_query sırasında index'e yazma devam ediyor"
update_by_query, çalışırken snapshot alır. Arada yeni dökümanlar eklenirse bunlar güncellenmez. İdeal olarak yazma trafiğini durdur veya conflicts=proceed kullan.
❌ "Çok sık güncelleme yapıyorum"
Her güncelleme = sil + yeniden yaz. Saniyede yüzlerce güncelleme yapıyorsan segment'lerde "deleted" doküman birikir, merge yükü artar. Mümkünse güncellemeleri batch'le.
Özet
Elasticsearch'te güncelleme aslında sil + yeniden yaz işlemidir — dökümanlar immutable
PUT tüm dökümanı replace eder, _update ile partial update yapılır
Scripted update ile hesaplama, koşul ve array manipülasyonu yapılabilir
Upsert (doc_as_upsert) ile varsa güncelle, yoksa oluştur pattern'ı
update_by_query ile sorguya uyan tüm dökümanları toplu güncelle
detect_noop gereksiz yazmaları önler — aynı değeri gönderirsen "noop" döner
retry_on_conflict concurrent güncelleme çakışmalarını handle eder
Script'lerde params kullanmak performans için kritik — script derleme cache'lenir
Bir sonraki derste Document Silme işlemlerini öğreneceğiz — DELETE, Delete by Query ve Bulk API!
AI Asistan
Sorularını yanıtlamaya hazır