← Kursa Dön
📄 Text · 25 min

Document Silme — DELETE ve Delete by Query

DELETE, Delete by Query, Bulk API

Bir kütüphaneden kitap çıkarmayı düşün. Tek bir kitabı raftan alabilirsin (DELETE). "2010 öncesi tüm kitapları çıkarın" diyebilirsin (Delete by Query). Ya da bir liste verirsin — "Bu 50 kitabı çıkarın" (Bulk DELETE). Elasticsearch'te silme işlemi de tam olarak bu üç yolla yapılır.

Ama bilmen gereken önemli bir detay var: Elasticsearch'te silme anında fiziksel olarak gerçekleşmez. Döküman "deleted" olarak işaretlenir ve arka planda segment merge sırasında temizlenir. Bu, performans ve tutarlılık için kritik bir tasarım kararı.


Silme Nasıl Çalışır? — Perde Arkası

1. DELETE /products/_doc/1

2. Elasticsearch, dökümanı segmentte "deleted" olarak işaretler
   → Döküman hâlâ diskte duruyor
   → Ama aramalarda görünmüyor (filtreleniyor)

3. Segment merge sırasında:
   → Deleted dökümanlar fiziksel olarak temizlenir
   → Disk alanı geri kazanılır

┌──────────────┐         ┌──────────────┐
│ Segment A    │  Merge  │ Segment C    │
│ Doc 1 ✅     │  ───→   │ Doc 1 ✅     │
│ Doc 2 ❌ del │         │ Doc 3 ✅     │
│ Doc 3 ✅     │         │              │
└──────────────┘         └──────────────┘
    Doc 2 silindi!            Temizlendi!

Bu mekanizmanın sonuçları:

  • Silme hemen disk alanı geri vermez — merge beklemek gerekir

  • Silme performansı yüksektir — fiziksel silme yapmaz, sadece işaretler

  • Deleted dökümanlar bellek ve disk kullanmaya devam eder — merge olana kadar


DELETE — Tekil Döküman Silme

En temel silme operasyonu:

// ID ile döküman sil
DELETE /products/_doc/1

// Yanıt (başarılı):
{
  "_index": "products",
  "_id": "1",
  "_version": 4,
  "result": "deleted",
  "_shards": {
    "total": 2,
    "successful": 2,
    "failed": 0
  },
  "_seq_no": 15,
  "_primary_term": 1
}

Var Olmayan Dökümanı Silme

DELETE /products/_doc/999

// Yanıt (HTTP 404):
{
  "_index": "products",
  "_id": "999",
  "_version": 1,
  "result": "not_found",
  "_shards": {
    "total": 2,
    "successful": 2,
    "failed": 0
  }
}

result: "not_found" — döküman zaten yoktu. Bu bir hata değil, HTTP 404 döner. İdempotent davranış: aynı silme isteğini tekrar göndersen sorun çıkmaz.

Versiyonlu Silme (Optimistic Concurrency)

// Sadece belirli versiyondaysa sil
DELETE /products/_doc/1?if_seq_no=15&if_primary_term=1

// seq_no veya primary_term uyuşmazsa:
// 409 Conflict hatası

Routing ile Silme

// Custom routing ile index'lenen dökümanı silerken routing belirtmek ZORUNLU
DELETE /orders/_doc/order-1?routing=customer-42

// Routing belirtmezsen yanlış shard'a gider — döküman bulunamaz
DELETE /orders/_doc/order-1
// result: "not_found" — çünkü yanlış shard'a baktı!

Refresh Parametresi

// Silme sonrası hemen arama sonuçlarından kaybolsun
DELETE /products/_doc/1?refresh=true

// Bir sonraki refresh cycle'ı bekle (daha verimli)
DELETE /products/_doc/1?refresh=wait_for

Delete By Query — Toplu Silme

Belirli kriterlere uyan tüm dökümanları sil:

// Stokta olmayan tüm ürünleri sil
POST /products/_delete_by_query
{
  "query": {
    "term": {
      "in_stock": false
    }
  }
}

// Yanıt:
{
  "took": 85,
  "timed_out": false,
  "total": 150,            // Eşleşen toplam
  "deleted": 150,          // Silinen
  "batches": 1,            // Kaç batch'te yapıldı
  "version_conflicts": 0,  // Çakışma
  "noops": 0,
  "retries": {
    "bulk": 0,
    "search": 0
  },
  "failures": []
}

