הגדרת DynamoDB לאפליקציות ווב: עיצוב טבלה יחידה, CDK, Streams

עומסים גבוהים על יישומי web גורמים להאטות במסד הנתונים ולעלויות תשתית עולות. אנחנו מתכננים DynamoDB turnkey: מסכמות single-table ועד לפריסה באמצעות CDK, עם הגדרת Streams וניטור. הצוות שלנו מטפל בכל המחזור, ומבטיח פעילות יציבה ותמיכה מתמשכת.

פיתוח ותחזוקה של כל סוגי האתרים:

אתרי מידע או יישומי אינטרנט
אתרי תדמית, דפי נחיתה, אתרי חברה, קטלוגים מקוונים, חידונים, אתרי קידום, בלוגים, מקורות חדשות, פורטלי מידע, פורומים, אגרגטורים
אתרי מסחר אלקטרוני או יישומי אינטרנט
חנויות מקוונות, פורטלי B2B, שווקים, בורסות מקוונות, אתרי קאשבק, בורסות, פלטפורמות דרופשיפינג, מנתחי מוצרים
יישומי אינטרנט לניהול תהליכים עסקיים
מערכות CRM, מערכות ERP, פורטלים ארגוניים, מערכות ניהול ייצור, מנתחי מידע
אתרי שירות אלקטרוני או יישומי אינטרנט
פלטפורמות מודעות, בתי ספר מקוונים, בתי קולנוע מקוונים, בוני אתרים, פורטלים לשירותים אלקטרוניים, פלטפורמות אירוח וידאו, פורטלים נושאיים

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

השירותים שאנו מציעים
מציג 1 מתוך 1כל 2062 השירותים
הגדרת DynamoDB לאפליקציות ווב: עיצוב טבלה יחידה, CDK, Streams
מורכב
~2-3 ימים

הכישורים שלנו:

שאלות נפוצות

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

  • פיתוח אפליקציית ווב עבור FEEDME
    פיתוח אפליקציית ווב עבור FEEDME
    1359
  • פיתוח חנות מקוונת לחברת FURNORO
    פיתוח חנות מקוונת לחברת FURNORO
    1330
  • פיתוח אפליקציית ווב עבור Enviok
    פיתוח אפליקציית ווב עבור Enviok
    1077
  • פיתוח CRM עבור Chasseurs
    פיתוח CRM עבור Chasseurs
    1113
  • פיתוח אתר עבור SBH Partners
    פיתוח אתר עבור SBH Partners
    1201
  • פיתוח אתר עבור Red Pear
    פיתוח אתר עבור Red Pear
    619

ב-5000 בקשות בשנייה, PostgreSQL מתחיל לפגר: שאילתות N+1, נעילות, פיגור רפליקה. מעבר ל-DynamoDB פותר את זה — זמן האחזור יורד מ-50ms ל-5ms, שיפור של פי 10, ועלויות התשתית יורדות ב-40% (חיסכון של $1,200 בחודש במקרה של לקוח אחד) על ידי ביטול רפליקות קריאה. אבל הגדרה לא נכונה של DynamoDB מובילה למחיצות חמות ולהגבלת קצב (throttling). אנו מגדירים DynamoDB מקצה לקצה: מעיצוב סכמת טבלה יחידה ועד לפריסת תשתית באמצעות AWS CDK. הניסיון שלנו: 5+ שנים על AWS, מעל 50 פרויקטים עם יישומי אינטרנט בעומס גבוה. אנו פועלים לפי השיטות המומלצות של AWS Well-Architected Framework. עלות הגדרת DynamoDB טיפוסית נעה בין $2,500 ל-$5,000 בהתאם למורכבות. קבלו הערכת פרויקט להגדרת DynamoDB שלכם במפתח אחד.

