אינדוקס בלוקצ'יין עם The Graph: עיצוב ואופטימיזציה של subgraph

האם ה-frontend של ה-dApp שלך מאט בקריאות RPC ישירות, וקשה לאחזר נתונים היסטוריים חלק אחר חלק? אנחנו מתכננים ומבצעים אופטימיזציה של subgraphs עבור The Graph, והופכים אירועי חוזה חכם גולמיים ל-API מהיר של GraphQL. הצוות שלנו מספק פרויקטים סוהריים—מעיצוב הסכמה ועד תמיכה שוטפת—כך שהיישום המבוזר שלך פועל בצורה אמינה וללא עיכובים.

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

שאלות נפוצות

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

  • פיתוח אפליקציית ווב עבור FEEDME
    פיתוח אפליקציית ווב עבור FEEDME
    1365
  • פיתוח חנות מקוונת לחברת FURNORO
    פיתוח חנות מקוונת לחברת FURNORO
    1330
  • עיצוב לוגו לחברת B2B Advance
    עיצוב לוגו לחברת B2B Advance
    776
  • פיתוח אפליקציית ווב עבור Enviok
    פיתוח אפליקציית ווב עבור Enviok
    1077
  • פיתוח לוגו חברת AIDER
    פיתוח לוגו חברת AIDER
    1022
  • פיתוח CRM עבור Chasseurs
    פיתוח CRM עבור Chasseurs
    1113

לעתים קרובות אנו נתקלים במצבים שבהם הפרונטאנד מבצע עשרות קריאות 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 סטנדרטיות. זהו מקור תדיר לשגיאות עבור מפתחים המגיעים מפרונטאנד.

  1. זהה את האירוע שאתה מטפל בו (לדוגמה, Array.from()).
  2. טען או צור ישות באמצעות Transfer או Account.load(address).
  3. עדכן שדות (יתרה, הפניות).
  4. שמור שינויים דרך 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 ועשרות אינטגרציות מוצלחות מבטיחים תוצאות. בקש ייעוץ — נכין הצעה מותאמת אישית.