צד שרת ל-dApp ב-Node.js: פיתוח מאפס

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

שירותי פיתוח בלוקצ'יין

שאלות נפוצות

העבודות האחרונות

  • פיתוח אפליקציית ווב עבור FEEDME
    פיתוח אפליקציית ווב עבור FEEDME
    1375
  • פיתוח חנות מקוונת לחברת FURNORO
    פיתוח חנות מקוונת לחברת FURNORO
    1349
  • עיצוב לוגו לחברת B2B Advance
    עיצוב לוגו לחברת B2B Advance
    794
  • פיתוח אפליקציית ווב עבור Enviok
    פיתוח אפליקציית ווב עבור Enviok
    1087
  • פיתוח לוגו חברת AIDER
    פיתוח לוגו חברת AIDER
    1032
  • פיתוח CRM עבור Chasseurs
    פיתוח CRM עבור Chasseurs
    1129

פיתוח צד שרת ל-dApp: היכן זה נשבר תחת עומס

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

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

נקודה שנייה — עסקאות תקועות. עסקה עם עמלה נמוכה מדי נשארת בממפול, והעסקה שאחריה לא תעבור בגלל סדר קפדני לפי מספר: השירות כבר השיב "נשלח", למרות שהפעולה לא בוצעה. בלי מחזור חיים מפורש — נשלחה, אושרה, הוחלפה, בוטלה — רישום הפעולות לא תואם את המציאות.

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

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

אפקט העיבוד מחדש: מהירות תגובה ויציבות

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

תפוקת השירות מפסיקה להיתקע בתהליך אחד: את העבודה מפזרים בין וורקרים, ופיקים נספגים בתור. בשיא גבוה פי 5–10 מהממוצע השירות לא מחזיר שגיאות — רק זמן ההמתנה גדל, וגם זה בתוך שניות.

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

משך זמן ההשבתה משתנה גם הוא: עדכונים מגלגלים למופע אחד בכל פעם, המצב משוחזר מצילום מצב ומיומן, ולכן ההפסקה נמדדת בשניות ולא בדקות. אנחנו בונים על Node.js — מודל לא חוסם מאפשר חיבורים רבים בו-זמנית בלי עלייה בהשהיות.

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

אפשרויות בנייה של צד השרת ל-dApp: השוואה

צד השרת של dApp מתקשר עם הרשת דרך אחד מארבעה מעגלים, והבחירה ביניהם קובעת מה יישבר ראשון: זמינות, מהירות תגובה או מהימנות הנתונים. להלן השוואה לפי שתי שאלות: מתי האפשרות מתאימה ואילו מגבלות יהיה צורך לקבל במודע.

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

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

שלבי העבודה: מביקורת תכנית ועד מסירת הפרויקט

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

  1. ביקורת צד השרת. בודקים היכן נשמרים המפתחות, איך נחתמות הפעולות, מה הועבר לתורים, אילו שגיאות חוזרות ביומנים. בפלט — רשימת צווארי בקבוק לפי השלכות כשל.
  2. מפת זרימת נתונים. מתעדים את מסלול האירוע כולו: קליטה, בדיקה, שליחה לרשת, אישור, כתיבה למסד, תשובה. רואים היכן אין הגנה מפני חזרות והיכן הלקוח יקבל מצב מיושן.
  3. תכנית פריסה. מתארים סביבות, תפקידי שירותים, אחסון סודות, מגבלות וכללי חזרה — בסיס גם להרכבה וגם למסירה לצוות.
  4. הגדרת שירותים. אינדוקס, מטמון, תורים ונראות: מדדים, מעקב בקשות, התראות על אי-התאמת יתרות.
  5. בדיקה תחת עומס. בונים פרופיל לפי תרחישים אמיתיים: שיא קריאה, פרץ כתיבה, כשל צומת רשת. בודקים התנהגות בזמן דגרדציה, ולא את מספר הבקשות המקסימלי.
  6. קבלה. הקריטריונים סוכמו מראש: שיעור פעולות מאושרות, זמן תגובה בשיא, נכונות החזרה לאחור.
  7. מסירה ותחזוקה. מאגר קוד, תכנית פריסה, נוהל עדכונים, הרשאות — ומכאן והלאה נשארים בקשר לאירועים.

ערכת מסירה: תכניות, תצורות, נהלים

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

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

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

מקרה בוחן: הסרנו עסקאות תקועות וחוסר סנכרון במצב

לקוח הגיע עם בעיה טיפוסית ל-dApp: השירות שלח עסקאות והמתין לאישור באותו תהליך שבו טופלו בקשות המשתמשים, והרשת הייתה Ethereum.

בארגון מחדש של השרשרת ובניסיונות חוזרים מספרי הסדר של הפעולות התפצלו, השליחה שוכפלה, וחלק מהעסקאות נשארו תלויות — עד 7% מהפעולות דרשו בירור ידני, והצוות בזבז ימים על השוואה במקום לפתח את המוצר.

עבדנו מחדש את הבקאנד ב-Node.js בשלושה שכבות: קליטת בקשות, תור שליחה וצופה נפרד לאישורים. לכל פעולה הוקצה מפתח שלפיו חזרה לא יוצרת עסקה שנייה, ומצב השירות נבנה כעת מאירועי הרשת בעת ההפעלה — המיקום האחרון שעובד נשמר במאגר, ולכן אחרי נפילה השחזור מתבצע אוטומטית, בלי השוואה ידנית.

הבא — בדיקות עומס: הזרמנו поток פעולות גבוה פי כמה משיא, עם ניתוקים והשהיות של הצומת, ובדקנו איך מתנהגים התור, מנגנון החזרות ולוגיקת האישורים תחת לחץ.

מדד לפני העיבוד מחדש אחרי
שליחות תקועות 6–8% מהפעולות 0.2%
כפילויות עסקאות באופן קבוע, ידני 0
שחזור אחרי נפילה השוואה ידנית, עשרות דקות אוטומטית, פחות מדקה
תגובת שירות, אחוזון 95 שניות פי 6 מהיר יותר

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

מה לעשות אם הבקאנד כבר נפרס אצל קבלן?

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

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

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

מעבר מדורג מתחיל במעגל מקביל במצב מראה: הוא קורא את אותם אירועים ומחשב את אותן יתרות, אבל לא שולח עסקאות. את התוצאות משווים לפתרון הפעיל לפי סכומי ביקורת; כשהפערים אפס — מעבירים את הכתיבה לחתם חדש, והישן משאירים כנקודת חזרה. החלפת קבלן עוברת בלי חלון השבתה.

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

תארו מה כבר נפרס ואיפה כואב — נחזור עם הערכה לפי שלבים ותכנית מעבר.

ספקות טכניים נפוצים לפני תחילת העבודה

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

היכן נשמרים מפתחות החתימה?

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

מה יקרה אם צומת הרשת לא זמין?

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

איך נמנע עיבוד חוזר של עסקה?

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

איך בנויה השלביות של העבודה ובדיקת הנכונות?

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

נדון במשימה: מה לשלוח להערכה מדויקת

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

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

אם חלק מנתוני הפתיחה עדיין אין, זה לא חוסם את ההתחלה: חצי מבררים בשיחה, ואת השאר מתעדים בתכנית העבודה כשאלות פתוחות. תארו את המשימה — נדון בה ונחזור עם הערכת לוחות זמנים, פילוח לפי שלבים והרכב צוות לנפח הספציפי.