DynamoDB הוא מסד נתונים NoSQL מנוהל מ-AWS עם זמן אחזור מובטח <10ms בכל קנה מידה ו-SLA זמינות של 99.999%. אין שרתים לתחזוק, אין צורך ב-sharding ידני. אידיאלי לארכיטקטורות serverless ויישומים עם עומסי שיא. על פי סטטיסטיקות, 90% מהיישומים עם עומסים >1000 בקשות/שנייה מרוויחים ממעבר ל-DynamoDB. טכניקות האופטימיזציה שלנו ל-DynamoDB מפחיתות את מספר מדיניות ה-IAM ב-60% ואת זמן האחזור של שאילתות מתחת ל-10ms עבור 99% מהבקשות.

אילו בעיות DynamoDB פותר ביישומי אינטרנט?

בעיות טיפוסיות עם מסדי נתונים רלציוניים תחת עומס גבוה: שאילתות N+1, JOIN מורכבים, פיגור רפליקה. DynamoDB מבטל את אלה באמצעות denormalization ועיצוב טבלה יחידה. עם זאת, עיצוב לקוי מוביל למחיצות חמות, דלדול RCU/WCU ועלויות גבוהות. אנו פותרים בעיות אלה בשלב העיצוב. לדוגמה, בפרויקט אחד העברנו מ-PostgreSQL ל-DynamoDB, הפחתנו את זמן האחזור מ-50ms ל-5ms (פי 10 מהר יותר) וקיצצנו את עלויות התשתית ב-40% על ידי הסרת רפליקות קריאה. זה אפשר טיפול בעומסי שיא של עד 10,000 בקשות/שנייה ללא throttling. עיצוב טבלה יחידה קל פי 3 לניהול מאשר טבלאות מרובות עם JOIN מורכבים.

מדוע עיצוב טבלה יחידה הוא הסטנדרט עבור DynamoDB?

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

Сущность: User
PK: USER#<userId>
SK: METADATA
GSI1PK: EMAIL#<email>
GSI1SK: USER#<userId>
Сущность: Order
PK: USER#<userId>
SK: ORDER#<orderId>
GSI1PK: ORDER#<orderId>
GSI1SK: USER#<userId>
Сущность: OrderItem
PK: ORDER#<orderId>
SK: ITEM#<itemId>
Сущность: Product
PK: PRODUCT#<productId>
SK: METADATA
GSI1PK: CATEGORY#<cat>
GSI1SK: PRODUCT#<productId>

גישה זו מאפשרת לכל השאילתות לפגוע בטבלה אחת באמצעות GSI. גישת טבלאות מרובות מגדילה את מספר הטבלאות, מסבכת את מדיניות IAM DynamoDB ומגדילה את זמן האחזור. LSI (אינדקסים משניים מקומיים) שימושיים למפתחות מיון חלופיים בתוך אותו PK.

לפי AWS Well-Architected Framework, שימוש בעיצוב טבלה יחידה ו-GSI הוא שיטה מומלצת עבור DynamoDB.

השוואה בין מצבי On-Demand ו-Provisioned

מאפיין On-Demand Provisioned
עלות כתיבה יקר פי ~1.5–2 ($0.75 ל-WCU לעומת $0.50) זול יותר
גמישות קנה מידה אוטומטי הגדרת קנה מידה אוטומטי ידנית
עומסי שיא ללא הגבלות סיכון ל-throttling
המלצה עומסים לא צפויים עומסים יציבים

On-Demand עדיף לעומסים משתנים אך יקר פי 1.5–2 בעומסים יציבים.

תשתית באמצעות AWS CDK (TypeScript)

import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'
import { RemovalPolicy } from 'aws-cdk-lib'

const table = new dynamodb.Table(this, 'AppTable', {
  tableName: 'MyApp',
  partitionKey: { name: 'PK', type: dynamodb.AttributeType.STRING },
  sortKey: { name: 'SK', type: dynamodb.AttributeType.STRING },
  billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
  // или PROVISIONED + auto scaling
  pointInTimeRecovery: true,
  deletionProtection: true,
  removalPolicy: RemovalPolicy.RETAIN,
  stream: dynamodb.StreamViewType.NEW_AND_OLD_IMAGES,
})

