לעתים קרובות אנו נתקלים במצבים שבהם הפרונטאנד מבצע עשרות קריאות eth_call ו-getLogs בכל טעינת עמוד. ברשת המרכזית (mainnet) זה לוקח 2–3 שניות, דרך RPC ציבורי זה לא אמין, וכאשר יש צורך בנתונים מצטברים או היסטוריים, קריאות ישירות הופכות לבלתי אפשריות. The Graph פותר בעיה זו בצורה נכונה: כותבים subgraph פעם אחת, שמתעדכן על פי אירועי החוזה, ומקבלים API מסוג GraphQL עם שאילתות שרירותיות במילישניות. חיסכון בעלויות תשתית יכול להגיע ל-90% (עד $2000 לחודש עבור פרויקט DeFi טיפוסי), ומהירות השאילתות גדלה פי עשרה. להלן נפרק כיצד לתכנן ולכתוב subgraph כדי להימנע מטעויות נפוצות ולהשיג ביצועים מקסימליים.
כיצד לתכנן סכמת subgraph?
subgraph מורכב משלושה רכיבים, וחשוב לתכנן את הסכמה על פי שאילתות הפרונטאנד, לא על פי מבנה האירועים.
-
subgraph.yaml— מניפסט. מתאר מקורות נתונים: לאילו חוזים להאזין, בלוק התחלתי (startBlock), אילו אירועים ופונקציות לטפל בהם. קריטי:startBlockחייב להיות בלוק פריסת החוזה, לא אפס — אחרת האינדוקס ייקח ימים. -
schema.graphql— סוגי ישויות. זה מה שיהיה זמין דרך GraphQL. תכנן אותו על פי צרכי הפרונטאנד, לא על פי מבנה אירועי החוזה — אלה דברים שונים. -
mappings.ts— מטפלים ב-AssemblyScript. הופכים אירועים גולמיים לישויות סכמה.
עיצוב סכמה
הטעות הנפוצה ביותר היא להפוך את הסכמה לשיקוף של אירועי החוזה. אם האירוע הוא Transfer(address from, address to, uint256 amount), אין צורך בישות TransferEvent. במקום זאת, חשוב על שאילתות: "מה היתרה הנוכחית של משתמש", "המחזיקים המובילים", "נפח ב-24 השעות האחרונות".
type Token @entity {
id: ID!
totalSupply: BigInt!
holderCount: Int!
}
type Account @entity {
id: Bytes!
balance: BigInt!
transfersIn: [Transfer!]! @derivedFrom(field: "to")
transfersOut: [Transfer!]! @derivedFrom(field: "from")
}
type Transfer @entity(immutable: true) {
id: Bytes!
from: Account!
to: Account!
amount: BigInt!
blockNumber: BigInt!
timestamp: BigInt!
}type Token @entity { id: ID! totalSupply: BigInt! holderCount: Int! } type Account @entity { id: Bytes! balance: BigInt! transfersIn: [Transfer!]! @derivedFrom(field: "to") transfersOut: [Transfer!]! @derivedFrom(field: "from") } type Transfer @entity(immutable: true) { id: Bytes! from: Account! to: Account! amount: BigInt! blockNumber: BigInt! timestamp: BigInt! } עבור Transfer הוא אופטימיזציה חשובה. ישויות בלתי ניתנות לשינוי אינן מאוחסנות במאגר הביטול (undo buffer), מה שהופך את האינדוקס למהיר ב-30–40%.
מטפלים: שלב אחר שלב
AssemblyScript אינו TypeScript מלא — אין @entity(immutable: true) דרך null, אין ?., אין שיטות JS סטנדרטיות. זהו מקור תדיר לשגיאות עבור מפתחים המגיעים מפרונטאנד.
- זהה את האירוע שאתה מטפל בו (לדוגמה,
Array.from()). - טען או צור ישות באמצעות
TransferאוAccount.load(address). - עדכן שדות (יתרה, הפניות).
- שמור שינויים דרך
new Account(address).
// Правильная загрузка или создание сущности
function getOrCreateAccount(address: Address): Account {
let account = Account.load(address)
if (account == null) {
account = new Account(address)
account.balance = BigInt.fromI32(0)
}
return account as Account
}
export function handleTransfer(event: TransferEvent): void {
let from = getOrCreateAccount(event.params.from)
let to = getOrCreateAccount(event.params.to)
from.balance = from.balance.minus(event.params.value)
to.balance = to.balance.plus(event.params.value)
from.save()
to.save()
// Immutable — создаём один раз, не загружаем
let transfer = new Transfer(
event.transaction.hash.concatI32(event.logIndex.toI32())
)
transfer.from = from.id
transfer.to = to.id
transfer.amount = event.params.value
transfer.blockNumber = event.block.number
transfer.timestamp = event.block.timestamp
transfer.save()
} מטפלי קריאות ומטפלי בלוקים
בנוסף לאירועים, The Graph יכול לטפל בקריאות פונקציות (save()) ובכל בלוק (// Правильная загрузка или создание сущности function getOrCreateAccount(address: Address): Account { let account = Account.load(address) if (account == null) { account = new Account(address) account.balance = BigInt.fromI32(0) } return account as Account } export function handleTransfer(event: TransferEvent): void { let from = getOrCreateAccount(event.params.from) let to = getOrCreateAccount(event.params.to) from.balance = from.balance.minus(event.params.value) to.balance = to.balance.plus(event.params.value) from.save() to.save() // Immutable — создаём один раз, не загружаем let transfer = new Transfer( event.transaction.hash.concatI32(event.logIndex.toI32()) ) transfer.from = from.id transfer.to = to.id transfer.amount = event.params.value transfer.blockNumber = event.block.number transfer.timestamp = event.block.timestamp transfer.save() } ). מטפלי קריאות נחוצים כאשר החוזה אינו פולט אירועים עבור פעולות נדרשות — חוזים ישנים לעתים קרובות חסרי אירועים. מטפלי בלוקים משמשים לצילומי מצב תקופתיים (לדוגמה, סטטיסטיקות יומיות). שניהם מאטים משמעותית את האינדוקס, במיוחד מטפלי בלוקים — השתמש בהם רק בעת הצורך.
| סוג מטפל | מטרה | השפעה על מהירות האינדוקס |
|---|---|---|
| מטפל אירועים | עיבוד אירועי חוזה | מינימלית (סוג עיקרי) |
| מטפל קריאות | מעקב אחר קריאות פונקציות | בינונית (דורש צומת ארכיון) |
| מטפל בלוקים | עיבוד בלוקים תקופתי | גבוהה (רץ על כל בלוק) |
מדוע The Graph מהיר יותר מקריאות RPC ישירות?
קריאות RPC ישירות מתבצעות ברצף ומעמיסות על הצומת. The Graph מבצע אינדוקס לנתונים פעם אחת ומאחסן אותם במסד נתונים מותאם הנגיש דרך GraphQL. שאילתות מתבצעות במילישניות (זמן טיפוסי של 50 עד 200 אלפיות השנייה), ועמודים דרך callHandlers/blockHandlers עובדים עד first — עבור מערכי נתונים גדולים יותר השתמש בעמודים מבוססי מפתח עם skip. על פי תיעוד The Graph, תפוקת האינדוקס יכולה להגיע ל-1000 אירועים בשנייה על חומרה סטנדרטית. חיסכון בעלויות תשתית יכול להגיע ל-90%, כפי שאושר על ידי ניסיון של פרויקטי DeFi גדולים — לדוגמה, Uniswap הפחית את עלויות ה-RPC ביותר מ-$3000 לחודש לאחר המעבר ל-subgraph.
איזה אירוח לבחור: Hosted Service, רשת מבוזרת, או אירוח עצמי?
הבחירה באפשרות פריסת ה-subgraph תלויה בדרישות לזמינות, שליטה ותקציב.
| אפשרות | זמינות | שליטה | עלות |
|---|---|---|---|
| Hosted Service | בסיסית (ללא SLA) | מוגבלת | חינם לפרויקטים קטנים |
| רשת מבוזרת | גבוהה (אינדוקסרים מבוזרים) | בינונית | דורש GRT (טוקן) |
| צומת Graph Node באירוח עצמי | מלאה (תשתית עצמית) | מלאה | עלויות תשתית ($1000+/חודש) |
הצוות שלנו, עם ניסיון של עשר שנים בבלוקצ'יין, יישם למעלה מ-20 אינטגרציות עם The Graph. אנו עוזרים לך לבחור את האפשרות האופטימלית לפרויקט שלך ולהימנע מבעיות נפוצות.
בעיות אינדוקס טיפוסיות
- ה-subgraph נכשל עם "store error" — בדוק שדות שאינם ניתנים ל-null.
- האינדוקס נתקע על בלוק — הוסף טיפול בעסקאות שבוטלו דרך
skip: 5000. - אי-התאמה בנתונים עקב reorgs — הגדר
id_gt. - צריכת גז מופרזת בעת כתיבה — השתמש ב-
receipt.statusעבור נתונים בלתי ניתנים לשינוי.
מה לדעת על עמודים וסינון?
| סוג שאילתה | דוגמה | מגבלה |
|---|---|---|
| עמודים | minEthereumBlockConfirmations |
@entity(immutable: true) עד 5000 |
| עמודים מבוססי מפתח | first: 100, skip: 0 |
ללא מגבלה |
| סינון | skip |
כל האופרטורים נתמכים |
| מיון | where: { id_gt: "..." } |
כל שדה ישות |
עמודים מבוססי מפתח מועדפים עבור מערכי נתונים גדולים — הם מהירים יותר ואין להם מגבלת where: { balance_gt: "0" }.
אינטגרציה עם פרונטאנד
מחסנית טיפוסית: Apollo Client או urql עבור יישומי React. The Graph תומך במנויים (subscriptions) דרך WebSocket לעדכונים בזמן אמת ללא סקרים.
const POSITIONS_QUERY = gql`
query UserPositions($account: Bytes!, $skip: Int!) {
positions(
where: { owner: $account, liquidity_gt: "0" }
orderBy: createdAt
orderDirection: desc
first: 100
skip: $skip
) {
id
pool {
token0 {
symbol
}
token1 {
symbol
}
feeTier
}
liquidity
depositedToken0
depositedToken1
}
}
`
לוחות זמנים ומה כלול
ב-2–5 ימים: עיצוב סכמה המותאם לצרכי הלקוח, כתיבת מיפויים לכל האירועים והקריאות, בדיקות על fork, פריסה ל-Hosted Service או הקמת צומת באירוח עצמי, אינטגרציה בסיסית לפרונטאנד קיים או מתן נקודת קצה GraphQL. העלות מחושבת באופן אישי, אך החיסכון בקריאות RPC מצדיק את ההשקעה — החזר ההשקעה הטיפוסי מתרחש תוך 1–3 חודשים.
צור קשר כדי לדון באינטגרציה של The Graph בפרויקט שלך. ניסיון עם The Graph ועשרות אינטגרציות מוצלחות מבטיחים תוצאות. בקש ייעוץ — נכין הצעה מותאמת אישית.







