מדוע אפליקציית מובייל מאטה עם גידול בנתונים
אפליקציה שעפה בהתחלה מתחילה להאט באופן מורגש כשמסד הנתונים המקומי גדל מעבר לעשרות אלפי הרשומות הראשונות.
רשימת אנשי הקשר או פיד ההזמנות נטענים בקפיצות, החיפוש מגיב באיחור, והגלילה מקרטעת — כי כל שאילתה לאחסון המקומי של האפליקציה עוברת כעת דרך הדיסק ולא דרך המטמון, ומבוצעת ב-thread הראשי של הממשק.
לאחר מכן מצטרפת הבעיה השנייה — חוסר סנכרון. חלק מהנתונים הספיקו לעלות לשרת, חלק נשארו בתור, ולאחר הפעלה מחדש המשתמש רואה תמונה מיושנת: הזמנה שכבר שלח שוב תלויה כטיוטה.
בעת ניתוק החיבור ברכבת התחתית או במעלית שינויים שלא נשמרו הולכים לאיבוד לחלוטין, אם האחסון אינו יודע לשרוד הפסקת טרנזקציה.
סיפור נפרד — סנכרון נתונים במצב לא מקוון. כשהאפליקציה חוזרת לרשת, היא מנסה למזג את העריכות שנצברו במכה אחת: קונפליקטים בין גרסאות נפתרים לפי "מי שאחרון צודק", ומעבר כבד על כל הטבלה חוסם את הממשק לשניות. המשתמש רואה מסך קפוא וסוגר את האפליקציה — בדיוק ברגע שבו הרשומה טרם נשמרה.
השורש של כל שלושת הכשלים אחד: האחסון המקומי תוכנן לנפח דמו, ולא לצמיחה אמיתית. כאן נכנס לתמונה realm בסיס נתונים — אחסון אובייקטים שבו רשומות נקראות כאובייקטים רגילים, שאילתות עוברות דרך אינדקסים, ושינויים מגיעים לממשק באופן ריאקטיבי, בלי קריאה חוזרת ידנית של טבלאות. נפרק כיצד זה נסגר בפועל: מסכמת האחסון והאינדקסים ועד לתור הסנכרון הנדחה ולכללי פתרון קונפליקטים.
מה נותנת מסד נתונים realm מוגדר כהלכה
סכמת נתונים כבסיס לתגובת הממשק
מהירות התגובה של הממשק נקבעת לא בעוצמת המכשיר, אלא באופן שבו סכמת הנתונים של האפליקציה מעוצבת. כאשר אובייקטים מתוארים באמצעות קשרים ולא כעותקים מקוננים, שליפת רשימה מתבצעת לפי שדה מאונדקס ומסתכמת בשברירי פריים — הגלילה נשארת חלקה גם באוספים גדולים.
סכמה שטוחה ללא אינדקסים נותנת אפקט הפוך: כל מסך מרכיב מחדש את הנתונים, והממשק "נתקע" עם גדילת מסד הנתונים.
מצבי סנכרון ועמידות בפני ניתוקי תקשורת
הפרמטר המרכזי הוא מצב הסנכרון. כתיבה מקומית עם טעינה ברקע מאפשרת לאפליקציה לעבוד במצב לא מקוון: המשתמש רואה נתונים עדכניים מהאחסון המקומי, והשינויים נשלחים לשרת ברגע שהחיבור מתחדש.
תור השינויים שורד הפעלה מחדש של האפליקציה, ולכן אף עריכה לא הולכת לאיבוד בעת ניתוק רשת במטרו או במעלית. זה מסיר מחלקה שלמה של כשלים — "הנתונים לא נשמרו" ו"המסך ריק ללא אינטרנט".
מה נותנת ההגדרה בפועל
| אפשרות | מתי מתאימה | מגבלות |
|---|---|---|
| מסד נתונים מקומי ללא סנכרון | ספרי עזר, מטמון, מצב לא מקוון | אין נתונים משותפים בין מכשירים |
| כתיבה מקומית + סנכרון ברקע | רוב אפליקציות המוצר | נדרשת טיפול בקונפליקטים של גרסאות |
| עבודה ישירה דרך השרת | נתונים תמיד מקוונים, נפח קטן | הממשק תלוי באיכות החיבור |
אנחנו עובדים על Realm — מסד נתונים אובייקטי נותן תגובת רשימות מהירה פי כמה בהשוואה לבניית שאילתות מעל אחסון קבצים, ונפח נתוני העזר עם סכמה תקינה מצטמצם פי שלושה.
תוצאה מדידה
לאחר הגדרת הסכמה ומצבי הסנכרון, זמן פתיחת מסכים כבדים מתקצר משניות לשברירי שנייה, ושיעור הפניות שהסתיימו בשגיאה ברשת לא יציבה יורד לאחוזים בודדים. בנפרד נעלמת מחלקת כשלים הקשורים לחוסר סנכרון: המשתמש כבר לא רואה נתונים מיושנים לאחר החזרה לאפליקציה.
כיצד אנו מגדירים אחסון: שלבי העבודה ההנדסית
הגדרת אחסון מקומי מתחילה לא בקוד, אלא בביקורת: אנו בוחנים אילו ישויות כבר קיימות במכשיר, כיצד הן מקושרות, היכן נרשמות כפילויות ואילו שאילתות מבוצעות בתהליך הראשי.
לעתים קרובות מתברר שחלק מהנתונים כלל אינם צריכים להישמר באופן לא מקוון, וחלקם, להיפך, הולכים לאיבוד בעת עדכון האפליקציה. מכך תלויים הסכימה, האינדקסים ואסטרטגיית הסנכרון עם השרת.
- ביקורת אחסון. אנו מנתחים את הסכימה הנוכחית, נפח הנתונים, תדירות הקריאה והכתיבה, ההתנהגות בעת יציאה מהחשבון והתקנה מחדש. אנו מתעדים צווארי בקבוק: שליפות כבדות, היעדר אינדקסים, נעילות בעת כתיבה.
- תכנון סכימת נתונים. אנו מגדירים ישויות, קשרים וסוגי שדות בהתאם לתרחישי מסך אמיתיים, ולא "לעתיד". אנו מכניסים גרסאות לסכימה, כדי שהעדכון הבא של האפליקציה לא ישבור את נתוני המשתמש.
- הגדרת תצורה ומיגרציות. אנו מתארים את כללי המעבר בין גרסאות הסכימה, את סדר ההוספה והמחיקה של שדות, ואת ההתנהגות בעת חזרה לגרסה קודמת. מיגרציית נתוני האפליקציה מתבצעת ללא אובדן רשומות משתמש.
- אופטימיזציה של שאילתות. אנו מעבירים שליפות כבדות מהתהליך הראשי, מוסיפים אינדקסים עבור מסננים ומיון ספציפיים, ובודקים את ההתנהגות במכשירים חלשים ובמצב של אחסון מלא.
- בדיקה על נתונים אמיתיים. אנו מריצים תרחישים של עדכון, הפסקת כתיבה, חוסר מקום וגישה מקבילה. אנו בוחנים מה קורה בעת התנגשות בין הגרסה המקומית לגרסת השרת של הרשומה.
- מסירת תיעוד. אנו מוסרים את הסכימה, כללי המיגרציות, תיאור האינדקסים והוראות להוספת שדות חדשים — כדי שצוות הלקוח יוכל לפתח את האחסון בלעדינו.
את הסכימה ואת כללי המיגרציות אנו מתארים כך שניתן יהיה לקרוא אותם ללא צלילה לקוד: טבלת ישויות, קשרים, גרסאות וסדר השינויים. זה פותר את הבעיה הטיפוסית שבה כעבור חצי שנה איש אינו זוכר מדוע השדה נקרא דווקא כך ומה לעשות איתו במהדורה הבאה.
במה שונה מסד נתונים מקומי מסנכרון בענן?
מסד הנתונים Realm פותר שתי משימות שונות שלעיתים קרובות מתבלבלות ביניהן: אחסון נתונים על המכשיר וסנכרון שלהם בין מכשירים לשרת. אחסון מקומי הוא קובץ על הדיסק של הטלפון, שהאפליקציה ניגשת אליו ישירות, ללא רשת.
סנכרון בענן הוא תוספת שמנטרת שינויים באובייקטים ומעבירה אותם בין מכשירים דרך השרת, תוך פתרון קונפליקטים לפי כללים מוגדרים מראש. זה לא "או-או": הסנכרון פועל מעל האחסון המקומי, ולא במקומו.
ההבדל מתגלה ברגע שהרשת נעלמת. במצב מקומי האפליקציה ממשיכה לקרוא ולכתוב למסד באותה מהירות — השאילתות פונות לקובץ, ולא לשרת, ולכן הממשק לא נחסם ולא מציג אינדיקטורי טעינה אינסופיים.
בסנכרון ללא מצב אופליין מחושב היטב, חלק מהפעולות בתור וממתינות לחזרת החיבור, ועריכות מתנגשות יש לפתור בדיעבד. בדיוק משום כך הבחירה במצב אחסון מתחילה לא בשאלה "האם צריך סנכרון", אלא בשאלה "מה חייב לעבוד ללא רשת".
יש גם מחיר שני — מורכבות מודל הנתונים. סכימה מקומית חיה לפי הכללים של מכשיר אחד: מיגרציות מיושמות בעת עדכון האפליקציה, גרסת הסכימה גדלה באופן ליניארי, וחזרה לאחור צפויה.
סנכרון מוסיף ממדים — מספר מכשירים עורכים אובייקט אחד, חלק מהשינויים מגיעים באיחור, ולוגיקת הגישה בשרת חייבת להתאים לזו שבצד הלקוח. ככל שיותר ישויות משתתפות בהחלפה, כך יקרה יותר הטעות בכללי פתרון הקונפליקטים: אי-התאמה בנתונים בין מכשירים המשתמש מבחין בה לפני שתקבלו אות.
הכלל המעשי פשוט. אם הנתונים שייכים למכשיר אחד ולמשתמש אחד — הערות, מטמון קטלוג, טיוטות, הגדרות — אחסון מקומי מספיק, והוא נותן זמן תגובה מינימלי.
אם אותה קבוצת נתונים צריכה להיראות במספר מכשירים או להימשך לטלפון חדש לאחר התחברות — יש צורך בסנכרון, אך עם מצב אופליין מתואר בבירור: מה נכתב מקומית, מה עובר לתור, מה מתנגש וכיצד. אפשרות משולבת גם עובדת: ספרי עזר ותוכן משתמש מסונכרנים, בעוד נתונים מקומיים כבדים נשארים על המכשיר.
הטעות שאנו רואים לרוב היא להפעיל סנכרון "ליתר ביטחון" ולקבל תעבורה גדלה, השהיות בממשק ואי-התאמות שאי אפשר לשחזר. הטעות ההפוכה היא להחזיק הכל מקומית במקום שבו המשתמש מחכה לנתונים שלו במכשיר שני.
אנו מנתחים תרחישי שימוש, קובעים אילו ישויות חייבות לשרוד החלפת מכשיר, ורק אחר כך בוחרים מצב אחסון. תארו את המשימה — נחזור עם הערכה ותוכנית.
מקרה בוחן: האצנו את האפליקציה פי שלושה על בסיס נתונים גדל
הלקוח הגיע עם תלונה: האפליקציה "קופאת" בעת פתיחת רשימת ההזמנות. על סט בדיקה של כמה מאות רשומות הכול עף, אבל בפרודקשן הצטברו אצל המנהלים כמה עשרות אלפי אובייקטים — והמסך נפתח תוך 2.5–3 שניות, הגלילה הייתה מקוטעת, והחיפוש בהיסטוריה עבד בהשהיה מורגשת.
המדידות הראו: עיקר הזמן לא הושקע ברינדור, אלא בקריאה מאחסון — השאילתות בוצעו על ה-thread הראשי, הסכמה הייתה שטוחה, והסנכרון משך את כל סט האובייקטים בכל הפעלה.
מה שינינו
עיבדנו מחדש את הסכמה: הפרדנו ישויות מקושרות לטבלאות נפרדות, הוספנו שדות עם אינדקס לסינון ולמיון, והסרנו אוספים מקוננים כפולים. קריאות כבדות הועברו לתורים ברקע, והתוצאה הועברה לממשק דרך צפייה בשינויים — הרשימה מתעדכנת מעצמה, ללא שאילתות חוזרות ידניות.
הסנכרון הועבר מטעינה מלאה לאינקרמנטלי: הלקוח מבקש רק רשומות שהשתנו לפי חתימת זמן, והקונפליקטים נפתרים לפי הכלל "השינוי האחרון מנצח" עם יומן לבדיקת מקרים שנויים במחלוקת.
במקביל הוגדרה מדיניות ניקוי: רשומות מיושנות מעבר לתקופה מוגדרת נמחקות על ידי משימת רקע, ונפח האחסון הפסיק לגדול ללא גבול.
מדדים לפני ואחרי
| מדד | לפני | אחרי |
|---|---|---|
| פתיחת הרשימה | 2.5–3 ש' | 0.8–0.9 ש' |
| גלילה | מקוטעת | פריימים יציבים |
| סנכרון מלא בהפעלה | כל הסט | רק שינויים |
| גדילת האחסון | ליניארית | מוגבלת על ידי מדיניות ניקוי |
בנפרד מדדנו הפעלה קרה: הזמן עד למסך מוכן הצטמצם כמעט פי שלושה, וצריכת הזיכרון במכשירים חלשים ירדה בכ-שליש — האפליקציה הפסיקה להיות נסגרת על ידי המערכת ברקע.
תוצאת ההטמעה
המהירות עלתה פי שלושה על נפחי נתונים אמיתיים, ולא על סט דמו. הכשלים בגלל חוסר בזיכרון נעלמו, והתלונות על "תקיעות" פסקו. הסכמה וכללי הסנכרון תועדו, כדי שצוות הלקוח יוכל לפתח אותה בלעדינו.
עבדנו עם Realm — מסד נתונים אובייקטי שמאחסן נתונים ישירות על המכשיר ואינו דורש שרת נפרד.
ספקות לפני הטמעה: תאימות, מיגרציה, חזרה לאחור
מה יקרה לסכמת הנתונים בעת עדכון האפליקציה — האם נתוני המשתמשים יאבדו?
הסכמה מנוהלת בגרסאות, והמעבר בין גרסאות מתואר בכללים מפורשים: אילו שדות נוספו, שונו בשמם או נמחקו וכיצד מועברים הערכים. בעת ההפעלה האפליקציה מיישמת את המעבר פעם אחת, בטרנזקציה — אם הוא לא עבר, מסד הנתונים נשאר במצבו הקודם, ולא במצב半半-מורכב.
לפני ההפעלה הראשונה של הגרסה החדשה במכשיר נעשית גיבוי של קובץ האחסון, ולכן חזרה לגרסה קודמת של האפליקציה אינה מותירה את המשתמש בלי נתונים.
מה אם יהיה צורך לחזור לגרסה קודמת? חזרה לאחור של שינויים היא לא רק החזרת build, אלא גם מעבר סכמה הפוך. אנו מתארים אותו מראש לכל release, כדי שהחזרה לא תהפוך לעריכה ידנית של נתונים במכשירים.
כיצד מופרדות הרשאות הגישה לאחסון? הרשאות הגישה לאחסון מתוארות לפי תפקידים: מי קורא, מי כותב, מי משנה את הסכמה. זה מתועד במסמכי מסד הנתונים, ולא נשאר בראש של המפתח.
מה אם לחלק מהמשתמשים יש גרסה ישנה של האפליקציה? מספר גרסאות סכמה נתמכות במקביל, כל עוד מתבצע עדכון הדרגתי, — הסנכרון לא נשבר אצל אלה שטרם עדכנו.
לאחר המסירה הלקוח מקבל לידיו:
- תיאור סכמת הנתונים עם גרסאות והכללים למעבר ביניהן;
- מערכת כללים למיגרציית נתונים לכל release, עם בדיקה על נפחים אמיתיים;
- נוהל חזרה לאחור של שינויים — גם של הסכמה וגם של האפליקציה;
- מטריצת הרשאות גישה לאחסון לפי תפקידים;
- תיעוד מסד הנתונים: מבנה, קשרים, אילוצים, סדר גיבוי;
- מערכת בדיקות אוטומטיות שמופעלות לפני כל עדכון;
- הוראות לצוות התמיכה: כיצד לקרוא את מצב מסד הנתונים ומה לעשות בעת תקלה.
תארו את המשימה שלכם — נחזור עם הערכה ותוכנית: נראה כיצד הסכמה תתאים לנתונים הקיימים ואילו צעדים יידרשו בעדכון הראשון.







