הרחבת תשתית בלוקצ'יין: מצומת ל-WebSocket
תשתית שעבדה היטב עם 100 משתמשים מתחילה להתפורר ב-10,000. מחסנית הבלוקצ'יין היא ספציפית: צוואר הבקבוק לרוב אינו במקום שבו מצפים – לא במסד הנתונים, לא במעבד – אלא בצומת RPC שלא יכול לעמוד בקצב של eth_getLogs, או באינדקסר שמפגר ב-50 בלוקים, או במטפל WebSocket שמפיל חיבורים תחת עומס. הרחבת תשתית בלוקצ'יין היא דיסציפלינה נפרדת עם דפוסים לא קונבנציונליים. אנחנו עושים זאת כבר למעלה מ-5 שנים ומבטיחים שהתשתית שלכם תוכל לעמוד בכל עומס – צרו קשר, נבחן את הפרויקט שלכם ללא עלות.
למה הרחבת תשתית בלוקצ'יין היא דיסציפלינה נפרדת
בניגוד ל-web קלאסי, ישנן נקודות כשל שלא ניתן לתקן על ידי הוספת שרתים פשוטה: מצב השרשרת, מגבלות WebSocket, נתונים בלתי ניתנים לשינוי בצמתים. כל שכבה דורשת גישה משלה – ממאגר צמתים ועד אינדוקס מונחה אירועים.
אבחון: היכן צוואר הבקבוק האמיתי
לפני שמרחיבים כל דבר – מדדו. צווארי בקבוק טיפוסיים: זמן השהיה לכל שיטת RPC, עומק תור האינדקסר, פער בין ראש הצומת לראש מסד הנתונים, תפוקת חיבורי WebSocket. אנו משתמשים ב-Prometheus ובדשבורדים מוכנים לאיסוף מדדים – כלול בהיקף העבודה.
// Инструментация RPC вызовов class InstrumentedProvider { private metrics: Map<string, number[]> = new Map(); async call(method: string, params: any[]): Promise<any> { const start = performance.now(); try { const result = await this.provider.send(method, params); this.record(method, performance.now() - start); return result; } catch (err) { this.recordError(method); throw err; } } getPercentiles(method: string) { const samples = (this.metrics.get(method) || []).sort((a, b) => a - b); return { p50: samples[Math.floor(samples.length * 0.5)], p95: samples[Math.floor(samples.length * 0.95)], p99: samples[Math.floor(samples.length * 0.99)], count: samples.length, }; } } איך להרחיב את שכבת RPC
מאגר צמתים עם איזון עומסים
צומת יחיד הוא נקודת כשל יחידה וצוואר בקבוק. תצורת ייצור מינימלית: שלושה צמתים עם בדיקות בריאות ו-round-robin שמדלג על צמתים לא בריאים. לפעולות stateful (מנויים, עסקאות ממתינות) – ניתוב דביק (sticky routing). קוד הדוגמה למטה – אנו משתמשים בו בכל פרויקט.
class NodePool { private nodes: RpcNode[]; private currentIndex = 0; private healthStatus: Map<string, boolean> = new Map(); async sendRequest(method: string, params: any[]): Promise<any> { for (let i = 0; i < this.nodes.length; i++) { const node = this.nodes[this.currentIndex % this.nodes.length]; this.currentIndex++; if (!this.healthStatus.get(node.url)) continue; try { return await node.send(method, params); } catch (err) { this.healthStatus.set(node.url, false); setTimeout(() => this.healthStatus.set(node.url, true), 30_000); } } throw new Error('All nodes unhealthy'); } } שמירת תגובות RPC במטמון
בקשות רבות זהות וניתנות לשמירה במטמון: // Инструментация RPC вызовов class InstrumentedProvider { private metrics: Map<string, number[]> = new Map(); async call(method: string, params: any[]): Promise<any> { const start = performance.now(); try { const result = await this.provider.send(method, params); this.record(method, performance.now() - start); return result; } catch (err) { this.recordError(method); throw err; } } getPercentiles(method: string) { const samples = (this.metrics.get(method) || []).sort((a, b) => a - b); return { p50: samples[Math.floor(samples.length * 0.5)], p95: samples[Math.floor(samples.length * 0.95)], p99: samples[Math.floor(samples.length * 0.99)], count: samples.length, }; } } (24 שעות), class NodePool { private nodes: RpcNode[]; private currentIndex = 0; private healthStatus: Map<string, boolean> = new Map(); async sendRequest(method: string, params: any[]): Promise<any> { for (let i = 0; i < this.nodes.length; i++) { const node = this.nodes[this.currentIndex % this.nodes.length]; this.currentIndex++; if (!this.healthStatus.get(node.url)) continue; try { return await node.send(method, params); } catch (err) { this.healthStatus.set(node.url, false); setTimeout(() => this.healthStatus.set(node.url, true), 30_000); } } throw new Error('All nodes unhealthy'); } } (שעה – קוד החוזה לא משתנה), eth_chainId עבור גרסאות שאינן האחרונות (דקה), eth_getCode (5 דקות לאחר סופיות). לעולם אל תשמרו במטמון eth_getBlockByNumber או eth_getTransactionReceipt. השתמשו ב-Redis – זה מפחית בקשות לצומת ב-80%.
const CACHEABLE_METHODS: Record<string, number> = { 'eth_chainId': 86400, 'eth_getCode': 3600, 'eth_getBlockByNumber': 60, 'eth_getTransactionReceipt': 300, }; class CachingRpcProxy { async send(method: string, params: any[]): Promise<any> { const ttl = CACHEABLE_METHODS[method]; if (!ttl) return this.upstream.send(method, params); if (params.includes('latest') || params.includes('pending')) { return this.upstream.send(method, params); } const cacheKey = `rpc:${method}:${JSON.stringify(params)}`; const cached = await this.redis.get(cacheKey); if (cached) return JSON.parse(cached); const result = await this.upstream.send(method, params); await this.redis.setex(cacheKey, ttl, JSON.stringify(result)); return result; } } אינדוקס: מסקירה תקופתית למונחה אירועים
בעיית הסקירה התקופתית
סקירה כל 5 שניות עבור 10,000 כתובות – 2,000 בקשות בשנייה. הצומת מוצף. עברו למודל מונחה אירועים באמצעות יומני EVM: latest אחד עבור טווח בלוקים מחליף אלפי בקשות בודדות. חיסכון – עד 90% מעומס RPC.
The Graph לאינדוקס מורכב
לצבירות לפי משתמש, נתונים היסטוריים – The Graph subgraph. Graph Node באירוח עצמי על PostgreSQL 14+ עם קלט/פלט מספק. כלול בהיקף עבודה טיפוסי: הגדרת subgraph, פריסה, ניטור.
WebSocket: הרחבת מנויים
WebSocket הוא stateful – round-robin של nginx לא עובד. השתמשו ב-Redis pub/sub: שירות נפרד מפרסם אירועים (בלוק חדש, עסקה) ל-Redis, ושרתי WS נרשמים ומפיצים ללקוחותיהם. הוספת שרת WS נוסף – הדבר היחיד הנדרש להרחבה אופקית.
ניהול עומס הצומת
מיזוג בקשות – אם 100 בקשות מבקשות בו-זמנית את אותו משאב, שלבו אותן לקריאת RPC אחת. Multicall – בקשת HTTP אחת במקום 100 עבור pending. שני הדפוסים מפחיתים את עומס הצומת עשרות מונים.
| בעיה | פתרון | מורכבות | חיסכון בעומס RPC |
|---|---|---|---|
| צוואר בקבוק בצומת RPC | מאגר צמתים + איזון | נמוכה | 50%+ |
| בקשות זהות חוזרות | מיזוג בקשות + מטמון Redis | נמוכה | 80%+ |
| ניטור יתרות של 100+ כתובות | Multicall + אינדוקס אירועים | בינונית | 90%+ |
| WS מפיל חיבורים תחת עומס | תשתית Redis pub/sub | בינונית | — |
| שאילתות היסטוריות איטיות | ארכיון Erigon/Reth + אופטימיזציית שאילתות | בינונית | 70%+ |
| אנליטיקה מורכבת של נתוני on-chain | The Graph subgraph | גבוהה | — |
מה כלול בעבודה שלנו
- ביקורת של הארכיטקטורה הנוכחית עם מדידת זמן השהיה, תפוקה ופיגור;
- תכנון פתרון למחסנית שלכם (Ethereum, Polygon, Arbitrum, Solana);
- יישום: מאגר צמתים, מטמון, אינדוקס מונחה אירועים, שער WebSocket;
- שילוב ניטור (Grafana, Prometheus, התראות);
- תיעוד והעברת גישה;
- הכשרת צוות על התשתית החדשה;
- חודש תמיכה לאחר ההשקה.
תהליך
- אנליטיקה – פרופיל התשתית הנוכחית, זיהוי צווארי בקבוק.
- עיצוב – בחירת דפוסים, הכנת סכמה.
- יישום – כתיבת קוד, הגדרת שירותים.
- בדיקות – מבחני עומס על התרחישים שלכם.
- פריסה וניטור – השקה עם תצפית בימים הראשונים.
לוחות זמנים משוערים
מ-2 עד 6 שבועות תלוי במורכבות ובמספר הרשתות. העלות מחושבת באופן אישי לאחר הביקורת – צרו קשר, נעריך תוך 1-2 ימים.
אנו עובדים עם Ethereum, Polygon, Arbitrum, Optimism, BNB Chain, Solana. ניסיון – 10+ פרויקטים, למעלה מ-5 שנים בשוק. איכות מובטחת: כל הפתרונות עוברים בדיקה וסקירה.