table.addGlobalSecondaryIndex({
  indexName: 'GSI1',
  partitionKey: { name: 'GSI1PK', type: dynamodb.AttributeType.STRING },
  sortKey: { name: 'GSI1SK', type: dynamodb.AttributeType.STRING },
  projectionType: dynamodb.ProjectionType.ALL,
})

יישום מאגרים עם AWS SDK v3 DynamoDB

export class UserRepository {
  async create(user: CreateUserInput): Promise<User> {
    const id = crypto.randomUUID()
    const now = new Date().toISOString()
    await db.send(new TransactWriteCommand({
      TransactItems: [
        {
          Put: {
            TableName: TABLE,
            Item: {
              PK: `USER#${id}`,
              SK: 'METADATA',
              GSI1PK: `EMAIL#${user.email}`,
              GSI1SK: `USER#${id}`,
              id,
              email: user.email,
              name: user.name,
              passwordHash: user.passwordHash,
              role: 'user',
              createdAt: now,
              updatedAt: now,
              _type: 'User'
            },
            ConditionExpression: 'attribute_not_exists(PK)'
          }
        }
      ]
    }))
    return { id, ...user, role: 'user', createdAt: now, updatedAt: now }
  }

  async findByEmail(email: string): Promise<User | null> {
    const result = await db.send(new QueryCommand({
      TableName: TABLE,
      IndexName: 'GSI1',
      KeyConditionExpression: 'GSI1PK = :pk',
      ExpressionAttributeValues: {
        ':pk': `EMAIL#${email}`
      },
      Limit: 1
    }))
    return (result.Items?.[0] as User) ?? null
  }

  async findById(id: string): Promise<User | null> {
    const result = await db.send(new GetCommand({
      TableName: TABLE,
      Key: {
        PK: `USER#${id}`,
        SK: 'METADATA'
      }
    }))
    return (result.Item as User) ?? null
  }
}

כיצד להגדיר DynamoDB Streams ו-Lambda Triggers?

Streams מאפשרים תגובה לשינויי נתונים בזמן אמת — פי 10 מהר יותר מסקר (polling) לשינויי מסד נתונים. תרחיש טיפוסי הוא עדכון אינדקס חיפוש או שליחת התראות. דוגמה ל-Lambda handler:

export const handler = async (event: DynamoDBStreamEvent) => {
  for (const record of event.Records) {
    if (record.eventName !== 'MODIFY') continue
    const newImage = unmarshall(record.dynamodb!.NewImage!)
    const oldImage = unmarshall(record.dynamodb!.OldImage!)
    if (newImage._type === 'Order' && newImage.status !== oldImage.status) {
      await notifyOrderStatusChange(newImage.id, newImage.status)
    }
  }
}

כיצד לנטר DynamoDB ולהגדיר התראות?

