מדריך מקיף על Elastic Stack - חלק 3 - Elasticsearch

זה החלק השלישי במדריך על Elastic Stack. בחלק הראשון עסקנו ב-filebeat. בשני ב-logstash ובחלק הזה נעסוק בכלי השלישי בשרשרת - elasticsearch:




Elasticsearch זה ה-Data Base שלנו, והוא בעצם לב המערכת. Elasticsearch הוא DB מסוג NoSQL והוא שומר את הנתונים ב-json-ים. 

המבנה של ה-DB הוא כך:

index/mapping/document

נסביר על כל חלק:

document – שמירה של נתונים במערכת תהיה בפורמט json בתוך אובייקט שנקרא document. אם למשל ב-SQL כל שורה בטבלה מציינת נתון מסויים עם כמה שדות, אז ב-elasticsearch כל נתון נקרא document והוא בפורמט json.

mapping – ה-mapping דומה ל-schema ב-SQL. הוא מתאר את כל השדות שיש ב-Index. איזה שדות יש ומה סוג כל שדה. Elasticsearch יודע לייצר בצורה אוטומטית את ה-mapping (זה נקרא dynamic mapping) וזה יתרון גדול על SQL שבו לא ניתן להכניס נתונים לפני שמגדירים schema. ברור שאתה יודע טוב יותר מהו סוג המידע שלך מאשר מה ש-elasticsearch יקבע. ולכן ניתן לעדכן את ה-mapping לפי הצורך. בהמשך נראה את ה-mapping והדברים יהיו יותר מובנים. כאן ניתן לקרוא הסבר טוב על mapping.

index – אוסף של documents. כשמכניסים מידע לתוך elasticsearch צריך להגדיר לאיזה אינדקס הוא יכנס. תחת אינדקס יכולים להיות יותר מ-mapping אחד. 

כללים לנתינת שמות לאינדקסים:

  • חייב להיות באותיות קטנות
  • לא יכול להתחיל ב "_", "-", "+"
  • לא יכול לכלול רווח, פסיק או \, /, *, ?, ", <, >, |, #, :
  • לא יכול להיות שם שהוא רק "." או ".."
  • עד 255 תווים

ניתן גם לקנפג את המערכת כך ששמות של אינדקסים יווצרו באופן אוטומטי, למשל כתלות בתאריך.

קונפיגורציה

ניתן לקנפג את ה-cluster כולו וניתן גם לקנפג node מסוים. יש שתי אפשרויות קינפוג, דינאמי וסטאטי.

קינפוג דינאמי

ישנם הגדרות שניתן לקנפג באופן דינאמי על מערכת רצה. קינפוג דינאמי נעשה ע"י שימוש ב-cluster update settings API. ניתן לקנפג באופן קבוע (persistent) כך שהשינויים ישארו גם אחרי restart של ה-cluster. וניתן גם לקנפג באופן זמני (transient) שיתבטל לאחר restart של ה-cluster.

קינפוג סטאטי

קינפוג סטאטי מתבצע דרך הקובץ elasticsearch.yml. הקינפוג דרך הקובץ הזה צריך להתבצע על מערכת שלא רצה והוא צריך להתבצע על כל node במערכת.

קדימויות בקונפיגורציה

אם אותה הגדרה שונתה בכמה שיטות, צריך להבין מה סדר הקדימות. להלן סדר הקדימות:
  1. קינפוג זמני (transient)
  2. קינפוג קבוע (persistent)
  3. קינפוג דרך elasticsearch.yml
  4. ערכים דיפולטיביים
אם למשל היתה הגדרה ב-elasticsearch.yml ששונתה בקינפוג דינאמי באופן קבוע, אז הקינפוג הדינאמי יגבר על מה שכתוב ב-elasticsearch.yml. אם לאחר מכן שוב אותה הגדרה שונתה באופן זמני בקינפוג דינאמי, ההגדרה הזמנית תיגבר על ההגדרה הקבועה והיא זו שתשפיע עד ל-restart של המערכת.

חלוקה ל-Shards

