ElasticSearch – wyszukiwanie full-text

TL;DR: ElasticSearch to potężna platforma wyszukiwania full-text oparta na Apache Lucene. Pozwala na błyskawiczne przeszukiwanie milionów dokumentów z zaawansowanymi opcjami filtrowania, analizy i oceny trafności. Idealny dla e-commerce, systemów CMS i aplikacji wymagających szybkiego wyszukiwania tekstowego.

Dlaczego ElasticSearch to game-changer

Tradycyjne bazy danych SQL świetnie radzą sobie z dokładnymi zapytaniami, ale gdy użytkownik wpisuje „laptop dell szybki” w wyszukiwarkę sklepu, MySQL może się nie sprawdzić. ElasticSearch to rozwiązanie stworzone specjalnie do wyszukiwania full-text – analizuje tekst, rozpoznaje synonimy i zwraca najbardziej trafne wyniki w milisekundach.

ElasticSearch jest używany przez Netflix, GitHub, Wikipedię i Spotify do obsługi miliardów zapytań dziennie. To dowód na jego niezawodność w środowisku produkcyjnym.

Co się nauczysz

  • Jak zainstalować i skonfigurować ElasticSearch
  • Podstawowe koncepcje: indeksy, typy, dokumenty
  • Tworzenie mappingów dla optymalnego wyszukiwania
  • Implementacja zapytań full-text z oceną trafności
  • Integracja z aplikacją Java przez REST API
  • Analiza i debugowanie wyników wyszukiwania

Wymagania wstępne

Poziom: Podstawy programowania
Doświadczenie: 6-18 miesięcy z Java/aplikacjami webowymi
Wiedza: Podstawy HTTP, JSON, pojęcie REST API
Narzędzia: Java 8+, dowolne IDE, curl lub Postman

Czym jest ElasticSearch

ElasticSearch to rozproszona platforma wyszukiwania i analityki oparta na Apache Lucene. W przeciwieństwie do tradycyjnych baz danych, ElasticSearch został zaprojektowany od podstaw do wyszukiwania full-text.

Full-text search – wyszukiwanie które analizuje całą zawartość dokumentów, a nie tylko dokładne dopasowania. Obsługuje synonimy, błędy pisowni i ocenia trafność wyników.

Kluczowe różnice od tradycyjnych baz

Tradycyjna baza SQLElasticSearch
TabelaIndeks
WierszDokument
KolumnaPole
Dokładne dopasowaniaWyszukiwanie rozmyte
Brak oceny trafnościScoring algorytmy

Instalacja i pierwszy kontakt

Szybka instalacja

# Pobierz ElasticSearch 2.4 (aktualna wersja w 2016)
wget https://download.elastic.co/elasticsearch/release/org/elasticsearch/distribution/zip/elasticsearch/2.4.0/elasticsearch-2.4.0.zip

# Rozpakuj i uruchom
unzip elasticsearch-2.4.0.zip
cd elasticsearch-2.4.0
./bin/elasticsearch
Pro tip: ElasticSearch domyślnie uruchamia się na porcie 9200. Sprawdź czy działa: curl http://localhost:9200

Pierwszy test – sprawdzenie klastra

curl -X GET "localhost:9200"

Powinieneś zobaczyć odpowiedź JSON z informacjami o klastrze:

{
  "name" : "Blackheart",
  "cluster_name" : "elasticsearch",
  "version" : {
    "number" : "2.4.0",
    "build_hash" : "ce9f0c7394dee074091dd1bc4e9469251181fc55",
    "build_timestamp" : "2016-08-29T09:14:17Z",
    "build_snapshot" : false,
    "lucene_version" : "5.5.2"
  },
  "tagline" : "You Know, for Search"
}

Podstawowe operacje – CRUD

Tworzenie indeksu

# Tworzenie indeksu "sklep"
curl -X PUT "localhost:9200/sklep"