מדדים מרכזיים: Сущность: User PK: USER#<userId> SK: METADATA GSI1PK: EMAIL#<email> GSI1SK: USER#<userId> Сущность: Order PK: USER#<userId> SK: ORDER#<orderId> GSI1PK: ORDER#<orderId> GSI1SK: USER#<userId> Сущность: OrderItem PK: ORDER#<orderId> SK: ITEM#<itemId> Сущность: Product PK: PRODUCT#<productId> SK: METADATA GSI1PK: CATEGORY#<cat> GSI1SK: PRODUCT#<productId> , import * as dynamodb from 'aws-cdk-lib/aws-dynamodb' import { RemovalPolicy } from 'aws-cdk-lib' const table = new dynamodb.Table(this, 'AppTable', { tableName: 'MyApp', partitionKey: { name: 'PK', type: dynamodb.AttributeType.STRING }, sortKey: { name: 'SK', type: dynamodb.AttributeType.STRING }, billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, // или PROVISIONED + auto scaling pointInTimeRecovery: true, deletionProtection: true, removalPolicy: RemovalPolicy.RETAIN, stream: dynamodb.StreamViewType.NEW_AND_OLD_IMAGES, }) table.addGlobalSecondaryIndex({ indexName: 'GSI1', partitionKey: { name: 'GSI1PK', type: dynamodb.AttributeType.STRING }, sortKey: { name: 'GSI1SK', type: dynamodb.AttributeType.STRING }, projectionType: dynamodb.ProjectionType.ALL, }) , export class UserRepository { async create(user: CreateUserInput): Promise<User> { const id = crypto.randomUUID() const now = new Date().toISOString() await db.send(new TransactWriteCommand({ TransactItems: [{ Put: { TableName: TABLE, Item: { PK: `USER#${id}`, SK: 'METADATA', GSI1PK: `EMAIL#${user.email}`, GSI1SK: `USER#${id}`, id, email: user.email, name: user.name, passwordHash: user.passwordHash, role: 'user', createdAt: now, updatedAt: now, _type: 'User' }, ConditionExpression: 'attribute_not_exists(PK)' } }] })) return { id, ...user, role: 'user', createdAt: now, updatedAt: now } } async findByEmail(email: string): Promise<User | null> { const result = await db.send(new QueryCommand({ TableName: TABLE, IndexName: 'GSI1', KeyConditionExpression: 'GSI1PK = :pk', ExpressionAttributeValues: { ':pk': `EMAIL#${email}` }, Limit: 1 })) return (result.Items?.[0] as User) ?? null } async findById(id: string): Promise<User | null> { const result = await db.send(new GetCommand({ TableName: TABLE, Key: { PK: `USER#${id}`, SK: 'METADATA' } })) return (result.Item as User) ?? null } } , export const handler = async (event: DynamoDBStreamEvent) => { for (const record of event.Records) { if (record.eventName !== 'MODIFY') continue const newImage = unmarshall(record.dynamodb!.NewImage!) const oldImage = unmarshall(record.dynamodb!.OldImage!) if (newImage._type === 'Order' && newImage.status !== oldImage.status) { await notifyOrderStatusChange(newImage.id, newImage.status) } } } , ConsumedReadCapacityUnits. הגדר התראה מיידית על ConsumedWriteCapacityUnits. השתמש בלוחות מחוונים של CloudWatch לניטור DynamoDB. אנו ממליצים להגדיר התראות על חריגה מ-80% מהקיבולת המוקצית.

תהליך הגדרת DynamoDB

  1. ניתוח דפוסי גישה של היישום
  2. עיצוב סכמת טבלה יחידה
  3. יצירת תשתית באמצעות CDK
  4. יישום מאגרים עם AWS SDK v3 DynamoDB
  5. הגדרת Streams ו-Lambda triggers
  6. הגדרת ניטור והתראות

תוצרים

רכיב תיאור
עיצוב סכמה הגדרת PK/SK, GSI, LSI
תשתית מחסנית CDK עם טבלה, Streams, IAM
קוד מאגר TypeScript/Node.js עם AWS SDK v3
אינטגרציה Lambda triggers, API Gateway
ניטור לוח מחוונים CloudWatch, התראות
תיעוד תיאור סכמה, דפוסי גישה, הוראות
גישה גישה לקונסולת AWS ולמאגר
מסירה מפגש הדרכה של שעה
תמיכה חודש תמיכה לאחר ההשקה

לוחות זמנים ועלות

עיצוב ואינטגרציה בסיסית: 3–5 ימים (מסירה במפתח אחד). הוספת Streams, Lambda, ניטור: עוד 3–5 ימים. מעבר ממסד נתונים רלציוני: 2–4 שבועות בהתאם לנפח. התמחור מחושב באופן אישי, תלוי במורכבות הסכמה ובהיקף האינטגרציה. צרו קשר להערכת פרויקט — נעריך מורכבות ולוחות זמנים. עלות הגדרת DynamoDB טיפוסית נעה בין $2,500 ל-$5,000. בקשו הערכה מקדימה כדי להבין כמה זמן ומשאבים יידרשו.