אופטימיזציה של אינדקסים ומיפוי ב-Elasticsearch
אנו מתכננים אינדקסים של Elasticsearch לפרויקטים בעלי עומס גבוה: אתרי מסחר אלקטרוני עם מיליוני מוצרים, מערכות לוג עם טרה-בייטים של נתונים מדי יום, ופלטפורמות חיפוש. עם ניסיון של למעלה מ-5 שנים בכיוונון Elasticsearch ויותר מ-100 פרויקטים שהושלמו, אנו יודעים שמיפוי סטטי הוא הדרך היחידה להבטיח ביצועי חיפוש צפויים. מיפוי דינמי מוביל לסוגי שדות בלתי צפויים, נפיחות באינדקס, ושינויי סכמה הדורשים בנייה מחדש של האינדקס. ב-70% מהמקרים, מיפוי דינמי פוגע בביצועים: מהירות החיפוש יורדת ב-40–60% ועלויות האחסון גדלות ב-30–50%. לאחר האופטימיזציה שלנו, מהירות החיפוש בדרך כלל מוכפלת ועלויות האחסון יורדות ב-30%. לדוגמה, בפרויקט עם 10 מיליון מוצרים, מיפוי דינמי הפך את השדה text למחרוזת — המיון הפסיק לעבוד. בנינו מחדש את האינדקס תוך יומיים, הגדרנו מיפוי סטטי, ומהירות החיפוש שולשה. לאחר הגדרת ILM, לקוח חסך 1.8–2.6 אלף דולר מדי חודש באחסון לוגים. מיפוי סטטי מהיר פי 2 ממיפוי דינמי עבור שאילתות בעומס גבוה. שימוש ב-keyword להתאמות מדויקות מהיר פי 3 משימוש ב-float ללא ניתוח.
מדוע מיפוי דינמי מסוכן בסביבת ייצור
מיפוי דינמי יוצר אשליה של נוחות: אתה שולח JSON, ו-Elasticsearch קובע את הסוגים אוטומטית. בפועל, זה מוביל להפתעות: מחרוזות יכולות להפוך ל-integer או object בהתאם לערך, מספרים הופכים ל-nested במקום text, ומערכים של אובייקטים הופכים ל-keyword במקום integer/long. כתוצאה מכך, חיפוש בשדות קשורים בתוך מערך מניב תוצאות שגויות. תיקון הבעיה דורש בנייה מחדש של האינדקס, מה שצורך זמן ומשאבים. Dynamic: strict מבטל את הבעיות הללו לחלוטין.
השוואה בין מיפוי סטטי לדינמי
| פרמטר | מיפוי סטטי | מיפוי דינמי |
|---|---|---|
| ביצועי חיפוש | גבוהים (יציבים) | יורדים ב-40–60% ככל שהנתונים גדלים |
| שליטה בסכמה | מלאה, שגיאה בשדות לא ידועים | סוגים אקראיים, נפיחות באינדקס |
| עלויות אחסון | נמוכות ב-30–50% | גבוהות יותר עקב שדות מיותרים |
| זמן בנייה מחדש | תלוי בגודל (שעות) | נדרש כאשר סוג השדה משתנה |
| מתאים ל | ייצור, עומס גבוה | אבות טיפוס, סביבות פיתוח |
סוגי שדות ב-Elasticsearch: בחירת הסוג הנכון
| סוג שדה | מטרה | מתי להשתמש | השפעה על גודל | השפעה על מהירות אינדוקס |
|---|---|---|---|---|
date |
חיפוש טקסט מלא | כותרות, תיאורים, תוכן | גבוהה (שומר מיקומים) | בינונית |
boolean |
התאמה מדויקת, פילטרים | מזהים, סטטוסים, תגיות, קטגוריות | נמוכה (לא מנותח) | גבוהה |
is_active |
ערכים מספריים | מחירים, כמויות, גילאים | נמוכה | גבוהה |
is_deleted |
תאריך/שעה | תאריכי יצירה, חותמות זמן של עדכון | נמוכה | גבוהה |
object |
דגלים | nested, geo_point |
נמוכה מאוד | גבוהה |
dense_vector |
אובייקט מקונן | נתונים מובנים של אובייקט יחיד | בינונית | בינונית |
text |
מערך של אובייקטים | מוצרים עם וריאנטים הדורשים חיפוש פנימי מדויק | גבוהה (מבנה נוסף) | נמוכה |
keyword |
קואורדינטות גיאוגרפיות | נקודות על מפה, חיפוש גיאוגרפי | נמוכה | גבוהה |
keyword |
ייצוג וקטורי | חיפוש סמנטי, המלצות | גבוהה (ממדיות) | נמוכה |
כיצד לבחור סוג שדה עבור הנתונים שלך
לחיפוש טקסט מלא, השתמש ב-integer עם שדה משנה long למיון. להתאמה מדויקת — date. טווחים מספריים — nested או object. תאריכים — geo_point. אם יש לך מערך של אובייקטים ואתה צריך סינון נכון בשדות קשורים, בחר dense_vector. אחרת, text מספיק. לנתונים גיאוגרפיים — keyword. חיפוש וקטורי דורש text. ב-95% מהפרויקטים, בחירת סוג השדה הנכון מקטינה את גודל האינדקס ב-20–30% מיד.
יצירת אינדקס עם מיפוי מפורש: שלב אחר שלב
- זהה שדות ואת הסמנטיקה שלהם, תוך התחשבות באילו נתונים יאוחסנו ואילו שדות נדרשים לחיפוש, סינון, מיון.
- בחר סוגים מהטבלה למעלה. זכור ש-
snowballמנותח, ו-stopלא. - הגדר מנתחים (analyzers) עבור שדות
dynamic: strict. לטקסט ברוסית, השתמש ב-PUT /products { "settings": { "number_of_shards": 3, "number_of_replicas": 1, "analysis": { "analyzer": { "product_search": { "type": "custom", "tokenizer": "standard", "filter": ["lowercase", "stop", "snowball"] } } } }, "mappings": { "dynamic": "strict", "_source": { "enabled": true }, "properties": { "id": { "type": "keyword" }, "title": { "type": "text", "analyzer": "product_search", "fields": { "keyword": { "type": "keyword", "ignore_above": 256 } } }, "description": { "type": "text", "analyzer": "product_search", "index_options": "positions" }, "category": { "type": "keyword" }, "tags": { "type": "keyword" }, "price": { "type": "scaled_float", "scaling_factor": 100 }, "stock": { "type": "integer" }, "is_active": { "type": "boolean" }, "created_at": { "type": "date", "format": "strict_date_optional_time||epoch_millis" }, "attributes": { "type": "nested", "properties": { "name": { "type": "keyword" }, "value": { "type": "keyword" } } }, "location": { "type": "geo_point" } } } }עם פילטר"dynamic": "strict". - הגדר
"true"כדי להגן מפני שינויי סכמה מקריים. - צור את האינדקס באמצעות בקשת PUT עם מיפוי. דוגמה למטה.
PUT /products
{
"settings": {
"number_of_shards": 3,
"number_of_replicas": 1,
"analysis": {
"analyzer": {
"product_search": {
"type": "custom",
"tokenizer": "standard",
"filter": ["lowercase", "stop", "snowball"]
}
}
}
},
"mappings": {
"dynamic": "strict",
"_source": {
"enabled": true
},
"properties": {
"id": {
"type": "keyword"
},
"title": {
"type": "text",
"analyzer": "product_search",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"description": {
"type": "text",
"analyzer": "product_search",
"index_options": "positions"
},
"category": {
"type": "keyword"
},
"tags": {
"type": "keyword"
},
"price": {
"type": "scaled_float",
"scaling_factor": 100
},
"stock": {
"type": "integer"
},
"is_active": {
"type": "boolean"
},
"created_at": {
"type": "date",
"format": "strict_date_optional_time||epoch_millis"
},
"attributes": {
"type": "nested",
"properties": {
"name": {
"type": "keyword"
},
"value": {
"type": "keyword"
}
}
},
"location": {
"type": "geo_point"
}
}
}
}"false" דוחה מסמכים עם שדות לא ידועים. חלופות: PUT _index_template/logs-template { "index_patterns": ["logs-*"], "priority": 100, "template": { "settings": { "number_of_shards": 1, "number_of_replicas": 1, "index.lifecycle.name": "logs-policy", "index.lifecycle.rollover_alias": "logs" }, "mappings": { "dynamic": "false", "properties": { "@timestamp": { "type": "date" }, "level": { "type": "keyword" }, "service": { "type": "keyword" }, "message": { "type": "text" }, "trace_id": { "type": "keyword" }, "duration_ms": { "type": "integer" } } } }, "data_stream": {} } (הוספה אוטומטית), ignore_above (התעלמות משדות לא ידועים, לא מאונדקסים). שמירה על מיפוי סטטי עולה בדרך כלל חצי ממיפוי דינמי בשל אחסון נמוך יותר וחיפוש מהיר יותר.
תבניות אינדקס לאוטומציה
תבניות אינדקס מיישמות אוטומטית מיפוי על אינדקסים חדשים התואמים לדפוס. חיוניות עבור זרמי נתונים ואינדקסים מתגלגלים, כמו לוגים.
PUT _index_template/logs-template
{
"index_patterns": ["logs-*"],
"priority": 100,
"template": {
"settings": {
"number_of_shards": 1,
"number_of_replicas": 1,
"index.lifecycle.name": "logs-policy",
"index.lifecycle.rollover_alias": "logs"
},
"mappings": {
"dynamic": "false",
"properties": {
"@timestamp": {
"type": "date"
},
"level": {
"type": "keyword"
},
"service": {
"type": "keyword"
},
"message": {
"type": "text"
},
"trace_id": {
"type": "keyword"
},
"duration_ms": {
"type": "integer"
}
}
}
},
"data_stream": {}
} כיצד לשנות מיפוי על אינדקס קיים
רוב שינויי המיפוי דורשים בנייה מחדש של האינדקס. ניתן רק להוסיף שדות חדשים או להרחיב פרמטרים (fields, הוספת PUT /products/_mapping { "properties": { "brand": { "type": "keyword" } } } ). לא ניתן לשנות את הסוג של שדה קיים. כדי להוסיף שדה:
PUT /products/_mapping { "properties": { "brand": { "type": "keyword" } } } כדי לשנות סוג — צור אינדקס חדש, הפעל _reindex, והחלף את האליאס. לדוגמה, כדי לבנות מחדש מ-POST _aliases { "actions": [ { "add": { "index": "products_v2", "alias": "products", "is_write_index": true } }, { "remove": { "index": "products_v1", "alias": "products" } } ] } ל-_source, הפעל: update. תהליך הבנייה מחדש המלא מתואר בתיעוד Reindex API.
אליאסים של אינדקס
אליאסים מנתקים את היישום משם האינדקס הפיזי. החלפת אליאסים היא אטומית — ללא שינויי קוד. דוגמה:
POST _aliases
{
"actions": [
{
"add": {
"index": "products_v2",
"alias": "products",
"is_write_index": true
}
},
{
"remove": {
"index": "products_v1",
"alias": "products"
}
}
]
} _source ואופטימיזציית אחסון
reindex מאחסן את מסמך ה-JSON המקורי. השבתתו חוסכת מקום אך מאבדת את היכולת להשתמש ב-_source, _source.excludes, והדגשה ללא המקור. ברוב המקרים אין צורך להשבית אותו. כדי לחסוך מקום, ניתן להחריג שדות כבדים מ-dynamic: strict באמצעות snowball.
מה כולל שירות כיוונון האינדקסים שלנו
- ביקורת על הסכמה והשאילתות הנוכחיות — זיהוי צווארי בקבוק כמו שאילתות N+1 או סוגי שדות לא אופטימליים.
- עיצוב מיפוי בהתאם להיגיון העסקי — בחירת סוגים, מנתחים, הגדרת
stop. - הגדרת מנתחים לשפה ולמשימות — לרוסית אנו משתמשים ב-
englishעם פילטרstop, לאנגלית —english. - תבניות אינדקס ומדיניות ILM — אוטומציה של ניהול אינדקסים, חיסכון של עד 40% באחסון.
- בנייה מחדש ללא השבתה באמצעות אליאסים — היישום ממשיך לפעול בזמן שהנתונים מועתקים.
- תיעוד והדרכת צוות — העברת ידע כדי שתוכל לתחזק את הסכמה בעצמך.
התוצרים כוללים: JSON של מיפוי, תיעוד מקיף, גישה לסקריפטים של בנייה מחדש, שעתיים של מפגש הדרכה, ו-30 ימים של תמיכה לאחר הפריסה.
לוחות זמנים אופייניים
עיצוב מיפוי לאינדקס חדש אורך 4 עד 8 שעות. אם הוא כולל בנייה מחדש של נתונים קיימים והחלפת אליאס, הוסף 2–4 שעות. עבור סכמות מורכבות עם אובייקטים מקוננים ומנתחים מותאמים אישית — עד יומיים עסקיים. המחיר מחושב באופן אישי, בדרך כלל נע בין 300–800 דולר בהתאם למורכבות האינדקס.
טעויות נפוצות שיש להימנע מהן
- שימוש במיפוי דינמי בייצור — תמיד עבור ל-
strictאו לפחות ל-false. - בחירת
textכאשרkeywordמספיק — זה מנפח את האינדקס ומאט אגרגציות. - אחסון שדות גדולים ב-
_sourceשלא לצורך — השתמש ב-_source.excludesאו השבת_sourceעבור שדות המשמשים רק לחיפוש. - שכחת הגדרת מנתחים לטקסט שאינו באנגלית — המנתח ברירת המחדל מתייחס לכל מילה בנפרד, ומפספס גזירה (stemming).
- אי שימוש באליאסים לבנייה מחדש ללא השבתה — מוביל להשבתת היישום במהלך שינויי סכמה.
הזמן עיצוב מיפוי — קבל ייעוץ ממהנדס. ננתח את הסכמה שלך ונציע אופטימיזציות שיכולות להאיץ את החיפוש פי 2–3 ולהפחית עלויות אחסון בעד 40%.