כאשר אנחנו מתקינים elasticsearch ומריצים אותו, ה-elasticsearch מנסה להצטרף ל-cluster שאליו הוא שייך כפי שמוגדר לו ב-elasticsearch.yml. אם לא קיים cluster כזה, elasticsearch ייצור cluster חדש עם node אחד שזה בעצם המכונה הנוכחית שעליה elasticsearch רץ. בהתחלה אין שום מידע ב-elasticsearch ולכן כשנרצה להכניס את ה-document הראשון נצטרך ליצור אינדקס כיון שכל document צריך לשבת בתוך אינדקס. אם לא ניצור אינדקס הוא יווצר באופן אוטומטי (עם ערכים דיפולטיביים) כשנכניס document ראשון. כאשר יוצרים אינדקס צריך להגדיר מכמה shards האינדקס הזה יורכב. כל אינדקס חייב להיות מורכב לפחות מ-shard אחד, כיון שזה המקום שבו מאוחסן המידע. הדיפולט זה חמישה primary shards. 
מה הכוונה ב-primary shards?
הכוונה היא שה-documents שמאוכסנים באינדקס הנוכחי יתפצלו בין חמישה מחסנים.
 __  __  __  __  __
|1 ||2 ||3 ||4 ||5 |
|__||__||__||__||__|
בכל פעם שנכניס document חדש לאינדקס, elasticsearch יבחר shard אחד מתוך החמישה ושם יאחסן אותו. ה-primary shards הם המקום שהמידע שלנו מאוחסן, לא העתק של המידע אלא המידע עצמו.
למה בעצם כדאי לחלק את המידע בכל אינדקס למספר shards? 
יש לזה כמה יתרונות, קודם כל זה מאפשר למערכת לנתח כמויות מידע גדולות. המערכת של elastic נועדה לעבוד עם big data, וחלוקה ל-shards מאפשרת לחלק את המידע בין מכונות שונות וכך לאפשר עיבוד של big data. 
יתרון נוסף, אפילו כשיש לנו כמה shards על אותה מכונה, elasticsearch יודע לנצל את החלוקה של המידע ולעבד את ה-shards השונים באופן מקבילי.
כאשר מצטרפת ל-cluster מכונה נוספת, elasticsearch יעביר חלק מה-shards למכונה השניה כדי ליצור איזון וכך לאפשר שימוש בכל המכונות כדי להגדיל את היעילות. אז אם יש לנו חמישה shards ועכשיו יש לנו 2 מכונות אז elasticsearch יעביר למשל שני shards למכונה החדשה ואז נקבל את המצב הבא:
Node 1
 __   __   __
|1 | |2 | |3 |
|__| |__| |__|

Node 2
 __  __
|4 ||5 |
|__||__|

עד עכשיו דיברנו על primary shards, סוג נוסף של shards הוא ה-replica. הדיפולט הוא אחד, והכוונה היא שלכל primary shard יהיה replica shard אחד שיהיה העתק שלו. ה-replica shards נועדו לגיבוי, ולכן אף פעם לא יווצר replica על אותה מכונה שבה נמצא ה-primary shard התואם שלו. בנוסף, ה-replicas משפרים את ביצועי המערכת מבחינת חיפושים ומציאת documents.
אם נחזור לדוגמה שלנו, כאשר מוגדר לנו שיש replica אחד, נקבל את המצב הבא:
Node 1
 __  __  __  __  __
|1 ||2 ||3 ||4R||5R|
|__||__||__||__||__|

Node 2
 __  __  __  __  __
|1R||2R||3R||4 ||5 |
|__||__||__||__||__|
כפי שניתן לראות, כל המידע נמצא בכל אחד מה-nodes. ולכן בצורה הזו אם node אחד קרס, ה-replica shards אוטומטית יהפכו להיות primary והמערכת עדיין תהיה פעילה לגמרי. ברגע שהמכונה שנפלה תחזור היא תצטרף חזרה ל-cluster ושוב יחזרו להיות replicas. יקח קצת זמן עד שכל ה-shards יהיו פעילים לגמרי, כיון שה-shards במכונה שחזרה צריכים להסתנכרן עם המידע החדש שהתקבל בזמן שהמכונה לא היתה פעילה, אבל לאחר שהסינכון יסתיים, המצב יחזור להיות כמו לפני הנפילה של המכונה.

כפי שכתבתי לעיל, כאשר יוצרים אינדקס צריך להגדיר מכמה shards הוא יורכב וכמה replicas יהיו. כדי לעשות את זה ניתן להשתמש בפקודה הבאה (ב-console שנמצא ב-dev tools ב-Kibana):
PUT test-index
{
  "settings": {
    "index.number_of_shards": 3,
    "index.number_of_replicas": 1
  }
}

