Tüm yazılara dön

Elasticsearch edge_ngram ile Her Alanda Autocomplete

Sahada kanıtlanmış pattern: tek dynamic template, tek edge_ngram analyzer, tek multi_match — 8 karakterlik UUID parçası, derin nested SOC case'ini buluyor.

Elasticsearch edge_ngram ile Her Alanda Autocomplete

Bir SOC analisti arama kutusuna UUID'nin tamamını yapıştırmaz. MITRE tekniğinin ilk sekiz karakterini, hostname parçasını, kullanıcı adının yarısını yazar — ve case'in gelmesini bekler. Bir cybersecurity analytics şirketinin bana getirdiği gereksinim tam olarak buydu: case dokümanlarında partial-match arama; üç seviye derinde, nested attack pattern ve stage'lerin içindeki alanlar dahil.

Zorluk şurada: dokümanlar hem geniş hem derin. Tek bir case; users, endpoints, techniques, pattern dizisi, her pattern'de yirmiden fazla stage, her stage'de ayrı bir MITRE ID taşıyor. Her alanı sorguda tek tek saymak ya da her alana elle autocomplete mapping'i yazmak ölçeklenmez. PoC'de kanıtladığımız pattern aşağıda — birebir sahadan.

Kısa cevap: Tek bir custom edge_ngram analyzer tanımlayın, match_mapping_type: "string" içeren bir dynamic_templates girdisiyle bütün string alanlara bağlayın, multi_match ile "fields": ["*"] üzerinde sorgulayın. Her derinlikteki her text alanı, alan başına sıfır mapping işiyle prefix aranabilir olur. Cevabı _source includes ile inceltin. Trade-off'ları bilin: index büyür ve production öncesi mutlaka ayrı bir search_analyzer tanımlayın.

Gereksinim: nested SOC case'inde partial arama

Alan listesi müşterinin kendi notundan: uid, users, endpoints, assignedTo, updatedAt, triggersNumberpatterns, stages ve steps ise iç onay bekliyordu. Doküman şekli klasik SOC: lateral_movement, privilege_escalation gibi techniques alanları, saldırı anlatısını taşıyan pattern'ler ve attack-pattern--dcaa092b-... biçiminde STIX ID taşıyan stage'ler.

Varsayılan mapping ile bu iş iki yerden kırılır. Birincisi, standart text analizi kelime sınırından tokenize eder — ngram olmadan dcaa yazınca hiçbir şey dönmez. İkincisi, ilginç alanlar sürekli değişiyor: şema hâlâ tartışılırken ("stages için yöneticime sormam lazım") alan bazlı mapping bakım tuzağıdır.

Index: tek analyzer, tek dynamic template

PoC'deki index tanımının tamamı; yalnızca isim genelleştirildi:

PUT soc-cases
{
  "mappings": {
    "dynamic_templates": [
      {
        "all_strings_autocomplete": {
          "match_mapping_type": "string",
          "mapping": {
            "type": "text",
            "analyzer": "auto_complete"
          }
        }
      }
    ]
  },
  "settings": {
    "analysis": {
      "analyzer": {
        "auto_complete": {
          "tokenizer": "ngram_tokenizer",
          "filter": [ "lowercase" ]
        }
      },
      "tokenizer": {
        "ngram_tokenizer": {
          "type": "edge_ngram",
          "min_gram": 2,
          "max_gram": 10,
          "token_chars": [ "letter", "digit" ]
        }
      }
    }
  }
}

Buradaki üç bilinçli karar:

ID aramasını token_chars taşıyor

token_chars: ["letter", "digit"] ile tokenizer harf ve rakam dışındaki her şeyde böler — tire dahil. attack-pattern--dcaa092b-7de9-... değeri attack, pattern, dcaa092b, 7de9 parçalarına ayrılır ve her parça kendi edge ngram'lerini alır. Analistin UUID'nin ortasından case bulabilmesinin sebebi bu: parça, kendi token'ının başlangıcı.

min_gram: 2, max_gram: 10

Tek karakterlik arama gürültüdür; o yüzden min_gram: 2. max_gram: 10 ise term patlamasını sınırlar — uzun token'lar yalnızca ilk on karaktere kadar indexlenir. Kullanıcılar daha uzun parça yapıştırıyorsa ya max_gram'ı artırın ya da sorguyu ngram'lemeyen bir search-time analyzer'a yaslanın (aşağıda). Parametreler için Elastic'in edge_ngram tokenizer dokümantasyonuna bakın.

Dynamic template her derinlikteki her string'i yakalıyor

match_mapping_type: "string" sayesinde Elasticsearch'ün keşfettiği her string alan — top-level uid, nested patterns.stages.mitreId, gelecek sprintte eklenecek her alan — otomatik olarak auto_complete analyzer'lı text olur. Şema tartışması sonuçlandığında mapping güncellemesi gerekmez. Tarih ve sayılar tespit edilen tiplerinde kalır; updatedAt gerçek date olarak range sorgularına açık kalır.

Sorgu: * üzerinde multi_match, _source ile cerrahi kesim

Doğrulama sorgusu 8 karakterlik UUID parçasını her yerde aradı ve cevabı UI'ın ihtiyacına indirdi:

GET soc-cases/_search
{
  "query": {
    "multi_match": {
      "query": "dcaa092b",
      "fields": [ "*" ]
    }
  },
  "_source": [
    "uid", "users", "endpoints", "updatedAt",
    "techniques", "patterns",
    "steps.triggersNumber", "steps.mitreId"
  ]
}

_source include listesi kopyalanmaya değer: steps.triggersNumber ve steps.mitreId, steps dizisindeki her objeden yalnızca bu iki alanı döndürür — yirmi dolgun obje yerine. Konuşkan SOC dokümanlarında, search-as-you-type UI için gerçek payload tasarrufu.

Testin kanıtladıkları

PoC cevabındaki kanıt, fazlası değil:

  • Nested bir stage'in MITRE UUID'sinin ilk 8 karakteri olan dcaa092b parçası case'i döndürdü: hits.total.value: 1, max_score: 2.079955.
  • took: 1 ms — tek dokümanlık test index'inde; doğruluğu kanıtlar, performansı değil. Bunu benchmark diye pazarlamayacağım.
  • Cevaptaki steps objeleri tam olarak triggersNumber ve mitreId içeriyordu; _source filtresinin nested dizide çalıştığının teyidi.

Ölçekte index boyutu veya latency rakamı bu PoC'de yok — kaynak pakette bulunmuyor; uydurmak faydasızdan beter olur.

Production öncesi trade-off'lar

search_analyzer tanımlayın. PoC aynı ngram analyzer'ı hem index hem search tarafında kullandı; kısa girdilerde çalışır ama sürpriz eşleşmeler üretir: sorgu da ngram'lere bölündüğü için uzun sorgular gevşek eşleşir. Production'da auto_complete ile indexleyin, sorguyu düz lowercase tarzı bir analyzer ile olduğu gibi eşleyin.

edge_ngram index'i şişirir. 10 karaktere kadar her token 9'a kadar term üretir. Bunu her string alana uygulamak bilinçli bir depolama-latency takasıdır. Autocomplete yalnızca birkaç alana lazımsa, dynamic template'i her şeyi yakalamak yerine path_match ile daraltın.

fields: ["*"] pratik ama bedava değil. Yüzlerce alana sorgu anında genişleme CPU yer. PoC için doğruydu; sıcak bir arama kutusunda alan gruplarını açıkça listeleyin veya copy_to ile tek arama alanında toplayın. Cluster etkisinden endişeniz varsa izleyin — Elasticsearch monitoring çalışmalarında tam olarak bu sorgu yükü pattern'ini takip ediyoruz.

Öğrendiklerimiz

  1. Tek dynamic template, elli mapping güncellemesini döver. match_mapping_type: "string" mevcut ve gelecek her text alana analyzer'ı otomatik bağlar.
  2. ID araması token_chars özelliğidir. Harf/rakam tokenizasyonu UUID'leri ve tireli ID'leri bağımsız aranabilir parçalara böler.
  3. Gram sınırlarını bilin. min_gram: 2 tek karakter gürültüsünü keser; max_gram: 10 term patlamasını sınırlar — "uzun sorgu eşleşmiyor" ticket'ı gelmeden önce ikisini de tanıyın.
  4. Nested dizileri _source includes ile inceltin. steps.triggersNumber obje başına iki alan döndürür, objenin tamamını değil.
  5. Index ve search analyzer'ı production öncesi ayırın. Aynı analyzer'la ngram araması klasik edge_ngram tuzağıdır.
  6. Sadece ölçtüğünüzü iddia edin. Tek dokümanda 1 ms took doğruluk kontrolüdür, benchmark değil.

Sık Sorulan Sorular

Neden *dcaa092b* gibi wildcard sorgusu değil de edge_ngram?

Başında wildcard olan sorgular term dictionary'yi arama anında tarar ve index büyüdükçe yavaşlar. edge_ngram maliyeti bir kez, index anında öder; prefix araması exact term eşleşmesine dönüşür — interaktif arama kutusu için doğru takas.

Neden search_as_you_type field tipi değil?

search_as_you_type sağlam bir built-in ama seçilmiş alanlarda phrase-prefix tamamlamada parlar. Buradaki gereksinim, alan başına mapping olmadan bütün alanlarda — tireli UUID'lerin içi dahil — fragment eşleşmesiydi. Custom edge_ngram analyzer + dynamic template bu şekle daha iyi oturur ve token_chars kontrolünü sizde tutar.

Dynamic template tarihleri ve sayıları bozar mı?

Hayır. Yalnızca match_mapping_type: "string" olanları yakalar. Tespit edilen tarihler (updatedAt gibi) ve sayısal tipler normal tiplerinde kalır; sıralama ve range filtreleri çalışmaya devam eder.

max_gram'dan uzun parçaları nasıl ararım?

Ya max_gram'ı artırın (index büyümesini kabul ederek) ya da ngram'sız bir search_analyzer tanımlayın: uzun sorgu tek term olarak cap'e kadar indexlenmiş gram'lerle eşleşir; exact uzun aramalar için prefix sorgusuyla birleştirebilirsiniz.

Partial-match arama tasarımı — veya bunu kaldıracak bir Elasticsearch cluster'ı mı lazım? → searchali.com

Arama altyapınızı sınırların ötesine taşıyalım.

Yüksek performanslı ve hatasız bir arama deneyimi için hemen iletişime geçin.