תארו לעצמכם שהחנות המקוונת שלכם גדלה ל-500,000 מוצרים, וחיפוש לפי שם באמצעות ILIKE % ב-PostgreSQL נתקע עם כל הקלדה. לקוחות עוזבים, ההמרה יורדת—כל שנייה של עיכוב מפחיתה אותה ב-7%. נתקלנו במצב הזה יותר מפעם אחת. בפרויקט אחד, חיפוש על פני מיליון מוצרים לקח 8 שניות; לאחר מעבר ל-Elasticsearch, זה היה 80 אלפיות השנייה. ההמרה גדלה ב-15%. במהלך השנים, הגדרנו את Elasticsearch ליותר מ-30 פרויקטים—מקטלוגים ועד שווקים. הניסיון שלנו מראה שהגדרת אינדקס ואנלייזר נכונה מפחיתה את זמן החיפוש פי 10.
למה Elasticsearch במקום חיפוש טקסט מלא ב-PostgreSQL?
PostgreSQL יכול לבצע חיפוש טקסט מלא באמצעות tsvector, אבל הוא מתקשה בעומסים כבדים, מורפולוגיה מורכבת ופאקטות. Elasticsearch עולה על PostgreSQL פי 10-20 במהירות בעומסים אמיתיים. השוו:
| קריטריון | PostgreSQL (ILIKE/tsvector) | Elasticsearch |
|---|---|---|
| מהירות חיפוש עבור מיליון רשומות | 200-500 אלפיות השנייה | 10-50 אלפיות השנייה |
| מורפולוגיה רוסית | בסיסית דרך מילונים | גזירה, מילים נרדפות, אנלייזרים מותאמים אישית |
| סינון פאקטות | מוגבל | אגרגציות חזקות |
| השלמה אוטומטית | פריצות עם טריגרמים | Edge n-gram, Suggester |
| חיפוש גיאוגרפי | דרך PostGIS, איטי | טבעי, מהיר |
בנוסף, החיפוש המבוזר של Elasticsearch מתמודד עם מיליארדי מסמכים.
איך אנו מגדירים את Elasticsearch למשימה שלכם
אנחנו לא מגדירים Elasticsearch "מתוך הקופסה." אנו תמיד מנתחים את מבנה הנתונים שלכם ואת השאילתות האופייניות. הנה דוגמה אמיתית: עבור קטלוג אלקטרוניקה עם 200,000 פריטים, יצרנו אינדקס עם שני אנלייזרים: רוסית (גזירה + מילות עצירה) והשלמה אוטומטית (edge n-gram). ההתקנה ארכה 4 ימים. בפרויקט אחר, היינו צריכים char_filter מותאם אישית כדי לנקות שמות מוצרים מתווים מיוחדים.
התקנת Elasticsearch 8.x
# Добавить репозиторий
wget -qO - https://artifacts.elastic.co/GPG-KEY-elasticsearch | gpg --dearmor -o /usr/share/keyrings/elasticsearch-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/elasticsearch-keyring.gpg] https://artifacts.elastic.co/packages/8.x/apt stable main" > /etc/apt/sources.list.d/elastic-8.x.list
apt update && apt install -y elasticsearch
# Сохранить пароль superuser из вывода установки
systemctl enable elasticsearch && systemctl start elasticsearch
תצורה מינימלית לפיתוח עם צומת יחיד:
# /etc/elasticsearch/elasticsearch.yml
cluster.name: myapp-search
node.name: node-1
path.data: /var/lib/elasticsearch
path.logs: /var/log/elasticsearch
network.host: 127.0.0.1
discovery.type: single-node
xpack.security.enabled: true
xpack.security.http.ssl.enabled: false # для dev; в prod — включитьאנו מקצים heap כך: לא יותר מ-50% זיכרון RAM, לא יותר מ-32 GB (בגלל OOPs דחוסים). 4 GB מספיקים להתחלה.
מיפוי אינדקס
המיפוי מגדיר את סכימת האינדקס. מיפוי שגוי לא ניתן לתיקון ללא בנייה מחדש של האינדקס. קראו עוד במיפוי Elasticsearch:
PUT /products
{
"settings": {
"number_of_shards": 2,
"number_of_replicas": 1,
"analysis": {
"analyzer": {
"russian_analyzer": {
"type": "custom",
"tokenizer": "standard",
"filter": ["lowercase", "russian_stop", "russian_stemmer"]
},
"autocomplete_analyzer": {
"type": "custom",
"tokenizer": "standard",
"filter": ["lowercase", "edge_ngram_filter"]
},
"autocomplete_search": {
"type": "custom",
"tokenizer": "standard",
"filter": ["lowercase"]
}
},
"filter": {
"russian_stop": {
"type": "stop",
"stopwords": "_russian_"
},
"russian_stemmer": {
"type": "stemmer",
"language": "russian"
},
"edge_ngram_filter": {
"type": "edge_ngram",
"min_gram": 2,
"max_gram": 20
}
}
}
},
"mappings": {
"properties": {
"id": {
"type": "keyword"
},
"name": {
"type": "text",
"analyzer": "russian_analyzer",
"fields": {
"autocomplete": {
"type": "text",
"analyzer": "autocomplete_analyzer",
"search_analyzer": "autocomplete_search"
},
"keyword": {
"type": "keyword"
}
}
},
"description": {
"type": "text",
"analyzer": "russian_analyzer"
},
"category": {
"type": "keyword"
},
"brand": {
"type": "keyword"
},
"price": {
"type": "scaled_float",
"scaling_factor": 100
},
"in_stock": {
"type": "boolean"
},
"attributes": {
"type": "object",
"dynamic": true
},
"location": {
"type": "geo_point"
},
"created_at": {
"type": "date"
}
}
}
} שאילתת חיפוש עם פאקטות
POST /products/_search
{
"query": {
"bool": {
"must": [
{
"multi_match": {
"query": "беспроводные наушники",
"fields": ["name^3", "description", "name.autocomplete^2"],
"type": "best_fields",
"fuzziness": "AUTO"
}
}
],
"filter": [
{
"term": {
"in_stock": true
}
},
{
"range": {
"price": {
"gte": 1000,
"lte": 10000
}
}
},
{
"terms": {
"category": ["audio", "headphones"]
}
}
]
}
},
"aggs": {
"categories": {
"terms": {
"field": "category",
"size": 20
}
},
"brands": {
"terms": {
"field": "brand",
"size": 30
}
},
"price_ranges": {
"range": {
"field": "price",
"ranges": [
{
"to": 1000
},
{
"from": 1000,
"to": 5000
},
{
"from": 5000,
"to": 15000
},
{
"from": 15000
}
]
}
}
},
"highlight": {
"fields": {
"name": {},
"description": {
"fragment_size": 150
}
}
},
"from": 0,
"size": 24,
"sort": [
{
"_score": "desc"
},
{
"created_at": "desc"
}
]
} איך להגדיר השלמה אוטומטית?
ההשלמה האוטומטית מיושמת באמצעות אנלייזר edge n-gram, כפי שמוצג במיפוי. Edge n-gram יוצר טוקנים מ-2 עד 20 תווים. משתמשים מקבלים הצעות לאחר הזנת 2-3 תווים—משפר את חוויית המשתמש. השדה # Добавить репозиторий wget -qO - https://artifacts.elastic.co/GPG-KEY-elasticsearch | gpg --dearmor -o /usr/share/keyrings/elasticsearch-keyring.gpg echo "deb [signed-by=/usr/share/keyrings/elasticsearch-keyring.gpg] https://artifacts.elastic.co/packages/8.x/apt stable main" > /etc/apt/sources.list.d/elastic-8.x.list apt update && apt install -y elasticsearch # Сохранить пароль superuser из вывода установки systemctl enable elasticsearch && systemctl start elasticsearch ממפה את תחילת כל מילה. בשאילתות חיפוש, השתמשו ב-# /etc/elasticsearch/elasticsearch.yml cluster.name: myapp-search node.name: node-1 path.data: /var/lib/elasticsearch path.logs: /var/log/elasticsearch network.host: 127.0.0.1 discovery.type: single-node xpack.security.enabled: true xpack.security.http.ssl.enabled: false # для dev; в prod — включить או PUT /products { "settings": { "number_of_shards": 2, "number_of_replicas": 1, "analysis": { "analyzer": { "russian_analyzer": { "type": "custom", "tokenizer": "standard", "filter": ["lowercase", "russian_stop", "russian_stemmer"] }, "autocomplete_analyzer": { "type": "custom", "tokenizer": "standard", "filter": ["lowercase", "edge_ngram_filter"] }, "autocomplete_search": { "type": "custom", "tokenizer": "standard", "filter": ["lowercase"] } }, "filter": { "russian_stop": { "type": "stop", "stopwords": "_russian_" }, "russian_stemmer": { "type": "stemmer", "language": "russian" }, "edge_ngram_filter": { "type": "edge_ngram", "min_gram": 2, "max_gram": 20 } } } }, "mappings": { "properties": { "id": { "type": "keyword" }, "name": { "type": "text", "analyzer": "russian_analyzer", "fields": { "autocomplete": { "type": "text", "analyzer": "autocomplete_analyzer", "search_analyzer": "autocomplete_search" }, "keyword": { "type": "keyword" } } }, "description": { "type": "text", "analyzer": "russian_analyzer" }, "category": { "type": "keyword" }, "brand": { "type": "keyword" }, "price": { "type": "scaled_float", "scaling_factor": 100 }, "in_stock": { "type": "boolean" }, "attributes": { "type": "object", "dynamic": true }, "location": { "type": "geo_point" }, "created_at": { "type": "date" } } } } בשדה זה.
לוחות זמנים משוערים
לוחות הזמנים תלויים במורכבות. הגדרה בסיסית עם אינדקס אחד ואינטגרציה אורכת 3-5 ימים. אם אתם צריכים השלמה אוטומטית, פאקטות וסנכרון PostgreSQL, הוסיפו עוד 3-5 ימים. קלאסטר עם 3 צמתים וניטור אורך 1-2 שבועות. העלויות מחושבות באופן אישי. להלן לוח זמנים משוער לפי שלב:
| שלב | משך |
|---|---|
| ניתוח ועיצוב | 1-2 ימים |
| התקנה והגדרת אינדקס | 1-2 ימים |
| אינטגרציה עם היישום | 2-3 ימים |
| בדיקות ואופטימיזציה | 1-2 ימים |
| פריסה וניטור | יום אחד |
סנכרון נתונים מ-PostgreSQL
לסנכרון, אנו משתמשים ברפליקציה לוגית דרך Debezium + Kafka בתרחישי ייצור. להתחלה, בנייה מחדש תקופתית באמצעות cron מספיקה. להלן דוגמה ב-TypeScript:
// sync/product-indexer.ts
import { Client } from '@elastic/elasticsearch'
import { Pool } from 'pg'
const es = new Client({
node: 'http://localhost:9200',
auth: {
username: 'elastic',
password: process.env.ES_PASSWORD!
}
})
const pg = new Pool({
connectionString: process.env.DATABASE_URL
})
export async function indexProduct(id: string) {
const { rows } = await pg.query(
`
SELECT p.*, c.name AS category_name,
json_agg(json_build_object('key', a.key, 'value', a.value)) AS attributes
FROM products p
LEFT JOIN categories c ON c.id = p.category_id
LEFT JOIN product_attributes a ON a.product_id = p.id
WHERE p.id = $1
GROUP BY p.id, c.name
`,
[id]
)
if (!rows.length) {
await es.delete({ index: 'products', id })
return
}
const p = rows[0]
await es.index({
index: 'products',
id: p.id,
document: {
id: p.id,
name: p.name,
description: p.description,
category: p.category_name,
price: p.price,
in_stock: p.stock_quantity > 0,
attributes: Object.fromEntries(p.attributes?.map((a: any) => [a.key, a.value]) ?? []),
created_at: p.created_at
}
})
}
export async function reindexAll() {
const { rows } = await pg.query('SELECT id FROM products WHERE deleted_at IS NULL')
const chunks = chunk(rows.map(r => r.id), 100)
for (const ids of chunks) {
await Promise.all(ids.map(indexProduct))
console.log(`Indexed ${ids.length} products`)
}
}
תוכנית הגדרה שלב אחר שלב
- ניתוח—לימוד מבנה הנתונים, שאילתות אופייניות, דרישות מהירות (1-2 ימים).
- עיצוב—פיתוח מיפוי, אנלייזרים, תוכנית סנכרון (1-2 ימים).
- יישום—התקנת Elasticsearch, הגדרת אינדקס, כתיבת אינטגרציה (3-5 ימים).
- בדיקות—בדיקת רלוונטיות חיפוש, פאקטות, מהירות (1-2 ימים).
- פריסה—פריסה לייצור, הגדרת ניטור (יום אחד).
מה כלול
לאחר סיום, אתם מקבלים:
- קלאסטר Elasticsearch מוגדר (צומת יחיד או קלאסטר) עם גישה
- אינדקסים עם אנלייזרים מותאמים אישית לשפה הרוסית
- אינטגרציה עם היישום שלכם דרך REST API
- סקריפטים לסנכרון נתונים (לדוגמה, מ-PostgreSQL)
- תיעוד תפעולי
- הדרכה לצוות שלכם
אנו נותנים אחריות ל-3 חודשים: אם החיפוש לא עובד כמצופה, אנו מתקנים זאת בחינם.
ניטור קלאסטר
אנו ממליצים לנטר את בריאות הקלאסטר דרך POST /products/_search { "query": { "bool": { "must": [ { "multi_match": { "query": "беспроводные наушники", "fields": ["name^3", "description", "name.autocomplete^2"], "type": "best_fields", "fuzziness": "AUTO" } } ], "filter": [ { "term": { "in_stock": true } }, { "range": { "price": { "gte": 1000, "lte": 10000 } } }, { "terms": { "category": ["audio", "headphones"] } } ] } }, "aggs": { "categories": { "terms": { "field": "category", "size": 20 } }, "brands": { "terms": { "field": "brand", "size": 30 } }, "price_ranges": { "range": { "field": "price", "ranges": [ { "to": 1000 }, { "from": 1000, "to": 5000 }, { "from": 5000, "to": 15000 }, { "from": 15000 } ] } } }, "highlight": { "fields": { "name": {}, "description": { "fragment_size": 150 } } }, "from": 0, "size": 24, "sort": [{ "_score": "desc" }, { "created_at": "desc" }] } ולהפעיל slowlog עבור שאילתות חיפוש. השתמשו ב-Elastic Metricbeat כדי לאסוף מדדים—זה עוזר לזהות הידרדרות בזמן.
טעויות נפוצות בהגדרה
- מיפוי שגוי—מיפוי דינמי מוביל לסוגי שדות בלתי צפויים, ושובר פאקטות. תמיד הגדירו סכימה במפורש.
- heap קטן מדי—זיכרון לא מספק גורם להשהיות GC תכופות וירידה בביצועים. הקצו לפחות 50% זיכרון RAM, אבל לא יותר מ-32 GB.
- אין slowlog—בלעדיו, לא תראו שאילתות איטיות. הפעילו slowlog בתצורה:
name.autocomplete. - התעלמות מרפליקות—לסבילות לתקלות, יש צורך לפחות ברפליקה אחת. הגדירו
match_phrase_prefix.
התחילו את העבודה
אם החיפוש שלכם איטי או לא יכול להתמודד עם העומס, צרו קשר. אנו נעריך את המערכת שלכם ונציע פתרון. הזמינו הגדרת Elasticsearch סוהר—קבלו ייעוץ. אל תדחו: כל שנייה של עיכוב עולה לכם בלקוחות. בקשו ביקורת היום.