Dodawanie dokumentów

# Dodanie produktu do indeksu
curl -X POST "localhost:9200/sklep/produkty/1" -H 'Content-Type: application/json' -d'
{
  "nazwa": "Laptop Dell Inspiron 15",
  "opis": "Szybki laptop dla studentów i profesjonalistów",
  "cena": 2499,
  "kategoria": "elektronika",
  "tagi": ["laptop", "dell", "praca", "nauka"]
}
'
W ElasticSearch 2.4 używamy jeszcze typów dokumentów (tutaj „produkty”). W nowszych wersjach typy zostały usunięte dla uproszczenia.

Dodajmy więcej przykładowych produktów

# Smartfon
curl -X POST "localhost:9200/sklep/produkty/2" -H 'Content-Type: application/json' -d'
{
  "nazwa": "iPhone 7",
  "opis": "Najnowszy smartfon Apple z zaawansowanym aparatem",
  "cena": 3299,
  "kategoria": "elektronika",
  "tagi": ["smartphone", "apple", "telefon", "aparat"]
}
'

# Książka
curl -X POST "localhost:9200/sklep/produkty/3" -H 'Content-Type: application/json' -d'
{
  "nazwa": "Effective Java",
  "opis": "Przewodnik po najlepszych praktykach programowania w Javie",
  "cena": 89,
  "kategoria": "książki",
  "tagi": ["java", "programowanie", "książka", "bloch"]
}
'

Wyszukiwanie full-text w praktyce

Podstawowe zapytanie

# Wyszukiwanie produktów zawierających "laptop"
curl -X GET "localhost:9200/sklep/_search" -H 'Content-Type: application/json' -d'
{
  "query": {
    "match": {
      "nazwa": "laptop"
    }
  }
}
'

Zaawansowane zapytanie multi-field

# Wyszukiwanie w nazwie I opisie
curl -X GET "localhost:9200/sklep/_search" -H 'Content-Type: application/json' -d'
{
  "query": {
    "multi_match": {
      "query": "szybki laptop",
      "fields": ["nazwa^2", "opis"]
    }
  }
}
'
Pro tip: Notacja ^2 przy polu „nazwa” oznacza, że trafienia w nazwie są 2x ważniejsze niż w opisie. To tzw. boosting.

Filtrowanie wyników

# Laptop w przedziale cenowym 2000-3000 zł
curl -X GET "localhost:9200/sklep/_search" -H 'Content-Type: application/json' -d'
{
  "query": {
    "bool": {
      "must": {
        "match": {"nazwa": "laptop"}
      },
      "filter": {
        "range": {
          "cena": {
            "gte": 2000,
            "lte": 3000
          }
        }
      }
    }
  }
}
'

Analiza tekstu – serce wyszukiwania

ElasticSearch automatycznie analizuje tekst przed indeksowaniem. Rozdziela słowa, usuwa znaki interpunkcyjne, sprowadza do małych liter.

Analiza tekstu to jak przygotowanie składników przed gotowaniem. „Laptop Dell Inspiron!” staje się tokenami: [„laptop”, „dell”, „inspiron”] gotowymi do wyszukiwania.

Testowanie analizatora

# Sprawdź jak ElasticSearch analizuje tekst
curl -X GET "localhost:9200/sklep/_analyze" -d '
{
  "text": "Szybki laptop Dell dla programistów!",
  "analyzer": "standard"
}
'

Mapping – definiowanie struktury

Mapping to schema która mówi ElasticSearch jak traktować poszczególne pola. Domyślnie ES automatycznie wykrywa typy, ale lepiej je zdefiniować jawnie.

Tworzenie mappingu

# Usuń stary indeks
curl -X DELETE "localhost:9200/sklep"