Delete By Query Örnekleri

// 2023 öncesi tüm logları sil
POST /app-logs-*/_delete_by_query
{
  "query": {
    "range": {
      "@timestamp": {
        "lt": "2023-01-01"
      }
    }
  }
}

// Belirli bir kullanıcının tüm verilerini sil (GDPR compliance)
POST /user-data/_delete_by_query
{
  "query": {
    "term": {
      "user_id": "user-12345"
    }
  }
}

// Birden fazla koşul ile silme
POST /products/_delete_by_query
{
  "query": {
    "bool": {
      "must": [
        { "term": { "status": "discontinued" } },
        { "range": { "stock_count": { "lte": 0 } } },
        { "range": { "updated_at": { "lt": "2024-01-01" } } }
      ]
    }
  }
}

// Tüm dökümanları sil (index'i silmeden)
POST /products/_delete_by_query
{
  "query": {
    "match_all": {}
  }
}
// ⚠️ Bu mapping ve settings'i korur — sadece veriyi siler
// Index'i tamamen silmek istiyorsan: DELETE /products

Delete By Query Parametreleri

// Çakışmalarda devam et
POST /products/_delete_by_query?conflicts=proceed
{
  "query": { "match_all": {} }
}

// Paralel işleme (slices)
POST /products/_delete_by_query?slices=5
{
  "query": {
    "range": { "created_at": { "lt": "2024-01-01" } }
  }
}

// Otomatik slice sayısı (shard sayısı kadar)
POST /products/_delete_by_query?slices=auto
{
  "query": { "match_all": {} }
}

// Maksimum silme sayısı
POST /products/_delete_by_query?max_docs=1000
{
  "query": { "match_all": {} }
}
// Sadece ilk 1000 eşleşeni siler

// Asenkron çalıştır (büyük silmelerde)
POST /products/_delete_by_query?wait_for_completion=false
{
  "query": {
    "range": { "created_at": { "lt": "2024-01-01" } }
  }
}
// Yanıt: {"task": "node-1:54321"}

// Task durumunu kontrol et
GET /_tasks/node-1:54321

// Task'ı iptal et
POST /_tasks/node-1:54321/_cancel

Delete By Query — Scroll Size

// Dahili scroll boyutunu ayarla (varsayılan 1000)
POST /products/_delete_by_query?scroll_size=5000
{
  "query": { "match_all": {} }
}
// Daha büyük scroll_size = daha az batch = daha hızlı ama daha fazla bellek

Index Silme vs Delete By Query

İki yaklaşımın farkını anlamak önemli:

// Yöntem 1: Index'i komple sil (ÇOK HIZLI)
DELETE /app-logs-2024.01.15
// Anında tamamlanır — sadece metadata silme
// Mapping, settings, tüm veri gider

// Yöntem 2: Dökümanları sil, index'i koru (YAVAŞ)
POST /app-logs-2024.01.15/_delete_by_query
{
  "query": { "match_all": {} }
}
// Her dökümanı tek tek "deleted" işaretler — dakikalar sürebilir
// Mapping ve settings korunur
// Disk alanı hemen geri gelmez (merge bekler)
ÖzellikDELETE /index_delete_by_query
HızAnlık (metadata silme)Yavaş (döküman bazlı)
Mapping/SettingsSilinirKorunur
Disk geri kazanımıAnındaMerge sonrası
KullanımLog index'leri, geçici verilerSeçici silme

💡 İpucu: Zaman bazlı veriler (loglar, metrikler) için günlük/haftalık index kullan. "30 günden eski logları sil" demek yerine eski index'i komple sil — çok daha hızlı ve temiz.


Bulk API ile Silme

Birden fazla dökümanı tek request'te sil:

POST /_bulk
{"delete": {"_index": "products", "_id": "1"}}
{"delete": {"_index": "products", "_id": "2"}}
{"delete": {"_index": "products", "_id": "3"}}
{"delete": {"_index": "products", "_id": "4"}}
{"delete": {"_index": "products", "_id": "5"}}

Dikkat: delete action'da body satırı olmaz! Diğer action'lardan (index, create, update) farklı olarak sadece action satırı yeterli.

