מתכנתי Backend יכולים לאחסן נתונים בפורמט JSON כבר זמן רב, מאז PostgresSQL 9.2. לפתרון זה יתרונות רבים. למרבה הצער, יש לו גם מספר חסרונות. 

מאז גרסה 9.4 הוצגה הייצוג הבינארי של JSON – JSONB – השומר נתונים לאט יותר מ-JSON, אך מאיץ את עיבודם ומאפשר יצירת אינדקסים. זה ידוע לכל שניתן ליצור אינדקסים על עמודת JSONB, אך הנושא מורכב למדי ואפשר בסופו של דבר לקבל שאילתות לא יעילות. 

במאמר זה נתמקד ב-JSONB. אציג שיטות עבודה מומלצות לשימוש בעמודה מסוג זה ואדגיש את מגבלותיה. 

דוגמה כיצד ליצור טבלה ולמלא אותה בנתונים 

אני אוהב להמחיש את הנאמר, אז בואו ניצור טבלה עם עמודה מסוג JSONB עם קצת נתונים: 

JSONB in JAVA projects Pic1

עמודה מסוג JSONB מכילה שדה טקסט (שם), רשימה (מפלים), ומפה (אטרקציות). 

לאחר הרצת "select * מחופשות" נקבל: 

JSONB in JAVA projects Pic2

יצירת מודל כזה היא פשוטה. אין צורך לציין סוגי שדות. הכול הומר ל-JSONB ללא כל בעיות. עמודת ה-ID נוספה מכיוון שהטבלה תמופה לישות ב-JPA שדורשת ID. 

יישום JSONB 

מתי להשתמש ב-JSONB: 

  • מבנה נתונים בלתי צפוי: עלינו לשמור נתונים במסד הנתונים, אך לא ברור אילו נתונים נקבל מספק חיצוני. לחלופין, אנו מאפשרים ללקוח להגדיר שדות משלו. 
  • הרבה מאפיינים (שדות) שבשימוש נדיר, למשל נתונים הנשמרים במסד הנתונים רק לצורכי תצוגה. או שאנו שומרים אותם רק משום שיום אחד הם עשויים להיות שימושיים. 
  • יחסים למספר אובייקטים: כאשר איננו רוצים להחיל את הצטרף סעיף לטבלה עם קשרים מסיבות ביצועים. אנו יכולים לשמור בקלות את רשימת היחסים ב-JSONB. 

מתי לא להשתמש ב-JSONB: 

  • שאילתות לא יעילות: Postgres בשאילתות משתמש בסטטיסטיקות שאינן נשמרות עבור JSONB. הוא מנחש כיצד לבצע שאילתה ולעיתים מנחש באופן שגוי. הודות לאינדקסים, ניתן להאיץ את השאילתה, אך אכתוב על כך בהרחבה בהמשך המאמר. 
  • מקום דיסק מוגבל: KEY ו-VALUE נשמרים ב-JSONB. אם יש לכם מיליון רשומות ואתם מחזיקים KEY/VALUE עבור כל אחת מהן, ה-KEY ישוכפל מיליון פעמים. אני ממליץ לקרוא את המאמר המתאר כיצד מקום הדיסק צומצם ב-30% לאחר הסרת 45 מפתחות לעמודות – https://heap.io/blog/when-to-avoid-jsonb-in-a-postgresql-schema 
  • כאשר אנו רוצים להטיל מגבלה על שדה: לא קל להפוך שדה לייחודי. 

אופטימיזציה של שאילתות: מבט מעמיק על אינדוקס 

JSONB in JAVA projects tab1 new

B-Tree: זה עובד רק עם אופרטור השוויון (=). ניתן לבצע זאת עבור עמודת JSONB כולה או כפונקציה על שדה ספציפי.  

Hash: בדומה ל-B-tree, הוא פועל רק עם אופרטור השוויון (=). ניתן להשתמש בו גם על שדה ב-JSONB. עם B-tree, שאילתות מהירות יותר מאשר hash, אך תופסות יותר מקום וההכנסות אורכות יותר זמן.  