# Utwórz nowy z mappingiem
curl -X PUT "localhost:9200/sklep" -H 'Content-Type: application/json' -d'
{
  "mappings": {
    "produkty": {
      "properties": {
        "nazwa": {
          "type": "string",
          "analyzer": "standard"
        },
        "opis": {
          "type": "string",
          "analyzer": "standard"
        },
        "cena": {
          "type": "double"
        },
        "kategoria": {
          "type": "string",
          "index": "not_analyzed"
        },
        "tagi": {
          "type": "string",
          "index": "not_analyzed"
        }
      }
    }
  }
}
'
Uwaga: Pole z „index”: „not_analyzed” nie jest analizowane – przechowuje dokładną wartość. Używaj dla kategorii, tagów, ID.

Integracja z aplikacją Java

Dodawanie zależności



    org.elasticsearch
    elasticsearch
    2.4.0


    org.elasticsearch.client
    transport
    2.4.0

Prosty klient Java

import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.transport.TransportClient;
import org.elasticsearch.common.transport.InetSocketTransportAddress;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;

import java.net.InetAddress;

public class ElasticSearchClient {
    
    private TransportClient client;
    
    public void connect() throws Exception {
        client = TransportClient.builder().build()
                .addTransportAddress(new InetSocketTransportAddress(
                    InetAddress.getByName("localhost"), 9300));
    }
    
    public void searchProducts(String query) {
        SearchResponse response = client.prepareSearch("sklep")
                .setTypes("produkty")
                .setQuery(QueryBuilders.multiMatchQuery(query, "nazwa", "opis"))
                .get();
        
        System.out.println("Znaleziono: " + response.getHits().getTotalHits());
        
        for (SearchHit hit : response.getHits()) {
            System.out.println("Score: " + hit.getScore());
            System.out.println("Źródło: " + hit.getSourceAsString());
            System.out.println("---");
        }
    }
    
    public void close() {
        client.close();
    }
}
Pro tip: Transport Client używa portu 9300 (nie 9200). Port 9200 to HTTP REST API, 9300 to natywny protokół transportowy.

Użycie klienta

public class Main {
    public static void main(String[] args) throws Exception {
        ElasticSearchClient esClient = new ElasticSearchClient();
        esClient.connect();
        
        // Wyszukaj laptopy
        esClient.searchProducts("szybki laptop");
        
        esClient.close();
    }
}

Scoring – jak ElasticSearch ocenia trafność

ElasticSearch używa algorytmu TF-IDF (Term Frequency-Inverse Document Frequency) do oceny trafności wyników.

TF-IDF – algorytm który wyżej ocenia słowa występujące często w dokumencie, ale rzadko w całej kolekcji. Słowo „the” ma niski score, „elasticsearch” ma wysoki.

Wyjaśnienie score na przykładzie

# Wyjaśnienie dlaczego dany dokument ma taki score
curl -X GET "localhost:9200/sklep/_search?explain=true" -d '
{
  "query": {
    "match": {"nazwa": "laptop"}
  }
}
'

Częste problemy początkujących

Błąd 1: Używanie „term” query zamiast „match” dla wyszukiwania full-text. Term szuka dokładnego dopasowania, match analizuje tekst.
Błąd 2: Nie definiowanie mappingu – poleganie na auto-detection może prowadzić do nieprzewidywalnych wyników wyszukiwania.
Błąd 3: Mylenie portów – HTTP REST API (9200) vs Transport Client (9300). Używaj odpowiedniego portu dla swojego klienta.
Pułapka: Domyślnie ElasticSearch zwraca tylko 10 pierwszych wyników. Użyj parametru „size” aby pobrać więcej.

Monitoring i debugowanie

Sprawdzanie statusu klastra

# Status klastra
curl -X GET "localhost:9200/_cluster/health"

# Statystyki indeksu
curl -X GET "localhost:9200/sklep/_stats"

# Lista wszystkich indeksów
curl -X GET "localhost:9200/_cat/indices?v"

Analiza performance