Bulk Karışık İşlemler

POST /_bulk
{"index":  {"_index": "products", "_id": "10"}}
{"name": "Yeni Ürün", "price": 500}
{"update": {"_index": "products", "_id": "11"}}
{"doc": {"price": 600}}
{"delete": {"_index": "products", "_id": "12"}}
{"delete": {"_index": "products", "_id": "13"}}

Belirli Index'te Bulk Silme

POST /products/_bulk
{"delete": {"_id": "1"}}
{"delete": {"_id": "2"}}
{"delete": {"_id": "3"}}

Gerçek Dünya Senaryoları

Senaryo 1: GDPR — Kullanıcı Verisi Silme

Avrupa'da GDPR yasası gereği, kullanıcı talepte bulunursa tüm kişisel verileri silinmeli:

// 1. Kullanıcının tüm index'lerdeki verilerini bul
GET /users/_doc/user-12345
GET /orders/_search
{ "query": { "term": { "user_id": "user-12345" } } }
GET /reviews/_search
{ "query": { "term": { "user_id": "user-12345" } } }
GET /activity-logs-*/_search
{ "query": { "term": { "user_id": "user-12345" } } }

// 2. Her index'ten sil
DELETE /users/_doc/user-12345

POST /orders/_delete_by_query
{ "query": { "term": { "user_id": "user-12345" } } }

POST /reviews/_delete_by_query
{ "query": { "term": { "user_id": "user-12345" } } }

POST /activity-logs-*/_delete_by_query
{ "query": { "term": { "user_id": "user-12345" } } }

// 3. Force merge ile fiziksel olarak temizle (disk'ten de kaldır)
POST /users/_forcemerge?only_expunge_deletes=true
POST /orders/_forcemerge?only_expunge_deletes=true
POST /reviews/_forcemerge?only_expunge_deletes=true
// only_expunge_deletes=true: Sadece deleted dökümanları temizle

Senaryo 2: Log Retention — Eski Logları Temizleme

// Yöntem 1 (Önerilen): Index silme — ÇOK HIZLI
// Günlük index'ler: app-logs-2024.01.01, app-logs-2024.01.02, ...
DELETE /app-logs-2024.01.*
// Ocak 2024'ün tüm log index'leri silindi — milisaniyede

// Yöntem 2 (Önerilmez): Delete by query — YAVAŞ
POST /app-logs-*/_delete_by_query
{
  "query": {
    "range": {
      "@timestamp": { "lt": "2024-02-01" }
    }
  }
}
// Her dökümanı tek tek siler — saatler sürebilir

// Yöntem 3 (Otomatik): ILM Policy
PUT /_ilm/policy/log-retention
{
  "policy": {
    "phases": {
      "delete": {
        "min_age": "30d",
        "actions": {
          "delete": {}
        }
      }
    }
  }
}
// 30 günden eski index'ler otomatik silinir

Senaryo 3: E-Ticaret — Ürün Kaldırma

// Tek ürün kaldırma
DELETE /products/_doc/PROD-2025-001

// Marka toplu kaldırma (markayı platform'dan çıkarma)
POST /products/_delete_by_query
{
  "query": {
    "term": { "brand.keyword": "BannedBrand" }
  }
}

// Süresi dolmuş kampanya ürünlerini temizle
POST /products/_delete_by_query
{
  "query": {
    "bool": {
      "must": [
        { "exists": { "field": "campaign_end" } }
      ],
      "filter": [
        { "range": { "campaign_end": { "lt": "now" } } },
        { "term": { "auto_remove": true } }
      ]
    }
  }
}

Force Merge ile Deleted Döküman Temizleme

Silinen dökümanlar segment merge'e kadar disk'te kalır. Bunu hızlandırabilirsin:

// Sadece deleted dökümanları temizle (güvenli)
POST /products/_forcemerge?only_expunge_deletes=true

// Segmentleri birleştir + deleted temizle (daha agresif)
POST /products/_forcemerge?max_num_segments=1

// Tüm index'lerde temizlik
POST /_forcemerge?only_expunge_deletes=true

⚠️ Dikkat: _forcemerge kaynak yoğun bir işlemdir. Aktif yazma olan index'lerde çalıştırma — read-only veya yazma olmayan index'lerde kullan.

Deleted Döküman Sayısını Kontrol Etme