Gin:   

אינדקס GIN עם אופרטור המחלקה JSONB_OPS (ברירת מחדל) תומך באופרטורי קיום (?,? &,? |) ובהכלה (@>). הוא פועל רק על KEY. ניתן לבצע אותו על עמודה או על שדה. יש להיזהר לגבי שטח דיסק, מכיוון שהוא תופס הרבה מקום. הוא פחות יעיל מ-Hash ומ-B-Tree, אך גמיש יותר. 

  • Gin עם אופרטור המחלקה JSONB_PATH_OPS תומך רק ב-@> אך תופס פחות מקום, SELECT, UPDATE, DELETE וזמן הבנייה מהירים יותר מ-JSONB_OPS. 
  • Gin עם השימוש ב-GIN_TRGM_OPS כאינדקס היחיד תומך באופרטור like. לשם כך, עליך להתקין את התוסף PG_TRGM. זה עובד רק עבור ערכים, לא עבור מפתחות. 

כיצד נראות שאילתות Java? 

בפרויקטים שלי, אני משתמש בשיטות שונות לביצוע שאילתות באמצעות Spring Data JPA. כדי לנסות להשתמש באינדקסים שנוצרו בטבלת JSONB, נתמקד ב-3 אפשרויות: 

  • הצהרת @Query
  • מפרטים
  • @Query declaration with native query

הצהרת @Query 

JPA אינו תומך באופרטורים של JSONB (- >>? @> וכו'). לדוגמה, האופרטור – >> יכול להיות מוחלף בפונקציה JSONB_EXTRACT_PATH_TEXT. 

JSONB in JAVA projects code1

A דומה query יכול be הושג באמצעות ה מפרט. 

מפרטים 

JSONB in JAVA projects code2

שימוש במפרטים לשאילתות אינו קריא, אך הוא מצוין לבניית שאילתות דינמיות. למרבה הצער, כתיבת שאילתה באמצעות פונקציה לא תהיה יעילה. אם ניצור אינדקס: BTREE ((data – >> ‘name’)), הוא יושמט בעת ביצוע השאילתה באמצעות מפרטים. אם ברצוננו להאיץ את השאילתה, נוכל להשתמש בפונקציה CREATE INDEX index func_hname_indx ON holidays (jsonb_extract_path_text (data, ‘name’)); 

@Query עם שאילתה מקורית 

JSONB in JAVA projects code 3

שאילתות מקוריות מאפשרות שימוש באופרטורים המיועדים לחיפושי JSONB, למשל data – >> ‘waterfalls’, ובכך שימוש באינדקסים. 

בסעיף הבא אתאר רשימה של שאילתות עם מידע על ביצועים. 

הדוגמאות ליצירת אינדקסים ושאילתות 

* SEQ = חיפוש סדרתי על פני כל הטבלה 

* סריקת אינדקס = חיפוש מהיר באמצעות האינדקס

JSONB in JAVA projects tab2 new

הרשימה לעיל ממחישה את הגמישות של אינדקס Gin, אשר יכול לשמש במצבים רבים. 

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

JSONB in JAVA projects tab 3

מיון התוצאות: סעיף ה-"order by" is רק נתמך על ידי אינדקס BTree 

JSONB in JAVA projects code 4

תקציר 

JSONB שימושי מאוד במקרים רבים, במיוחד עבור נתונים לקריאה בלבד ונתונים שאינם דורשים שאילתות מתקדמות. 

אם הפרויקט שלנו דורש שימוש ב-JSONB ושאילתות מורכבות יותר, תמיד נוכל ליצור אינדקס וליישם שאילתה מקורית. 

עבור מסדי נתונים גדולים (למעלה ממיליון רשומות), יש להיות זהירים במיוחד ולשקול לשמור את הנתונים בעמודות המסורתיות שעבורן נשמרת הסטטיסטיקה ומשמשת לאופטימיזציה של שאילתות.