וכך יווצר אינדקס בשם test-index שמורכב משלוש shards עם replica אחד (לכל shard). לאחר שנוצר האינדקס לא ניתן לשנות את מספר ה-primary shards שהוא מורכב מהם (הסיבה שלא ניתן היא כיון שזה ישנה את הצורה שבה המידע מחולק בין shards וזה ישבור את ה-consistent hashing שזו השיטה ש-elasticsearch משתמש בה כדי לחלק מידע בצורה רוחבית), אבל כן ניתן לשנות את מספר ה-replicas בצורה הבאה:
PUT test-index/_settings
{
  "settings": {
    "index.number_of_replicas": 2
  }
}
ועכשיו האינדקס test-index יהיו מקונפג כך שיהיה לו שני replicas לכל shard.

הערה לגבי ה-health של המערכת: אם נגדיר אינדקס עם שלוש shards ועם replica אחד, אבל יהיה לנו רק מכונה אחת, ה-health של המערכת שלנו יהיה yellow וזה כיון שאף פעם לא נוצרים replicas על אותה מכונה שבה יש את ה-primary shards התואמים, ובמקרה הזה יש לנו רק מכונה אחת אז ה-replicas לא יווצרו. כיון שכך יש לנו בעצם שלוש replica shards שהם לא בשימוש, וזו הסיבה שהמערכת מוגדרת כ-yellow ולא כ-green. ברגע שנצרף מכונה נוספת ל-cluster ויהיה מקום ל-replicas המכונה תהפוך ל-green.

אז לכמה shards כדאי לחלק כל אינדקס?

כצפוי - זה תלוי... באתר הרישמי הסבירו את השיקולים בצורה הבאה:
ככל שמחלקים אינדקס ליותר shards יש יותר overhead למערכת בתחזוקה שלהם. מצד שני, אם יש קצת shards ולכן כל shard הוא גדול יחסית, ייקח יותר זמן להעביר אותו ל-node אחר ב-cluster כש-elasticsearch מוצא לנכון לעשות rebalace.
חיפוש שנעשה על המון shards הוא אמנם מהיר פר shard אבל יש overhead כללי בגלל כמות ה-shards. ולכן חיפוש על כמות קטנה יותר של shards כאשר כל shard הוא יותר גדול כנראה תהיה מהירה יותר.
ככלל אצבע אפשר לומר שגודל כל shard צריך להיות בין כמה GB בודדים ועד לעשרות GB. למקרים שבהם יש מידע שמבוסס על זמן מקובל שגודל כל shard הוא בטווח של 20GB-40GB. 
מצד שני, צריך להימנע מכמות גדולה של shards. כמות ה-shards של כל אינדקס תלויה בגודל ה-heap הזמין לנו במכונה. בכללי אפשר לומר שמספר ה-shards פר GB של heap צריך להיות קטן מ-20.

elasticsearch.yml 

במערכת שלנו אנו נקנפג ה-elasticsearch דרך הקובץ elasticsearch.yml שנמצא בתיקייה /etc/elasticsearch.

נעבור על כמה הגדרות בסיסיות:

  • cluster.name – השם של ה-cluster. אותו שם צריך להיות בכל elasticsearch שמותקן בשאר המכונות ב-cluster.
  • node.name – שם המכונה. כך שאר המכונות ב-cluster יוכלו להתייחס למכונה הזו בעזרת השם הזה.
  • path.data – המיקום שבו אנחנו רוצים לאחסן את המידע
  • path.logs – מיקום הלוגים של elasticsearch (הלוגים שהוא בעצמו כותב ולא הלוגים שהם הקלט שלו ובהם הוא משתמש כדי להוציא מידע)
  • network.host – מכונה אחרת שהמכונה הזו תתחבר אליה כדי ליצור cluster. ניתן להכניס IP או hostname. אם לא צריך cluster אין צורך בשדה הזה כי הדיפולט שלו זה _local_.
  • http.port – הפורט שדרכו מתחברים ל-elasticsearch ב-HTTP. למשל Kibana מתחבר בפורט הזה.

דוגמה לקובץ elasticsearch.yml:
cluster.name: es-01

node.name: node-1

path.data: /data/mydata01,/data/mydata02

network.host: 0.0.0.0

http.port: 9200

הרצת elasticsearch

לאחר שקינפגנו את ה-elasticsearch אנחנו מעוניינים להריץ אותו כ-service. לצורך כך נשתמש בפקודה:
service elasticsearch start
אם אנחנו רוצים לראות מה מצב ה-service נשתמש בפקודה:
service elasticsearch status
ואם אנחנו רוצים לעצור את הריצה, נכתוב:
service elasticsearch stop