# Sprawdź jak długo trwało zapytanie
curl -X GET "localhost:9200/sklep/_search?pretty" -d '
{
  "query": {"match_all": {}},
  "profile": true
}
'

Optymalizacja wydajności

Pro tip: ElasticSearch automatycznie odświeża indeks co sekundę. Dla bulk importu ustaw refresh_interval na -1, a po imporcie przywróć domyślną wartość.

Bulk indexing dla większej wydajności

# Bulk API - dodawanie wielu dokumentów naraz
curl -X POST "localhost:9200/sklep/produkty/_bulk" -H 'Content-Type: application/json' -d'
{"index":{"_id":"4"}}
{"nazwa":"MacBook Pro","opis":"Laptop dla kreatywnych profesjonalistów","cena":6999,"kategoria":"elektronika"}
{"index":{"_id":"5"}}
{"nazwa":"Logitech MX Master","opis":"Ergonomiczna mysz dla profesjonalistów","cena":299,"kategoria":"akcesoria"}
'
Czym różni się ElasticSearch od MySQL w kontekście wyszukiwania?

MySQL używa LIKE '%tekst%’ co jest bardzo wolne na dużych tabelach. ElasticSearch indeksuje każde słowo osobno i używa algorytmów full-text search, co daje wyniki w milisekundach nawet dla milionów dokumentów. Dodatkowo ES oferuje scoring (ocenę trafności), which MySQL nie posiada.

Kiedy używać „match” a kiedy „term” w zapytaniach?

„Match” dla wyszukiwania full-text – analizuje tekst i znajduje podobne wyniki. „Term” dla dokładnych dopasowań – ID, kategorie, daty. Przykład: match dla „laptop dell”, term dla category: „elektronika”.

Jak działa scoring w ElasticSearch?

ElasticSearch używa algorytmu TF-IDF. Wyższy score mają dokumenty gdzie szukane słowa występują często (TF), ale są rzadkie w całej kolekcji (IDF). Można to modyfikować przez boosting pól lub funkcje scoring.

Czy ElasticSearch może zastąpić tradycyjną bazę danych?

Nie całkowicie. ES to search engine, nie transactional database. Brak mu ACID guarantees, complex joins, czy strict consistency. Używaj ES do wyszukiwania, ale trzymaj główne dane w PostgreSQL/MySQL.

Jak obsłużyć polskie znaki diakrytyczne w wyszukiwaniu?

Użyj analizatora z ASCII folding filter, który zamieni „ą” na „a”, „ł” na „l” itp. Alternatywnie stwórz custom analyzer z Polish tokenizer i stopwords. Dzięki temu „kraków” znajdzie „Kraków”.

Jakie są limity ElasticSearch w wersji 2.4?

Domyślnie max 10000 dokumentów per search, max 1000 pól per mapping, max 32766 znaków per term. Większość limitów można zwiększyć przez konfigurację, ale wpływa to na wydajność.

Jak monitorować wydajność zapytań?

Użyj profile API dla szczegółowej analizy zapytań. Sprawdzaj slow log (domyślnie >0.5s dla query, >0.2s dla fetch). Monitoruj thread pool, heap usage i cache hit ratio przez /_nodes/stats.

Przydatne zasoby

🚀 Zadanie dla Ciebie

Stwórz indeks „blog” z artykułami zawierającymi pola: tytuł, treść, autor, data publikacji i tagi. Dodaj 5 przykładowych artykułów. Napisz zapytanie które znajdzie artykuły z ostatnich 30 dni zawierające słowo „java” w tytule lub treści, posortowane według daty publikacji malejąco.

Bonus: Dodaj facet search – pokaż liczbę artykułów per autor i per tag dla wyników wyszukiwania.

Masz doświadczenia z ElasticSearch? Jakie największe wyzwania napotkałeś przy implementacji wyszukiwania full-text? Podziel się w komentarzach!

Zostaw komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *

Przewijanie do góry