// Segment bilgisinde deleted_docs gösterir
GET /products/_segments

// _stats ile
GET /products/_stats/docs

// Yanıt:
{
  "indices": {
    "products": {
      "primaries": {
        "docs": {
          "count": 1000,     // Aktif döküman
          "deleted": 150     // Silinmiş ama henüz temizlenmemiş
        }
      }
    }
  }
}

// _cat ile hızlı bakış
GET /_cat/indices/products?v&h=index,docs.count,docs.deleted,store.size

Java ile Document Silme

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch.core.*;
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())
        );

        // 1. Tekil silme
        DeleteResponse deleteResponse = client.delete(d -> d
            .index("products")
            .id("1")
        );
        System.out.println("Result: " + deleteResponse.result());
        // "deleted" veya "not_found"

        // 2. Routing ile silme
        DeleteResponse routedDelete = client.delete(d -> d
            .index("orders")
            .id("order-1")
            .routing("customer-42")
        );

        // 3. Delete by query
        var dbqResponse = client.deleteByQuery(d -> d
            .index("products")
            .query(q -> q
                .term(t -> t
                    .field("in_stock")
                    .value(false)
                )
            )
        );
        System.out.println("Deleted: " + dbqResponse.deleted());
        System.out.println("Total matched: " + dbqResponse.total());
        System.out.println("Conflicts: " + dbqResponse.versionConflicts());

        // 4. Delete by query — range
        var rangeDelete = client.deleteByQuery(d -> d
            .index("app-logs-*")
            .query(q -> q
                .range(r -> r
                    .field("@timestamp")
                    .lt(co.elastic.clients.json.JsonData.of("2024-01-01"))
                )
            )
            .slices(s -> s.value(5))    // Paralel silme
        );
        System.out.println("Old logs deleted: " + rangeDelete.deleted());

        // 5. Bulk silme
        var bulkResponse = client.bulk(b -> b
            .index("products")
            .operations(ops -> ops.delete(d -> d.id("10")))
            .operations(ops -> ops.delete(d -> d.id("11")))
            .operations(ops -> ops.delete(d -> d.id("12")))
        );
        System.out.println("Bulk errors: " + bulkResponse.errors());
        bulkResponse.items().forEach(item ->
            System.out.printf("ID: %s → %s%n", item.id(), item.result())
        );

        // 6. Index silme
        var indexDeleteResponse = client.indices().delete(d -> d
            .index("old-index")
        );
        System.out.println("Index deleted: " + indexDeleteResponse.acknowledged());

        restClient.close();
    }
}

Tehlikeli İşlemler ve Güvenlik

Yanlışlıkla Index Silmeyi Engelleme

// Wildcard ile index silmeyi engelle
PUT /_cluster/settings
{
  "persistent": {
    "action.destructive_requires_name": true
  }
}

// Artık bu ÇALIŞMAZ:
DELETE /app-logs-*
// ❌ Wildcard ile silme engellendi!

// Ama bu çalışır:
DELETE /app-logs-2024.01.15
// ✅ Tam isim ile silme — izin verilir

Index Silme Koruması

// Index'i write-protected yap (silmeye karşı koruma)
PUT /important-data/_settings
{
  "index.blocks.read_only": true
}

// Artık DELETE çalışmaz:
DELETE /important-data/_doc/1
// ❌ 403 Forbidden — index read-only

// Korumayı kaldır
PUT /important-data/_settings
{
  "index.blocks.read_only": false
}

Alias ile Güvenli Silme

// Uygulama alias üzerinden çalışır
// Alias üzerinden index silme yapılamaz — güvenlik katmanı

DELETE /products-alias
// ❌ Sadece alias'ı siler, arkadaki index'e dokunmaz

// Index'i silmek için gerçek adını bilmelisin
DELETE /products-v2
// ✅ Bu gerçek index'i siler

Performans İpuçları

1. Toplu Silme için Index Silmeyi Tercih Et

Yöntem 1 — delete_by_query:
  1 milyon döküman silme → ~30 dakika
  Disk geri kazanımı → merge sonrası (saatler)

Yöntem 2 — Index silme:
  1 milyon döküman silme → ~10 milisaniye
  Disk geri kazanımı → anında

2. Delete by Query'de Slices Kullan