המערכת שלנו פשוטה ולכן אפשר לעצור את ה-elasticsearch ולהריץ מחדש בלי בעיה. במערכת אמיתית שצריכה להישאר זמינה כל הזמן הורדה והרמה של המערכת צריכה להעשות בכמה שלבים כדי לא לפגוע בזמינות המערכת. ניתן לקרוא על הנושא הזה כאן.

תשאול ה-Data Base

ל-elasticsearch יש REST API שבעזרתו ניתן לחקור את ה-DB. להלן כמה דוגמאות שימושיות (השתמשתי ב-localhost בהנחה שאנחנו משתמשים במכונה שבהelastisearch  רץ. אם אנחנו משתמשים במכונה אחרת נשתמש כמובן ב-IP שבו רץ elasticsearch). 

הערה: יש מערכות שצריך לכתוב את פקודת curl עם מקף "-" לפני הפקודה המבוקשת כמו curl -GET ויש כאלה ללא מקף.

  • כדי לקבל מידע כללי נכתוב:

curl -GET http://localhost:9200

נקבל json קצר עם שם ה-node הנוכחי, שם ה-cluster, גירסת elasticsearch, ועוד.

  • בדיקת תקינות המכונה, כמה מכונות יש ב-cluster ועוד:

curl -GET http://localhost:9200/_cat/health?v

התוספת "?v" (קיצור של verbose) מספקת לנו תוצאות יותר מפורטות. למשל במקרה הזה היא מוסיפה כותרת לכל שדה שמודפס.

  • רשימת האינדקסים, כמה documents יש בכל אינדקס, כמה זיכרון תופס כל אינדקס, ונתונים נוספים על האינדקסים:

curl -GET http://localhost:9200/_cat/indices?v

  • כדי להדפיס את האינדקסים בצורה ממויינת לפי שם האינדקס:

curl -GET http://localhost:9200/_cat/indices?s=index

  • הדפסת אינדקס:

curl -GET http://localhost:9200/index_name?pretty

שימו לב, הוא לא מדפיס את ה-documents שהאינדקס מכיל, אלא json שמתאר את האינדקס, למשל מה ה-mapping שהוא מכיל, מתי הוא נוצר, כמה העתקים (replicas) יש לו (דבר זה תורם ליכולת ה-fault-tolerance), ועוד.
  • הדפסה של כל ה-documents שנמצאים באינדקס מסוים:

    curl -GET http://localhost:9200/indexName/_search?pretty=true&q=*:*indexName

    • הדפסה של document מתוך אינדקס מסוים:

    curl -GET http://localhost:9200/index_name/_doc/docuent_id?pretty

    • מחיקה של אינדקס:

    curl -XDELETE http://localhost:9200/index_name

    • מחיקה של כל האינדקסים:

    curl -XDELETE http://localhost:9200/*

    כמקובל, הכוכבית מסמלת כל תו או תוים ולכן אפשר גם להשתמש בכוכבית כדי למחוק קבוצות של אינדקסים. למשל אינדקסים שמתחילים בשם מסוים ואח"כ יש תאריך – my_index_05_2020, my_index_04_2020, נוכל למחוק את כל הקבוצה הזו ע"י

    curl -XDELETE http://localhost:9200/my_index_*


    עד כאן להפעם.

    אם אהבתם, תכתבו משהו למטה...

    6 תגובות:

    1. אנונימי8/8/21 21:52

      תודה רבה לך רפאל

      השבמחק
    2. אנונימי18/10/21 09:13

      תודה על הסדרה המפורטת

      השבמחק
      תשובות
      1. אנונימי21/4/22 18:38

        תודה רבה
        אם תוכל לכתוב מדריך מסודר על חיפוש עם השלמה אוטומטית של כמה שדות זה ממש יעזור לי.
        לא מצאתי לזה שום מדריך באינטרנט

        מחק
      2. שלום אנונימי, לצערי אין לי זמן כרגע. אבל ממליץ לך לנסות לבד, ככה לומדים הכי טוב

        מחק
    3. אנונימי3/3/23 12:57

      תודה רבה רפאל, מאמר מעולה ממש. והעובדה שהוא בעברית משמעותית מאוד. הרבה יותר קל להבין מידע חדש כשההסבר כתוב בעברית

      השבמחק