// 5 paralel dilim — 5x hızlı
POST /big-index/_delete_by_query?slices=5
{
  "query": { "range": { "date": { "lt": "2024-01-01" } } }
}

3. Asenkron Çalıştır

// Büyük silme işlemlerini asenkron çalıştır
POST /big-index/_delete_by_query?wait_for_completion=false
{
  "query": { "match_all": {} }
}
// Task ID al, arka planda çalışsın

4. Force Merge ile Disk Geri Kazan

// Silme sonrası disk alanını geri al
POST /products/_forcemerge?only_expunge_deletes=true

Best Practices

Zaman bazlı veriler için index silme kullan — delete_by_query yerine eski index'i komple sil

`action.destructive_requires_name: true` ayarla — Wildcard ile yanlışlıkla silmeyi engelle

Büyük silmelerde `slices` kullan — Paralel işleme ile hızlandır

Custom routing varsa silmede de routing belirt — Yoksa döküman bulunamaz

ILM ile otomatik retention yap — Manuel silme yerine policy tanımla

Force merge ile deleted dökümanları temizle — Disk geri kazanımı için


Yaygın Hatalar

❌ "delete_by_query ile tüm index'i temizliyorum"

Index'i silip yeniden oluşturmak çok daha hızlı. delete_by_query döküman bazlı çalışır, milyonlarca kayıtta çok yavaş.

❌ "Döküman sildim ama disk alanı azalmadı"

Silme fiziksel değil, işaretleme. Merge olana kadar disk alanı geri gelmez. _forcemerge?only_expunge_deletes=true ile hızlandır.

❌ "Custom routing ile eklediğim dökümanı routing'siz siliyorum"

Yanlış shard'a gider, "not_found" döner. Routing belirtmek zorunlu.

❌ "Production'da wildcard ile index siliyorum"

DELETE /logs-* yanlışlıkla beklediğinden fazla index silebilir. destructive_requires_name: true ayarla.

❌ "delete_by_query sırasında yazma devam ediyor"

delete_by_query başladığında snapshot alır. Sonra eklenen dökümanlar silinmez. Çakışma olursa version_conflicts artar. conflicts=proceed ile devam edebilirsin.


CRUD İşlemleri Özet Tablosu

Bu bölümdeki tüm CRUD işlemlerini tek tabloda toparlayalım:

İşlemAPIAçıklama
Create (belirli ID)PUT /index/_doc/IDVarsa replace eder
Create (otomatik ID)POST /index/_docBenzersiz ID üretir
Create (strict)PUT /index/_create/IDVarsa 409 hata
Read (tekil)GET /index/_doc/IDMetadata + _source
Read (source only)GET /index/_source/IDSadece _source
Read (çoklu)GET /index/_mgetToplu okuma
Read (varlık)HEAD /index/_doc/ID200 veya 404
Update (replace)PUT /index/_doc/IDTüm dokümanı değiştirir
Update (partial)POST /index/_update/IDSadece belirtilen alanlar
Update (scripted)POST /index/_update/IDPainless script ile
Update (toplu)POST /index/_update_by_querySorguya uyanları güncelle
Delete (tekil)DELETE /index/_doc/IDTek döküman
Delete (toplu)POST /index/_delete_by_querySorguya uyanları sil
Delete (index)DELETE /indexTüm index'i sil
Bulk (karma)POST /_bulkindex/create/update/delete

Özet

  • DELETE /index/_doc/ID ile tekil döküman silersin — anlık, ama fiziksel silme merge'de olur

  • Delete by Query ile sorguya uyan tüm dökümanları toplu silersin — slices ile paralelize et

  • Bulk API ile birden fazla dökümanı tek request'te silersin — delete action'da body yok

  • Index silme (DELETE /index), delete_by_query'den çok daha hızlıdır — zaman bazlı veriler için kullan

  • Silinen dökümanlar hemen disk'ten kalkmaz — force merge ile temizlenir

  • action.destructive_requires_name: true ile wildcard silmeyi engelle

  • Custom routing varsa silmede de routing parametresini belirt

  • GDPR compliance için kullanıcının tüm verilerini tüm index'lerden sil + force merge

Bir sonraki bölümde Arama Temelleri — Query DSL'e geçeceğiz — Elasticsearch'ün en güçlü silahını öğreneceğiz!