2 שלב הבסיס

Workers ו-Storage — קיר ה-10ms, ובחירת KV / D1 / R2 / Durable Objects

הלומד יבנה מודל מנטלי מדויק של מגבלת ה-10ms CPU (execution time מול wall-clock), ויידע לבחור את ה-primitive הנכון לכל בעיית אחסון — KV, D1, R2 או Durable Objects — לפי consistency, מחיר ומגבלה, ולחבר אותם כ-bindings

... wait, "execution time vs wall-clock" -> זמן עיבוד בפועל מול זמן המתנה לרשת. Let's refine:

כשאנחנו בונים אפליק

מה יהיה לך ביד בסוף הפרק
מטרות למידה
מה צריך לפני שמתחילים
חוט הפרויקט

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

הפרק הזה: הלומד יבנה מודל מנטלי מדויק של מגבלת ה-10ms CPU (execution time מול wall-clock), ויידע לבחור את ה-primitive הנכון לכל בעיית אחסון — KV, D1, R2 או Durable Objects — לפי consistency, מחיר ומגבלה, ולחבר אותם כ-bindings

הפרק הבא: בפרק הבא נלמד לחבר את ה-Worker שלמדנו לייצר אל מודלי בינה מלאכותית (Workers AI) המופעלים באדג' ונחשב את צריכת ה-Neurons שלנו.

מילון מונחים — פרק 2
מונחהסבר
Isolateסביבת ריצה קלת משקל המבוססת על מנוע V8, המאפשרת להריץ אלפי תהליכים נפרדים בזיכרון משותף ללא הצורך במכולות (Containers) ומבלי לסבול מזמני הדלקה קרה (Cold starts).
Eventual Consistencyמודל עקביות נתונים המבטיח כי כלל העותקים של המידע יתעדכנו ויסתנכרנו בסופו של דבר, אך ללא התחייבות שהעדכון יקרה באופן מיידי בכל נקודות הקצה.
Subrequestקריאת HTTP יוצאת המתבצעת מתוך Worker של Cloudflare אל שירות חיצוני או אל שירותים מובנים אחרים ברשת של Cloudflare.
Rows Readמדד לחישוב השימוש בבסיס הנתונים D1, המייצג את מספר השורות שנסרקו על ידי שאילתת SQL לצורך החזרת התוצאות.
Zero Egressמדיניות תמחור שבה לא נגבים דמי תעבורת רשת יוצאת עבור הורדת קבצים מהאחסון, בניגוד לספקי ענן קלאסיים (כמו AWS S3).
GB-secondsיחידת מידה המחשבת את נפח הזיכרון שהוקצה עבור שירות (בג'יגה-בייט) מוכפל בזמן הריצה שלו (בשניות).
Hyperdriveשירות של Cloudflare המאפשר להאיץ ולהקל על קריאות לבסיסי נתונים חיצוניים (כמו PostgreSQL) על ידי ניהול מאגר חיבורים (Connection Pooling) חכם באדג'.
בינוני5 דקותחינםtheory-deep-dive

מודל ה-Isolate לעומת מכולות: למה קוד אדג' רץ במהירות האור וללא קור-סטארט

כאשר אנחנו בונים יישומים מודרניים בעזרת כלי בינה מלאכותית (AI) כמו קורסור (Cursor) או בולט (Bolt), אחת הבחירות החשובות ביותר שנעשה היא היכן להריץ את הקוד שלנו. רובנו רגילים לעבוד עם שרתי אינטרנט מסורתיים או שירותי ענן שמריצים "מכולות" (Containers — סביבות הרצה וירטואליות קלאסיות כמו Docker). במודל המכולות הישן, בכל פעם שמגיע משתמש חדש אחרי תקופה של חוסר פעילות, המערכת צריכה להעיר מכולה שלמה מהשינה שלה, לטעון את מערכת ההפעלה, להעלות את שפת ההרצה (Runtime) ורק אז להתחיל לעבד את הבקשה. התהליך הזה נקרא "אתחול קר" (Cold Start), והוא עשוי לקחת בין שנייה אחת לחמש שניות תמימות — נצח במונחים של חוויית משתמש מודרנית ואינטגרציות AI מהירות. שירות Cloudflare Workers (סביבת ההרצה ללא שרת של קלאודפלייר — Serverless Compute) פותר את הבעיה הזו לחלוטין על ידי מעבר למודל ארכיטקטוני שונה לחלוטין: מודל ה-Isolate (בידוד). במקום להרים מכונה וירטואלית שלמה עבור כל משתמש או עבור כל חתיכת קוד, קלאודפלייר משתמשת במנוע ה-V8 (מנוע ה-V8 — מנוע הרצת ה-JavaScript המהיר שמניע גם את דפדפן כרום — Google Chrome). מנוע ה-V8 יודע לקחת פיסות קוד קטנות ולהריץ אותן בתוך סביבות זיכרון סגורות ומבודדות שנקראות "Isolates". מכיוון שכל ה-Isolates הללו חולקים את אותו תהליך פיזי במחשב, אין צורך להעלות מערכת הפעלה מחדש. הזמן שלוקח ליצור Isolate חדש ולהתחיל להריץ בו קוד נמדד במיקרו-שניות בודדות — מה שמעלים לחלוטין את תופעת ה"אתחול הקר" (No cold starts) ומאפשר לאפליקציה שלכם להגיב במהירות האור מהמיקום הפיזי הקרוב ביותר למשתמש.

כיצד מנוע V8 מעלים את "האתחול הקר" (Cold Start)

כדי להבין את העוצמה של מודל ה-Isolate, נחשוב על חדר מלון לעומת דירה שלמה. אם בכל פעם שמשתמש היה מגיע היינו צריכים לקנות לו דירה, לרהט אותה ולחבר אותה לתשתיות (מכולה), זה היה לוקח חודשים. מודל ה-Isolates דומה לבית מלון ענק שבו החדרים כבר קיימים, התשתיות מחוברות, וכל מה שצריך זה פשוט לפתוח את הדלת ולהכניס את האורח פנימה. בפועל, קלאודפלייר מריצה מאות אלפי בידודים (Isolates) כאלה על גבי שרת פיזי יחיד באחד מתוך 300+ מרכזי הנתונים שלה ברחבי העולם. כאשר משתמש בתל אביב פונה לאתר שלכם, הבקשה שלו מגיעה לשרת הפיזי הקרוב ביותר אליו, שם נוצר Isolate בשבריר מילישנייה, מריץ את קוד ה-JavaScript שלכם, ומחזיר את התגובה מיידית. אם הקוד שלכם לא רץ, הוא פשוט לא צורך משאבים (Scale to Zero) ולא עולה לכם כסף. המודל הזה מאפשר לקלאודפלייר להעניק חבילה חינמית נדיבה במיוחד של 100,000 בקשות ביום ללא כל עלות.

המחיר של מהירות האור: מה אי אפשר לעשות באדג'?

עם המהירות המטורפת מגיעות גם מגבלות הנדסיות קשיחות. מנוע ה-V8 אינו מערכת הפעלה מלאה, ולכן קוד שרץ בתוך Workers אינו נהנה מאותן יכולות של שרת Node.js מסורתי. אם תנסו להריץ קוד שנכתב עבור שרתי Node.js סטנדרטיים, סביר להניח שתיתקלו בשגיאות מהירות מאוד. הנה שלוש המגבלות המרכזיות שאתם חייבים להכיר בתור Vibe Coders לפני שאתם מתחילים לכתוב קוד: הנה טבלת השוואה מהירה שתעשה לכם סדר בראש בין המודלים השונים:
תכונה מכולות (Containers / VMs) V8 Isolates (Workers)
זמן אתחול (Cold Start) איטי (1-5 שניות) מיידי (מיקרו-שניות בודדות)
צריכת זיכרון מינימלית גבוהה (עשרות או מאות מגה-בייט) נמוכה מאוד (פחות מ-3MB ל-Isolate)
מערכת קבצים (Filesystem) מלאה (קריאה וכתיבה חופשית לדיסק) אין (יש להשתמש ב-Cloud Storage)
תמיכה בספריות Node.js מלאה וללא הגבלות מוגבלת (תמיכה רק ב-Web Standards ו-WASM)
הרצה מקומית של Worker ראשון 3 דקות

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

פתחו את ממשק הטרמינל (Terminal) שלכם והריצו את הפקודות הבאות צעד אחר צעד:

  1. צרו פרויקט Worker חדש בעזרת כלי הפיגומים הרשמי c3 (Create Cloudflare CLI):
    npm create cloudflare@latest my-vibe-worker -- --template hello-world
    הפקודה תוריד את הקבצים הנדרשים ותכין סביבת פיתוח נקייה בתיקייה בשם my-vibe-worker.
  2. היכנסו לתיקיית הפרויקט החדשה שנוצרה:
    cd my-vibe-worker
  3. הריצו את שרת הפיתוח המקומי בעזרת כלי ה-CLI הרשמי Wrangler:
    npx wrangler dev
    כלי זה מריץ סימולטור מקומי סופר-מהיר בשם Miniflare המדמה את התנהגות האדג' של קלאודפלייר אצלכם במחשב.

פלט נראה לעין שתסיים איתו: בטרמינל תופיע שורה המודיעה שהשרת המקומי רץ בכתובת http://localhost:8787. אם תפתחו את הכתובת הזו בדפדפן, תראו מייד את הטקסט "Hello World!" מודפס על המסך ללא כל עיכוב או השהיית אתחול.

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

פירוק פצצת ה-10ms: מה שורף זמן מעבד (CPU Time) ומה לא נספר בכלל

כשאתם בונים באקו-סיסטם של Cloudflare Workers, אתם עומדים להיתקל במהירות במגבלה הבולטת ביותר של המסלול החינמי: מגבלת 10 מילי-שניות של זמן מעבד (10ms CPU limit). עבור מפתחים רבים, ובמיוחד עבור "Vibe Coders" שרגילים לבנות אבות-טיפוס מהירים בעזרת כלי AI, המגבלה הזו נשמעת כמו גזר דין מוות לאפליקציה. המחשבה הראשונה שעולה היא: "אם ה-API של ה-AI שלי לוקח 3 שניות להחזיר תשובה, איך ה-Worker שלי יכול לשרוד עם מגבלה של 10 מילי-שניות בלבד?". כדי לפתור את הפלונטר הזה, צריך להבין את ההבדל התהומי בין שני מושגים של זמן בעולם השרתים: "זמן שעון קיר" (Wall-clock time) לעומת "זמן מעבד" (CPU time). הבנה מדויקת של ההבדל הזה תאפשר לכם לכתוב קוד יעיל במיוחד, לנצל את המסלול החינמי עד הקצה, ולדעת בדיוק מתי האפליקציה שלכם באמת זקוקה לשדרוג למסלול בתשלום.

זמן שעון קיר לעומת זמן מעבד: מי סופר מה?

"זמן שעון קיר" (Wall-clock time) הוא פשוט הזמן הפיזי שחולף בעולם האמיתי מרגע תחילת ריצת הפונקציה ועד סיומה. אם מדדתם בסטופר שלכם 3 שניות, זהו זמן שעון הקיר. לעומת זאת, "זמן מעבד" (CPU time) הוא הזמן המצטבר שבו מעבד השרת הפיזי עסוק באופן פעיל בחישוב ובביצוע פקודות ה-JavaScript שלכם. מנוע ה-V8 של גוגל, שמריץ את ה-Workers בתוך סביבות בידוד קלות משקל הנקראות V8 Isolates (להבדיל ממכולות Docker כבדות או שרתים וירטואליים), עובד בצורה חכמה במיוחד. כאשר הקוד שלכם מבצע פעולה אסינכרונית (פעולה שאינה חוסמת את הריצה וממתינה לגורם חיצוני), כמו פנייה ל-API חיצוני באמצעות fetch, קריאה ממסד נתונים, או המתנה לתוצאה מ-Workers AI - ה-Worker שלכם פשוט "נרדם". בזמן ההמתנה הזה (הנקרא await latency), המעבד הפיזי פנוי לטפל בבקשות של משתמשים אחרים. שעון ה-CPU שלכם עוצר לחלוטין!
פעילות בקוד סוג הזמן הנמדד האם נספר במגבלת ה-10ms?
המתנה ל-fetch() של API חיצוני Wall-clock time ❌ לא (0ms CPU)
קריאה אסינכרונית מתוך Workers KV Wall-clock time ❌ לא
ריצת לולאת for על 10,000 איברים CPU time ✅ כן, באופן מלא
פענוח מחרוזת JSON ענקית (JSON.parse) CPU time ✅ כן, וזהו האויב הגדול ביותר
חיפוש תבניות טקסט מורכבות בעזרת Regex CPU time ✅ כן
בדקו את ה-CPU האמיתי שלכם 3 דקות

היכנסו ל-Cloudflare Dashboard שלכם, בחרו ב-Worker פעיל, והביטו בגרף ה-CPU Time (ולא ב-Duration). אתם תגלו שגם ב-Workers שמבצעים פניות אסינכרוניות ארוכות ל-APIs של AI והחזרת תגובה שלוקחת שניות ארוכות, זמן ה-CPU הממוצע נע לרוב בין 0.5ms ל-2ms בלבד.

מלכודת ה-JSON.parse והרשתות הסבוכות של ה-Regex

אם זמן ההמתנה לרשת אינו נספר, מה בכל זאת שורף לנו את ה-10ms ומפיל את ה-Worker? התשובה המרכזית היא עיבוד נתונים מקומי. שני המקורות הנפוצים ביותר לקריסה של Workers חינמיים הם: 1. עומס יתר של JSON.parse (JSON.parse overhead): כשאתם פונים ל-API ומקבלים תגובה גדולה (לדוגמה, קובץ נתונים של 2MB או פלט טקסטואלי ענקי של נתוני משתמשים), הפעולה של הפיכת מחרוזת הטקסט הגולמית לאובייקט JavaScript מובנה היא פעולה סינכרונית כבדה במיוחד. מנוע ה-V8 נאלץ לעבור תו-תו, לאמת את המבנה ולבנות את עץ הזיכרון. פענוח של קובץ JSON בגודל 2MB יכול לקחת בקלות 12 עד 20 מילי-שניות של זמן מעבד רצוף. בום! ה-Worker שלכם יקרוס מיד עם שגיאת CPU Limit Exceeded (קוד שגיאה 1102). 2. עלויות ריצה של ביטויים רגולריים (Regex execution cost): ביטויים רגולריים (Regex) הם כלי פופולרי מאוד לניקוי ועיבוד טקסטים שמגיעים ממודלי שפה (LLMs). עם זאת, הרצה של ביטוי רגולרי לא יעיל (במיוחד כזה שכולל קבוצות לכידה מקוננות שיוצרות מצב של Backtracking מוגזם) על טקסט ארוך, תגרום למעבד "להתקע" בחישובים סינכרוניים אינסופיים שישרפו את מכסת ה-10ms שלכם בשבריר שנייה. כדי להימנע מקריסות אלו, מומלץ לעבוד עם שיטות של הזרמת נתונים (Streaming), לעבד נתונים במקטעים קטנים, או לשדרג למסלול ה-Paid של Workers (שעלותו החל מ-5$ לחודש ומעניקה לכם תקרת CPU בריאה של עד 30 שניות כברירת מחדל, ואף יותר מכך למשימות מתוזמנות).
סימולטור Wall-clock לעומת CPU Time ומדידת עומסים ב-JSON Parse 20 דקות

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

  1. ודאו שמותקן אצלכם Node.js על ידי הרצת הפקודה הבאה במסוף (Terminal):
    node -v
    פלט צפוי: מספר הגרסה של Node (למשל v18.16.0 או גבוה יותר). אם הפקודה לא מזוהה, התקינו את Node.js מהאתר הרשמי שלהם לפני המשך התרגיל.
  2. התחברו לחשבון ה-Cloudflare שלכם דרך ממשק ה-CLI בעזרת הרצת הפקודה הבאה:
    npx wrangler login
    פלט צפוי: ייפתח חלון דפדפן שיבקש מכם לאשר את החיבור לחשבון Cloudflare שלכם. לאחר האישור, תראו במסוף הודעת הצלחה הכוללת את כתובת האימייל שלכם.
  3. צרו תיקיית פרויקט חדשה והתקינו בה את Wrangler:
    mkdir cpu-test-worker && cd cpu-test-worker && npm init -y && npm install wrangler --save-dev
    פלט צפוי: יצירת קובץ package.json בתיקייה והתקנה מקומית של Wrangler.
  4. צרו קובץ הגדרות בשם wrangler.toml בשורש התיקייה, והעתיקו אליו את התוכן הבא:
    name = "cpu-test-worker"
    main = "src/index.js"
    compatibility_date = "2026-03-01"
  5. צרו תיקייה בשם src ובתוכה קובץ בשם index.js. העתיקו לתוכו את קוד ה-Worker הבא במלואו:
    export default {
      async fetch(request, env, ctx) {
        // 1. מדידת זמן תחילת הריצה (Wall-Clock)
        const wallClockStart = performance.now();
    
        // 2. הדמיית השהיית רשת אסינכרונית (await latency) - לא צורכת CPU!
        // אנו מבצעים פנייה לשרת חיצוני שמחזיר תשובה לאחר השהייה קלה
        const networkStart = performance.now();
        await fetch("https://httpbin.org/delay/1");
        const networkEnd = performance.now();
        const networkLatency = networkEnd - networkStart;
    
        // 3. יצירת מחרוזת JSON ענקית בזיכרון המדמה פלט API גדול (כ-2.5MB)
        // אנו עושים זאת סינכרונית כדי למדוד עומס עיבוד
        const cpuStart = performance.now();
        
        const largeObject = {};
        for (let i = 0; i < 40000; i++) {
          largeObject[`key_item_${i}`] = {
            id: i,
            uuid: "abc-123-xyz-987-test-payload",
            active: true,
            nested: { value: Math.random() }
          };
        }
        const jsonString = JSON.stringify(largeObject);
        
        // 4. המלכודת הגדולה: פענוח של ה-JSON הענקי
        const parsedData = JSON.parse(jsonString);
        
        const cpuEnd = performance.now();
        const estimatedCpuTime = cpuEnd - cpuStart;
    
        // 5. מדידת סיום זמן הריצה הכולל (Wall-Clock)
        const wallClockEnd = performance.now();
        const totalWallClock = wallClockEnd - wallClockStart;
    
        // חישוב אחוז ניצול ה-CPU המשוער בהתבסס על מגבלת ה-10ms של המסלול החינמי
        const cpuLimitMs = 10;
        const cpuUsagePercent = (estimatedCpuTime / cpuLimitMs) * 100;
        const status = estimatedCpuTime > cpuLimitMs ? "CRITICAL_LIMIT_EXCEEDED" : "SAFE";
    
        const responsePayload = {
          status: status,
          metrics: {
            totalWallClockMs: parseFloat(totalWallClock.toFixed(2)),
            networkAwaitLatencyMs: parseFloat(networkLatency.toFixed(2)),
            estimatedCpuTimeMs: parseFloat(estimatedCpuTime.toFixed(2)),
            freeTierCpuLimitMs: cpuLimitMs,
            cpuUsagePercent: parseFloat(cpuUsagePercent.toFixed(1))
          },
          warning: "Note that performance.now() in Workers local environment might show slight variations compared to production production V8 hardware."
        };
    
        return new Response(JSON.stringify(responsePayload, null, 2), {
          headers: { "Content-Type": "application/json" }
        });
      }
    };
  6. הריצו את ה-Worker שלכם באופן מקומי לבדיקה:
    npx wrangler dev
    פלט צפוי: תראו כתובת מקומית כמו http://localhost:8787 מוצגת במסוף. פתחו את הדפדפן שלכם בכתובת הזו. לאחר כחצי שנייה של המתנה, הדפדפן יציג לכם פלט JSON המראה כי ה-Network Latency היה גבוה מאוד (מעל 1000ms), אך זמן ה-CPU המוערך של העיבוד שלכם היה נמוך משמעותית (לרוב סביב 4ms-8ms, תלוי בעוצמת המעבד של המחשב שלכם).
  7. פרסו את ה-Worker שלכם לרשת האינטרנט העולמית של Cloudflare:
    npx wrangler deploy
    פלט צפוי: תהליך העלאה מהיר שבסופו תקבלו כתובת URL חיה ורשמית (למשל https://cpu-test-worker.[your-subdomain].workers.dev). היכנסו לכתובת זו מהטלפון או מהמחשב כדי לראות את המדידה החיה על חומרת ה-Edge האמיתית של Cloudflare.

פלט נראה לעין שתסיים איתו: תקבלו דף אינטרנט המציג מסמך JSON מובנה ומפורט שמנתח בזמן אמת כמה מילי-שניות התבזבזו על המתנה לרשת (שלא נספרת במגבלת ה-10ms) לעומת כמה מילי-שניות של זמן מעבד שרפתם על פענוח ה-JSON, לצד אחוז סיכון מדויק לקריסת ה-Worker שלכם.

בינוני5 דקותחינםperformance-optimization

מכסת ה-Subrequests ומלכודת ה-Waterfall: איך להריץ 50 קריאות בחינם מבלי לחטוף timeout

כשאתם בונים יישומים מבוססי בינה מלאכותית (AI) על גבי Cloudflare Workers, קל מאוד להסתחרר מהמהירות של הרשת של קלאודפליי. הרי מדובר בריצה ישירות על "הקצה" (Edge) – בתוך סביבות הרצה מבודדות וקלות משקל המכונות Isolates (טכנולוגיית כתיבת קוד המבוססת על מנוע V8 של דפדפן כרום, המאפשרת לעלות לאוויר ללא זמני הפעלה קרים ובמהירות שיא). אבל ברגע שהקוד שלכם מתחיל לדבר עם העולם החיצון – לשלוף הגדרות מתוך בסיס הנתונים המהיר Workers KV (אחסון מפתח-ערך גלובלי), לבדוק מידע ב-D1 (בסיס נתונים SQL מבוזר), או לפנות ל-API חיצוני של OpenAI – אתם נתקלים במושג קריטי שנקרא קריאות משנה (Subrequests).

מהי בדיוק קריאת משנה? בכל פעם שה-Worker שלכם מבצע פקודת fetch() לאתר חיצוני, או פונה לאחד משירותי האחסון המובנים של קלאודפליי (כמו KV ,D1 ,R2 או Vectorize), הוא מייצר Subrequest. בחשבון החינמי של Cloudflare, המגבלה עומדת על 50 קריאות משנה לכל הפעלה בודדת של ה-Worker. עבור רוב האפליקציות זה נשמע המון, אך הבעיה האמיתית אינה רק כמות הקריאות, אלא הדרך שבה קוד שנוצר על ידי כלי AI נוטה לבצע אותן: בזו אחר זו, במה שנקרא תבנית מפל מים (Waterfall Pattern).

מלכודת ה-Waterfall: למה ה-Worker שלכם זוחל?

כלי פיתוח מבוססי AI כמו Cursor, GitHub Copilot או ChatGPT נוטים לכתוב קוד קריא ופשוט להבנה. הדרך הקלה ביותר לכתוב לוגיקה שמערבת מספר מקורות מידע היא סדרתית (Sequential). לדוגמה, אם האפליקציה שלכם צריכה להביא חמישה קבצי הגדרה שונים מ-KV, ה-AI כנראה יכתוב לכם קוד שנראה כך:

const user = await env.USERS_KV.get("user_123");
const theme = await env.SETTINGS_KV.get("theme_preference");
const localization = await env.LANG_KV.get("hebrew_pack");

במבט ראשון הקוד הזה נראה מצוין, אך מאחורי הקלעים הוא יוצר אסון ביצועים המכונה מלכודת ה-Waterfall. ה-Worker שולח בקשה ראשונה ל-KV, עוצר ומחכה שהתשובה תחזור. רק כשהיא חוזרת, הוא שולח את הבקשה השנייה, עוצר ומחכה שוב. אם כל פנייה כזו לוקחת 25 מילי-שניות (ms), שלוש הפניות האלו לבדן יגזלו 75ms של "זמן שעון אמיתי" (Wall-clock time).

כאן נכנסת ההבחנה החשובה ביותר שכל Vibe Coder חייב להכיר: זמן מעבד (CPU Time) לעומת זמן שעון (Wall-clock Time). במסלול החינמי של Cloudflare יש לכם מגבלה קשיחה של 10ms של זמן CPU לכל הרצה. אבל זמן CPU נמדד רק כאשר ה-Worker שלכם מבצע חישובים בפועל באופן פעיל (כמו עיבוד טקסט, הרצת לולאות או פענוח JSON). בזמן שהקוד שלכם עושה await ומחכה לתשובה מהרשת או מה-KV, ה-CPU של ה-Isolate נמצא במצב המתנה והטיימר שלו לא דופק! עם זאת, אם תשרשרו 20 קריאות במבנה של מפל מים, אתם אומנם לא תעברו את ה-10ms CPU, אך זמן התגובה של האתר שלכם יהיה אטי להחריד (חצי שנייה של המתנה מיותרת למשתמש הקצה), ובמקרים קיצוניים של המתנה ארוכה מדי לקריאות API חיצוניות, אתם עלולים לחטוף שגיאת Timeout מהרשת עצמה.

הפתרון: ריצה מקבילית בעזרת Promise.all

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

ב-JavaScript (השפה הרשמית של ה-Workers), הדרך לעשות זאת היא באמצעות Promise.all. כלי ה-AI שלכם יודע להשתמש בזה, אך אתם צריכים להנחות אותו לעשות זאת במפורש. במקום לכתוב שלושה קווי await נפרדים, אנחנו מייצרים מערך של בקשות (Promises) ומבצעים await יחיד על כולם יחד. בצורה זו, כל שלוש הפניות ל-KV יתבצעו באותו זמן בדיוק, וזמן ההמתנה הכולל של ה-Worker יהיה שווה לזמן של הבקשה האיטית ביותר מבין השלוש (כ-25ms בלבד במקום 75ms).

אופטימיזציה של קריאות משנה ב-Worker שלכם 10 דקות

בצעד זה נשכתב קוד טיפוסי של מפל מים (Waterfall) המבצע קריאות משנה מרובות ל-API חיצוני ונקצר את זמן הריצה שלו דרמטית באמצעות עבודה מקבילית נכונה.

  1. פתחו את קובץ ה-Worker הראשי שלכם (למשל index.js או src/index.ts) בפרויקט המקומי שלכם או בתוך עורך ה-Cursor.
  2. החליפו את קוד הטיפול הקיים שלכם בקוד הבא, המציג סימולציה של פנייה ל-3 מקורות מידע שונים במקביל:
export default {
  async fetch(request, env, ctx) {
    const startTime = Date.now();

    // הגדרת רשימת ה-URLs שאנו רוצים לתשאל (למשל, קריאות ל-API או ל-KV)
    const urls = [
      "https://api.cloudflare.com/client/v4/ips",
      "https://httpbin.org/delay/1", // קריאה שמדמה השהיה של שנייה
      "https://httpbin.org/user-agent"
    ];

    try {
      // במקום לבצע await לכל fetch בנפרד, אנו יוצרים מערך של Promises
      const promises = urls.map(url => fetch(url).then(res => res.json()));

      // הרצה מקבילית אמיתית - כל 3 קריאות המשנה יוצאות לדרך בו-זמנית!
      const [cloudflareIPs, delayedData, userAgent] = await Promise.all(promises);

      const duration = Date.now() - startTime;

      return new Response(JSON.stringify({
        message: "Success!",
        durationMs: duration,
        data: {
          ipsCount: cloudflareIPs.result?.ipv4_cidrs?.length || 0,
          userAgent: userAgent["user-agent"]
        }
      }), {
        headers: { "Content-Type": "application/json" }
      });

    } catch (error) {
      return new Response(JSON.stringify({ error: error.message }), {
        status: 500,
        headers: { "Content-Type": "application/json" }
      });
    }
  }
};
  1. הריצו את השרת המקומי באמצעות פקודת ה-Wrangler הבאה בטרמינל (הפקודה תפעיל את מנוע Miniflare המובנה שמדמה את סביבת הענן באופן מקומי):
    npx wrangler dev
    הפלט הצפוי בטרמינל יציג הודעה שהשרת רץ בכתובת local host (למשל http://localhost:8787).
  2. פתחו את הדפדפן או שלחו בקשת curl לכתובת שקיבלתם. שימו לב לערך durationMs המוחזר ב-JSON. תראו שהוא עומד על כ-1000ms בלבד (בגלל ההשהיה המובנית של הבקשה השנייה), למרות שביצענו שלוש קריאות רשת נפרדות שהיו אמורות לקחת במצטבר הרבה יותר זמן!

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

מתקדם5 דקותחינםarchitectural-framework

ארכיטקטורת האחסון של Cloudflare: הבדלי Consistency והתאמה לתרחישים

כשבונים אפליקציות מודרניות מבוססות בינה מלאכותית, אחת הטעויות הנפוצות ביותר היא להניח שניתן פשוט לחבר את ה-Worker שלנו למסד נתונים מסורתי שמארח בשרת יחיד (למשל, AWS RDS באורגון). קלאודפליי מארחת את הקוד שלכם ב-V8 *Isolates* (סביבות הרצה מבודדות וקלות משקל, הפועלות ללא צורך במערכת הפעלה מלאה) ביותר מ-300 ערים בעולם (ה-*Edge*, קצה הרשת קרוב למשתמש). אם ה-Worker שלכם בטוקיו יצטרך לבצע שאילתת SQL מול שרת בארה"ב בכל טעינת עמוד, זמן התגובה שלכם יקרוס ומגבלת ה-10ms CPU בשכבה החינמית תיחצה מיד. כדי לפתור זאת, Cloudflare מציעה ארכיטקטורת אחסון מבוזרת המותאמת במיוחד לעבודה בקצה, אך היא דורשת הבנה מעמיקה של עקרונות העקביות (*Consistency*). כדי לבחור נכון בין פתרונות האחסון השונים, עלינו להבין את ההבדל בין *Eventual Consistency* (עקביות בסופו של דבר) ל-*Strong Consistency* (עקביות חזקה). במערכות מבוזרות בקנה מידה גלובלי, לא ניתן לקבל גם מהירות קריאה אפסית בכל נקודה בעולם וגם עדכון נתונים מיידי בכל השרתים בו-זמנית. *Workers KV* הוא מסד נתונים מסוג מפתח-ערך (Key-Value) המצטיין ב-Eventual Consistency. כשאתם כותבים מידע ל-KV, העדכון נשמר בשרת הראשי ומתחיל להשתכפל לכל מאות השרתים בעולם. תהליך השכפול הזה לוקח בדרך כלל כ-60 שניות. המשמעות היא שקריאות של הנתון מאזור גיאוגרפי אחר עשויות להחזיר את הערך הישן למשך דקה שלמה, אך הן יתבצעו במהירות שיא של מילישניות בודדות כי המידע נשלף ישירות מהמטמון (Cache) המקומי של אותו שרת קצה. לעומת זאת, אפליקציות רבות דורשות *Strong Consistency* — הבטחה מוחלטת שכל קריאה של נתון מיד לאחר כתיבתו תחזיר את הערך המעודכן ביותר, ללא קשר למיקום הגיאוגרפי של המשתמש. שירות ה-*D1* (מסד נתונים יחסי מסוג SQLite המנוהל בקצה) פותר זאת על ידי ניתוב כל פעולות הכתיבה לשרת ראשי (Primary) יחיד, בעוד שפעולות הקריאה משוכפלות גלובלית. עבור אפליקציות הדורשות תיאום בזמן אמת, כמו צ'אטים קבוצתיים או עריכת מסמכים משותפת, Cloudflare מציעה את *Durable Objects (DO)*. אלו הם רכיבי תוכנה בעלי כתובת גלובלית ייחודית, המחזיקים מצב (State) בזיכרון המקומי שלהם ומנהלים תור הרצה חד-הליכי (Single-threaded). כל הפניות לאובייקט ספציפי מנותבות לאותו שרת פיזי יחיד בעולם שבו האובייקט רץ באותו רגע, מה שמבטיח עקביות חזקה ואפס סיכוי להתנגשויות מידע. בבואנו לתכנן את התקציב לפרויקט, עלינו לבחון היטב את מגבלות ה-*Free Tier* (השכבה החינמית) מול ה-*Paid Tier* (התוכנית בתשלום של $5 בחודש). בשכבה החינמית, המגבלות אינן נמדדות רק בנפח האחסון אלא בעיקר בכמות הפעולות (Operations). לדוגמה, ב-KV תקבלו 100,000 קריאות ביום אך רק 1,000 כתיבות ביום. אפליקציה קטנה עם 50 משתמשים פעילים המעדכנים סטטוס מספר פעמים בשעה תעבור את תקרת הכתיבה החינמית תוך חצי יום. מנגד, D1 מעניק מגבלה נדיבה בהרבה של 100,000 כתיבות שורות ו-5 מיליון קריאות שורות ביום, מה שהופך אותו לבחירה המועדפת על מפתחים רבים כבר משלבי ה-Bootstrapping הראשונים.
מטריצת בחירת האחסון באדג' לפי ארבעה קריטריונים

קריטריון 1: סוג המידע המאוחסן

קריטריון 2: דרישות עקביות הנתונים (Consistency)

קריטריון 3: יחס קריאה/כתיבה (Read/Write Ratio)

קריטריון 4: מגבלות תקציב ושכבה חינמית

השוואת תכונות ומגבלות של פתרונות האחסון ב-Cloudflare (מעודכן ל-2026)

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

סוג שירות האחסון מודל עקביות (Consistency) מגבלת נפח בשכבה החינמית מגבלת פעולות יומית בחינם תרחיש שימוש אופטימלי
Workers KV Eventual Consistency (עקביות בסופו של דבר) 1 GB סה"כ 100,000 קריאות / 1,000 כתיבות הגדרות קונפיגורציה, מפתחות API, הפצת הגדרות גלובליות
D1 Database Strong Consistency (בכתיבה לשרת הראשי) 5 GB סה"כ 5,000,000 קריאות שורה / 100,000 כתיבות שורה אפליקציות SaaS, ניהול משתמשים, מסדי נתונים יחסיים (SQL)
R2 Object Storage Strong Consistency (עבור פעולות בודדות) 10 GB סה"כ 10M קריאות (Class B) / 1M כתיבות (Class A) אחסון תמונות פרופיל, העלאות קבצים של משתמשים, וידאו
Durable Objects Strong Consistency (מוחלטת, באותו Isolate) 5 GB (במודל SQLite החדש מ-2026) 100,000 בקשות אובייקט / 5M קריאות שורת SQLite חדרי צ'אט, משחקים מרובי משתתפים, עורכים שיתופיים, WebSockets
התקנת פרויקט מקומי ובדיקת עקביות עם KV 10 דקות

כדי להבין כיצד פועל פיתוח מקומי מול רכיבי האחסון של קלאודפליי, נקים כעת פרויקט מינימלי ונבדוק כתיבה וקריאה מ-KV מקומי בעזרת Wrangler CLI (כלי הפיתוח הרשמי של Cloudflare).

  1. פתחו את מסוף הפקודות (Terminal) והריצו את הפקודה הבאה כדי ליצור פרויקט Worker חדש (ודאו שמותקן אצלכם Node.js):
    npm create cloudflare@latest my-storage-vibe -- --framework=hono
    במהלך ההתקנה, בחרו בברירות המחדל (TypeScript, ויצירת פרויקט חדש).
  2. היכנסו לתיקייה שנוצרה והתקינו את התלויות:
    cd my-storage-vibe
  3. פתחו את הקובץ wrangler.toml הממוקם בשורש הפרויקט והוסיפו את קישור ה-KV (הנקרא Binding) על ידי הוספת השורות הבאות בסוף הקובץ:
    [[kv_namespaces]]
    binding = "MY_KV"
    id = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" # מזהה דמוי עבור הפיתוח המקומי
  4. פתחו את הקובץ src/index.ts והחליפו את תוכנו בקוד הבא, המאפשר כתיבה וקריאה של מידע מה-KV:
    import { Hono } from 'hono'
    
    type Bindings = {
      MY_KV: KVNamespace
    }
    
    const app = new Hono<{ Bindings: Bindings }>()
    
    app.get('/write', async (c) => {
      const timestamp = new Date().toISOString()
      await c.env.MY_KV.put('last_access', timestamp)
      return c.text(`Saved timestamp: ${timestamp}`)
    })
    
    app.get('/read', async (c) => {
      const value = await c.env.MY_KV.get('last_access')
      return c.text(`Stored value: ${value || 'No value found yet'}`)
    })
    
    export default app
  5. הריצו את השרת המקומי באמצעות Wrangler:
    npx wrangler dev
    השרת המקומי יפתח בכתובת http://localhost:8787.
  6. פתחו את הדפדפן וגשו לכתובת http://localhost:8787/write. אתם אמורים לראות את הפלט המאשר את שמירת זמן הכתיבה. לאחר מכן, גשו לכתובת http://localhost:8787/read כדי לראות את שליפת הנתון ששמרתם.

פלט נראה לעין שתסיים איתו: בדפדפן יוצג הטקסט Stored value: 2026-05-29T12:00:00.000Z (בהתאם לזמן המדויק שבו הרצתם את פקודת הכתיבה). במסוף של Wrangler תוכלו לראות את תיעוד הבקשות ואת השימוש בממשק Local Explorer המאפשר לכם לצפות פיזית בערכים השמורים ב-KV המקומי שלכם דרך הדפדפן.

מתחיל5 דקותחינםstorage-configuration

Workers KV: זיכרון מטמון מבוזר באדג', המהירות האמיתית ומלכודת ה-1,000 כתיבות ביום

כשאתם בונים אפליקציות מבוססות בינה מלאכותית (AI) המשרתות משתמשים ברחבי העולם, המהירות היא הכל. רוב המפתחים רגילים לעבוד עם בסיסי נתונים מרכזיים (כמו PostgreSQL או MongoDB), אך ברגע שהשרת שלכם נמצא בצד אחד של העולם והמשתמש שלכם בצד השני, כל פנייה לנתונים מוסיפה עשרות ומאות מילישניות של זמן המתנה יקר. כאן נכנס לתמונה Workers KV (קיצור של Key-Value Store, מחסן מפתח-ערך מבוזר). מדובר במאגר נתונים מבוסס פשטות המתוכנן במיוחד לקריאה אולטרה-מהירה ישירות מתוך שרתי הקצה (Edge — שרתים הפזורים קרוב פיזית למשתמשי הקצה) של Cloudflare הפזורים ביותר מ-300 ערים בעולם.

הקסם האמיתי של Workers KV נעוץ ביכולת שלו לעקוף את "קיר ה-10ms" (הגבלת זמן ה-CPU הקשיחה של 10 מילישניות בלבד להרצת קוד בתוכנית החינמית של Workers). קריאה מתוך KV המקושר ל-Worker באמצעות Binding (קישור מובנה ומהיר המאפשר ל-Worker לגשת למשאבים של Cloudflare ללא קריאות HTTP חיצוניות) לא נספרת כזמן מעבד פעיל. זה מאפשר לכם לבצע אלפי שליפות מידע, לבדוק הרשאות או לשלוף הגדרות מערכת בשבריר מילישנייה מבלי לחשוש שה-Worker שלכם ייחסם או יקרוס. הנתונים נשמרים ישירות בזיכרון המטמון (Cache) של שרת הקצה הקרוב ביותר למשתמש שלכם, ובכך נמנע הצורך "לנסוע" הלוך וחזור לבסיס נתונים מרכזי הממוקם במדינה אחרת.

המלכודת: KV Eventual Consistency ומגבלת ה-1,000 כתיבות ביום

עם זאת, המהירות הפנומנלית הזו מגיעה עם מחיר ארכיטקטוני כבד שנקרא KV Eventual Consistency (עקביות בסופו של דבר). כאשר אתם כותבים או מעדכנים מפתח ב-KV, השינוי נכתב תחילה לשרת המרכזי של Cloudflare ורק אז מתחיל לחלחל בהדרגה לכל שאר 300+ שרתי הקצה בעולם. תהליך הפצה זה יכול לקחת עד 60 שניות. המשמעות היא שאם משתמש מעדכן את פרופיל המשתמש שלו מטוקיו, משתמש אחר (או אותו משתמש עצמו בבקשה עוקבת) שיגלוש מלונדון עשוי לראות את המידע הישן למשך עוד דקה שלמה. לכן, KV הוא כלי מושלם עבור נתונים שנכתבים לעיתים רחוקות ונקראים לעיתים קרובות מאוד (Read-heavy) – כמו הגדרות מערכת, דגלי תכונות (Feature Flags), או נתוני הפצה קבועים.

כאן בדיוק מונחת ה-KV write limits trap (מלכודת מגבלות הכתיבה). במסלול החינמי של Cloudflare, אתם זכאים ל-100,000 קריאות ביום, אך רק ל-1,000 כתיבות ביום. מפתחים רבים הבונים כלי AI נופלים במלכודת הזו כשהם מנסים להשתמש ב-KV כבסיס נתונים רגיל לשמירת סשנים פעילים של משתמשים, היסטוריית צ'אטים, או אנליטיקות בזמן אמת. כל פעולה כזו תובעת כתיבה, תחסל את המכסה היומית בתוך דקות בודדות, ותביא לקריסת האפליקציה.

תכונה Workers KV D1 (Serverless SQLite) R2 (Object Storage)
שימוש עיקרי הגדרות קבועות, מפתחות API, מטמון קריאה קבוע נתונים טבלאיים מורכבים, ניהול משתמשים, שאילתות SQL קבצים גדולים, תמונות, קובצי קול, גיבויים ומדיה
מהירות קריאה אולטרה מהיר (באדג', פחות מ-10ms) מהיר מאוד (סמוך ל-Worker, עקבי לחלוטין) בינוני (מותאם להעברת קבצים, ללא דמי egress)
רמת עקביות עקביות בסופו של דבר (עד 60 שניות הפצה) עקביות חזקה (Strong Consistency) עקביות חזקה מיידית לאחר כתיבה
מגבלת נפח חינמית 1 GB נפח אחסון כולל 5 GB נפח אחסון כולל 10 GB נפח אחסון כולל בחודש
טעות נפוצה: שימוש ב-KV לעדכון מונה כניסות (Hits counter) בזמן אמת

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

למה זה טעות: פעולה זו תחסל במהירות את מכסת ה-1,000 הכתיבות היומיות החינמיות שלכם (1,000 כניסות לאתר וחסמתם את האפליקציה שלכם לחלוטין). בנוסף, בגלל "עקביות בסופו של דבר" (Eventual Consistency), שינויים לוקחים עד 60 שניות להתפשט לכל השרתים בעולם. אם שני משתמשים ייכנסו במקביל, שניהם יקראו את אותו מספר ישן, יכתבו את אותו ערך חדש, והספירה תתחרבש ותציג נתונים לא נכונים למשתמשים.

מה לעשות במקום: השתמשו ב-D1 (בסיס הנתונים ה-SQL-י של Cloudflare) המאפשר שאילתות כתיבה חזקות ועקביות עם מכסת כתיבה נדיבה בהרבה של 100,000 שורות ביום, או ב-Durable Objects שמנהלים את ה-State בזיכרון בשיטה סינגל-תרדית (Single-threaded) אולטרה-מהירה מבלי ללכלך את הדיסק הפיזי בכל שנייה.

תבנית cf-kv.ts לקריאה מחוץ לשרת המקומי

לעיתים תרצו לגשת לנתונים השמורים ב-KV שלכם לא רק מתוך ה-Worker עצמו, אלא מתוך סקריפטים מקומיים, שרתים חיצוניים (כמו Next.js הרץ ב-Vercel), או כלי אוטומציה מבוססי פייתון. עבור מקרים אלו, השימוש ב-cf-kv.ts pattern הוא הפתרון האידיאלי. זוהי תבנית קוד קומפקטית המשתמשת ישירות ב-REST API הרשמי של Cloudflare עם Bearer Token (אסימון אבטחה מבוסס הרשאות) כדי לבצע פעולות קריאה וכתיבה מרחוק בצורה מאובטחת וללא תלות ב-Wrangler או בסביבת הריצה של Workers.

להלן קובץ ה-TypeScript המלא (cf-kv.ts) המממש את התבנית הזו. תוכלו להעתיק אותו ישירות לפרויקט שלכם:

// cf-kv.ts
export interface KVConfig {
  accountId: string;
  namespaceId: string;
  apiToken: string;
}

/**
 * שולף ערך מ-Workers KV באמצעות קריאת API ישירה לקלאודפלייר
 */
export async function getKVValue(config: KVConfig, key: string): Promise<string | null> {
  const { accountId, namespaceId, apiToken } = config;
  const url = `https://api.cloudflare.com/client/v4/accounts/${accountId}/storage/kv/namespaces/${namespaceId}/values/${key}`;

  try {
    const response = await fetch(url, {
      method: "GET",
      headers: {
        "Authorization": `Bearer ${apiToken}`,
        "Content-Type": "application/json",
      },
    });

    if (response.status === 404) {
      return null; // המפתח אינו קיים במערכת
    }

    if (!response.ok) {
      const errorText = await response.text();
      throw new Error(`Cloudflare KV API Error: ${response.status} - ${errorText}`);
    }

    // ה-API מחזיר את הערך הגולמי ישירות בתוך גוף התשובה
    return await response.text();
  } catch (error) {
    console.error("Failed to fetch from Workers KV:", error);
    throw error;
  }
}
הגדרה וקריאה מ-KV מקומי 8 דקות

בצעו את הצעדים הבאים כדי להגדיר תיקיית KV מקומית, לכתוב אליה ערך ולשלוף אותו בהרצה מקומית מלאה:

  1. פתחו את קובץ ההגדרות

    D1 SQLite ב-Edge: שאילתות רלציוניות ומחלת ה-Rows Read שתשבית לכם את המסד

    כשאתם בונים אפליקציות מודרניות על גבי התשתית של Cloudflare, אחד הכלים החזקים ביותר שעומדים לרשותכם הוא D1. זהו מסד נתונים רלציוני מבוסס SQLite, הפועל ישירות בתוך ה-Edge (קצה הרשת — רשת שרתים עולמית הפרוסה ביותר מ-300 ערים בעולם ומקרבת את החישוב פיזית אל המשתמש). המשמעות היא שרמת השהיית הגישה (Latency — הזמן שלוקח למידע לעבור מהשרת למשתמש) היא אפסית. ה-Workers שלכם (רכיבי Serverless המבוססים על V8 Isolate — סביבת הרצה מבודדת וקלת משקל המאפשרת הפעלה מיידית ללא השהיית עלייה המכונה Cold Start) מתקשרים עם D1 בצורה מהירה ויעילה במיוחד. עם זאת, העבודה בסביבת קצה מביאה איתה מודל תמחור ומגבלות ייחודיים שאתם חייבים להבין לעומק כדי לא להתעורר בוקר אחד ולגלות שהאפליקציה שלכם הושבתה. בעוד שבמסדי נתונים מסורתיים אתם משלמים על זיכרון ומעבד, ב-D1 המדד המרכזי שקובע אם תעברו למסלול בתשלום הוא כמות השורות שנקראו (Rows Read) וכמות השורות שנכתבו (Rows Written). בתוכנית החינמית אתם מקבלים מגבלה נדיבה של 5 מיליון שורות שנקראו ביום ו-100,000 שורות שנכתבו ביום. כאן בדיוק טמונה המלכודת הגדולה שנקראת Full-Table Scan (סריקת טבלה מלאה). כאשר אתם מריצים שאילתת SQL עם תנאי סינון מסוים, מנוע SQLite נדרש למצוא את השורות המתאימות. אם השדה שאתם מסננים לפיו אינו מוגדר כ-Index (אינדקס — מבנה נתונים מיוחד המאפשר למסד למצוא שורות במהירות מבלי לסרוק את כולן), המנוע ייאלץ לקרוא כל שורה ושורה בטבלה שלכם מתחילתה ועד סופה. אם יש לכם 50,000 שורות בטבלה, חיפוש של שורה בודדת ללא אינדקס מתאים יעלה לכם ב-50,000 Rows Read, למרות שהקוד שלכם יקבל חזרה רק שורה אחת! הרצה של שאילתה כזו 100 פעמים בלבד ביום תביא אתכם למגבלת ה-5 מיליון ותשתק את האפליקציה שלכם לחלוטין.

    ההבדל הקריטי בין שורות שחזרו לשורות שנקראו

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

    SELECT * FROM users WHERE email = 'x'SELECT * FROM users WHERE email = 'x'SELECT * FROM posts LIMIT 10SELECT * FROM posts ORDER BY views DESC LIMIT 10
    סוג השאילתה מצב האינדקס בטבלה שורות מוחזרות (Returned) שורות שנקראות בפועל (Rows Read)
    ללא אינדקס (Full-Table Scan) 1 N (כל השורות בטבלה)
    קיים אינדקס על העמודה email 1 1
    ללא סינון (סריקה פשוטה עם מגבלה) 10 10
    ללא אינדקס על העמודה views 10 N (סריקה ומיון של כל הטבלה)
    טעות נפוצה: הרצת שאילתות SELECT ללא סעיף WHERE על טבלאות צומחות ב-D1

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

    למה זה טעות: פעולה זו גורמת ל-Full-Table Scan מלא בכל פנייה. ככל שקהל המשתמשים שלכם גדל וכמות הנתונים נצברת, כל קריאה בודדת למסד הנתונים תעלה לכם במיליוני Rows Read. המכסה החינמית של 5 מיליון שורות ביום תישרף תוך דקות בודדות, והאפליקציה תחזיר שגיאות קריטיות לכל המשתמשים.

    מה לעשות במקום: הגדירו תמיד מגבלות LIMIT קשיחות בקוד השאילתה, והקפידו ליצור INDEX מוגדר על כל השדות שמופיעים בתוך סעיפי ה-WHERE או ה-ORDER BY שלכם.

    בדקו את תוכנית השאילתה שלכם עכשיו! 3 דקות

    פתחו את ממשק הטרמינל (Terminal) שלכם והריצו את פקודת ה-Explain של Wrangler כדי לבדוק האם השאילתה הכי נפוצה באפליקציה שלכם מבצעת סריקת טבלה מלאה ומסוכנת:

    npx wrangler d1 execute my-database-name --remote --command="EXPLAIN QUERY PLAN SELECT * FROM users WHERE email = 'user@example.com';"

    אם בפלט של הפקודה אתם רואים את המילים SCAN TABLE, המשמעות היא שאתם מבצעים סריקה מלאה ומבזבזים משאבים. אם מופיע SEARCH TABLE USING INDEX, השאילתה שלכם מוגנת ויעילה!

    מתודולוגיית ניהול והגבלת Rows Read ב-D1

    שלב 1: הוספת Index מדויק על השדות בשאילתות ה-WHERE.

    • ללא אינדקס, חיפוש שדה יסרוק את כל הטבלה. יצירת אינדקס בעזרת CREATE INDEX הופכת את עלות החיפוש מ-O(N) (סריקת כל השורות) ל-O(log N) (סריקה של שורות בודדות בלבד).

    שלב 2: הגבלת כמות השורות המוחזרות באמצעות LIMIT קשיח.

    • לעולם אל תסמכו על כך שהטבלה קטנה. הגדירו LIMIT 100 או ערך הגיוני אחר בכל שאילתת קריאה כדי למנוע מצב שבו באג בקוד הלקוח ימשוך מיליוני שורות בטעות.

    שלב 3: הרצת EXPLAIN באופן קבוע לבחינת תוכניות השאילתה.

    • לפני שאתם משחררים קוד לייצור (Production), הריצו EXPLAIN QUERY PLAN SELECT ... ב-CLI של Wrangler. השתמשו בפלט כדי לוודא שמופיעה המילה "USING INDEX" ולא "SCAN TABLE".

    שלב 4: שימוש ב-KV כשכבת Caching מעל D1 לשאילתות כבדות וחוזרות.

    • שאילתות מורכבות שמחשבות נתונים סטטיסטיים לא צריכות לרוץ על D1 בכל טעינת דף. שמרו את התוצאה ב-Workers KV (שבו יש 100,000 קריאות חינם ביום) עם תוקף (TTL) של מספר שעות, וקראו משם.
    בניית מחשבון "פיצוץ מגבלות" חינמי שיחשב מתי ה-Isolate שלכם יעלה כסף 25 דקות

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

    1. בדיקה והתקנה של דרישות קדם:

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

      node -v

      הפלט הצפוי הוא גרסה כגון v18.x.x או v20.x.x. אם הפקודה אינה מזוהה, עליכם להוריד ולהתקין את Node.js מהאתר הרשמי שלהם לפני שתמשיכו.

    2. התחברות לחשבון Cloudflare שלכם:

      כדי שתוכלו לעבוד עם הכלים של Cloudflare מהמחשב האישי, בצעו הרשמה וחיבור לחשבון שלכם על ידי הרצת הפקודה:

      npx wrangler login

      פלט צפוי: הטרמינל יפתח חלון דפדפן אינטרנט שמבקש מכם לאשר את החיבור ל-Wrangler. לאחר שתלחצו על "Allow", הטרמינל יציג הודעת הצלחה: Successfully logged in.

    3. יצירת פרויקט חדש והתקנת חבילות:

      הריצו את הפקודות הבאות כדי ליצור תיקייה חדשה,

      R2 Object Storage: אחסון קבצים ללא דמי תעבורה (Zero Egress) וחישוב מחלקות אופרציה A ו-B

      שירות האחסון Cloudflare R2 הוא פתרון אחסון אובייקטים (Object Storage — מערכת לאחסון קבצים לא מובנים כמו תמונות, וידאו, קובצי קול ומסמכים) התואם לחלוטין לפרוטוקול S3 הפופולרי של אמזון. היתרון הגדול ביותר שלו, שמבדיל אותו מכל שירותי הענן המסורתיים כמו AWS S3 או Google Cloud Storage, הוא מודל ה-Zero Egress (אפס עלויות תעבורה יוצאת). בעולם הענן המסורתי, אתם משלמים לא רק על נפח האחסון הפיזי, אלא קנס כבד בכל פעם שמשתמש מוריד קובץ, או כשאתם מעבירים קובץ מהשרת שלכם ליעד אחר באינטרנט. עלויות תעבורה יוצאת אלה (Egress Fees) הן ה"מלכודת" הפיננסית שמונעת פיתוח חופשי של אפליקציות עתירות מדיה. ב-R2, העברת קבצים החוצה לרשת האינטרנט או לתוך ה-Workers שלכם היא חינמית לחלוטין וללא הגבלה, מה שהופך אותו למושלם עבור אפליקציות AI שמייצרות או מעבדות כמויות עצומות של קבצי מדיה, כגון מחוללי תמונות, מערכות המרת קול לטקסט (Transcriptions), או סוכני AI שצריכים לשלוף קבצי PDF גדולים לצורך RAG (Retrieval-Augmented Generation — שיטה להעשרת מודלי שפה במידע חיצוני ייעודי).

      הסוד של R2: מה זה Zero Egress ולמה זה משנה את חוקי המשחק?

      עלויות Egress הן החשבון המפתיע ביותר שמפתחים מקבלים בסוף החודש. נניח שאתם מריצים אפליקציית AI שמייצרת קובצי אודיו מותאמים אישית למשתמשים שלכם, ונפח הקבצים הכולל הוא 100 גיגה-בייט. באמזון S3, האחסון עצמו יעלה לכם דולרים בודדים. אך אם המשתמשים שלכם יקשיבו לקבצים הללו מספר פעמים, ויגרמו להורדה של 1 טרה-בייט (TB) של מידע בחודש, אמזון תגבה מכם כ-90 דולר רק על עצם מעבר המידע מהשרתים שלה אליהם! ב-Cloudflare R2, עלות התעבורה הזו היא $0.00 עגול. אתם משלמים אך ורק על נפח האחסון הפיזי בפועל ($0.015 לגיגה-בייט בחודש לאחר חריגה מ-10 הגיגה-בייט החינמיים שלכם). זה מאפשר לכם לבנות אפליקציות מבלי לחשוש שהצלחה ויראלית של המוצר שלכם תגרור חובות ענק לענקיות הענן.

      חישוב עלויות: ההבדל הקריטי בין Class A ל-Class B Operations

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

      • Class A Operations (פעולות מסוג א'): אלו הן פעולות שמשנות את המצב של האחסון או דורשות משאבים כבדים לניהול המטא-דאטה. הדוגמה הקלאסית היא PutObject (העלאת קובץ חדש או דריסת קובץ קיים), CopyObject (העתקת קובץ בתוך הבאקט), ופעולות של יצירת העלאה בחלקים (Multipart Upload). בנוסף, פקודות כמו ListObjects שמבקשות לקבל רשימה של הקבצים בתיקייה נחשבות גם הן ל-Class A. המסלול החינמי של R2 מעניק לכם 1 מיליון פעולות מסוג זה בחודש, ומעבר לכך העלות היא $4.50 לכל מיליון פעולות נוספות.
      • Class B Operations (פעולות מסוג ב'): אלו הן פעולות קריאה בלבד שאינן משנות דבר במערכת. הדוגמה המרכזית היא GetObject (הורדה או שליפה של קובץ כדי להציגו למשתמש או לעבד אותו ב-Worker) וכן HeadObject (שליפת המידע הטכני על הקובץ, כמו הגודל שלו או תאריך היצירה, מבלי להוריד את הקובץ עצמו). מכיוון שפעולות אלו זולות בהרבה לביצוע, המסלול החינמי נדיב במיוחד ומעניק 10 מיליון פעולות בחודש, ומעבר לכך תשלמו $0.36 בלבד לכל מיליון פעולות.

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

      אחסון בתדירות נמוכה (Infrequent Access) — מתי הוא חוסך כסף?

      החל משנת 2025/2026, Cloudflare מאפשרת להגדיר קבצים תחת קלאס אחסון מיוחד שנקרא Infrequent Access (גישה לא תכופה). במסלול זה, עלות האחסון הפיזי יורדת מ-$0.015 ל-$0.01 בלבד לגיגה-בייט בחודש. אולם, יש לכך מחיר: פעולות Class A ו-Class B עולות כפול (Class A עולה $9.00 למיליון, Class B עולה $0.90 למיליון), ונוספת עלות אחזור (Retrieval Fee) של $0.01 לכל גיגה-בייט של מידע שאתם קוראים. כלל האצבע הוא פשוט: אם יש לכם קבצים שאתם חייבים לשמור מטעמי רגולציה, גיבויים היסטוריים או קובצי ארכיון של שיחות משתמשים ישנות, שהסיכוי שמישהו יבקש לקרוא אותם נמוך מפעם בחודש — העבירו אותם ל-Infrequent Access. לקבצים פעילים שנמצאים בשימוש שוטף של האפליקציה, השאירו אותם תמיד באחסון ה-Standard הרגיל.

      יצירת באקט R2 וחיבורו ל-Worker 10 דקות

      בצעד זה ניצור באקט R2 חדש באמצעות כלי הפיתוח Wrangler, נגדיר עבורו קישור מהיר (Binding) בקובץ הקונפיגורציה, ונכתוב קוד קצר שמדגים העלאה (Class A) וקריאה (Class B) של קבצים.

      1. ראשית, ודאו שאתם מחוברים לחשבון ה-Cloudflare שלכם דרך ה-CLI:

        npx wrangler login
      2. צרו באקט R2 חדש בשם vibe-storage על ידי הרצת הפקודה הבאה במסוף שלכם. הפלט יאשר שהבאקט נוצר בהצלחה באזור הגיאוגרפי הקרוב ביותר אליכם:

        npx wrangler r2 bucket create vibe-storage
      3. פתחו את קובץ ה-wrangler.toml בפרויקט שלכם והוסיפו את הגדרת הקישור הבאה בקצה הקובץ. קישור זה חושף את הבאקט ישירות לקוד ה-Worker תחת המשתנה הגלובלי env.MY_BUCKET:

        [[r2_buckets]]
        binding = "MY_BUCKET"
        bucket_name = "vibe-storage"
      4. החליפו את קוד ה-Worker שלכם בקובץ src/index.ts בקוד הבא, המטפל בבקשות POST להעלאת קבצים ובבקשות GET לשליפתם:

        export default {
          async fetch(request, env) {
            const url = new URL(request.url);
            const key = url.pathname.slice(1); // חילוץ שם הקובץ מנתיב ה-URL
        
            if (!key) {
              return new Response("אנא ספקו שם קובץ בנתיב (למשל: /document.txt)", { status: 400 });
            }
        
            // פעולת Class A: כתיבת אובייקט (PutObject)
            if (request.method === "POST") {
              const body = await request.text();
              await env.MY_BUCKET.put(key, body, {
                headers: { "Content-Type": "text/plain; charset=utf-8" }
              });
              return new Response(`הקובץ ${key} נשמר בהצלחה ב-R2! (בוצעה פעולת Class A)`, { status: 200 });
            }
        
            // פעולת Class B: קריאת אובייקט (GetObject)
            if (request.method === "GET") {
              const object = await env.MY_BUCKET.get(key);
              if (object === null) {
                return new Response("הקובץ המבוקש לא נמצא בבאקט", { status: 404 });
              }
              
              const headers = new Headers();
              object.writeHttpMetadata(headers);
              headers.set("Access-Control-Allow-Origin", "*");
              
              return new Response(object.body, { headers });
            }
        
            return new Response("שיטה לא נתמכת. השתמשו ב-GET לקריאה או POST לכתיבה", { status: 405 });
          }
        };
      5. הפעילו את שרת הפיתוח המקומי שלכם כדי לבדוק שהכל מוגדר כהלכה:

        npx wrangler dev

      פלט נראה לעין שתסיים איתו: לאחר שתריצו את ה-Worker מקומית, תוכלו להשתמש בכלי כמו Postman או Curl כדי לשלוח בקשת POST עם תוכן טקסט כלשהו לנתיב http://localhost:8787/

      Durable Objects: ניהול State עקבי בזמן אמת, SQLite backend ומלכודת ה-GB-Seconds

      בפיתוח אפליקציות מבוססות בינה מלאכותית וכלים בזמן אמת, אנו נתקלים במהירות במה שמכונה "קיר ה-10ms" — מגבלת זמן העיבוד (CPU Time) של השרתים החינמיים. בעוד שרכיבי חישוב ללא שרת (Serverless Compute) סטנדרטיים מצטיינים בפעולות מהירות ונטולות מצב (Stateless), ניהול מצב עקבי בזמן אמת דורש גישה ארכיטקטונית שונה לחלוטין. כאן נכנסים לתמונה Durable Objects (אובייקטים עמידים).

      Durable Objects הם רכיבי קוד ייחודיים בעלי כתובת גלובלית יחידה (Globally Addressable), המשלבים כוח עיבוד יחד עם אחסון קבוע וצמוד. בניגוד ל-Workers רגילים שמתעוררים ונעלמים עם כל בקשת רשת, אובייקט עמיד נשאר דולק בזיכרון כל עוד מתבצעת מולו אינטראקציה. הייחודיות הגדולה ביותר שלו היא הרצת קוד בחוט יחיד (single-threaded execution). מנגנון זה מבטיח כי כל הבקשות המופנות לאובייקט ספציפי יעובדו באופן סדרתי, אחת אחרי השנייה. בכך נמנעות לחלוטין בעיות סנכרון נתונים מורכבות (Race Conditions) ומבוטל הצורך במנגנוני נעילת מסדי נתונים מורכבים (Database Locks). זהו פתרון מושלם לניהול צ'אטים קבוצתיים, משחקי רשת, סנכרון מסמכים בזמן אמת או ניהול זיכרון של סוכני בינה מלאכותית (Agent Memory).

      עליית ה-SQLite Backend ומודל האחסון המקומי

      עד לתחילת שנת 2026, ניהול המצב בתוך Durable Objects התבסס בעיקר על ממשק מפתח-ערך (Key-Value) פשוט ומוגבל. השינוי הדרמטי שהושלם ב-2026 הוא השילוב המובנה של Durable Objects מבוססי SQLite backend. כעת, לכל מופע (

      צינורות העבודה של האדג': Workflows, Queues ו-Cron Triggers לתזמון משימות ב-$0

      // יצירת משימה אסינכרונית עם מזהה ייחודי const taskPayload = { jobId: crypto.randomUUID(), timestamp: Date.now(), action: "PROCESS_AI_DATA" }; // שליחת המשימה לתור לטיפול אסינכרוני ברקע await env.MY_QUEUE.send(taskPayload); console.log(`[Cron Trigger] Successfully pushed job ${taskPayload.jobId} to Queue.`); }, async queue(batch: MessageBatch, env: Env, ctx: ExecutionContext): Promise { console.log(`[Queue Consumer] Received a batch of ${batch.messages.length} messages.`); for (const message of batch.messages) { const payload = message.body; console.log(`[Queue Consumer] Processing Job ID: ${payload.jobId} (Action: ${payload.action})`); // כאן יכולה לבוא לוגיקה כבדה, קריאה ל-APIs או עיבודי נתונים // המתנה סימולטיבית (לא צורכת CPU באדג') await new Promise(resolve => setTimeout(resolve, 500)); // אישור שהודעה טופלה בהצלחה והסרתה מהתור message.ack(); }

      Hyperdrive וסביבת הפיתוח: קישור בסיסי נתונים חיצוניים, בדיקות עם Wrangler וסימולציות

      כשאתם בונים אפליקציות מבוססות בינה מלאכותית, אתם לרוב צריכים לשמור נתוני משתמשים, היסטוריית שיחות או הגדרות מערכת בבסיס נתונים רלציוני חזק כמו PostgreSQL (למשל באמצעות Supabase או Neon). אולם, כאן אתם נתקלים בבעיה ארכיטקטונית קשה המכונה "קיר ה-10ms" ומגבלות החיבורים של שרתים שאינם שרתים פיזיים קבועים (Serverless). קוד ה-Worker שלכם מבוסס על ארכיטקטורת V8 Isolate — סביבת ריצה מבודדת וקלת משקל במיוחד שקמה לתחייה בשבריר מילי-שנייה כדי לענות על בקשה של משתמש, ונסגרת מיד לאחר מכן. בסיסי נתונים מסורתיים, לעומת זאת, לא תוכננו לקצב מהיר כזה של חיבור וניתוק. כל חיבור חדש לבסיס הנתונים דורש תהליך לחיצת יד (TCP/TLS Handshake) שלוקח זמן יקר ומכלה את משאבי הזיכרון של שרת ה-Database שלכם. אם 1,000 משתמשים יפעילו את ה-Worker שלכם בו-זמנית, בסיס הנתונים פשוט יקרוס מעומס חיבורים.

      כדי לפתור את הבעיה הזו בדיוק נוצר **Hyperdrive** — מנהל חיבורים (Database Connection Pooler) ומטמון שאילתות גלובלי המובנה ברשת של Cloudflare. Hyperdrive פועל כגשר חכם: הוא מחזיק בריכת חיבורים פתוחה וקבועה מול בסיס הנתונים המרוחק שלכם. כאשר ה-Worker שלכם מבקש לשלוח שאילתה, הוא לא פותח חיבור חדש מול בסיס הנתונים, אלא משתמש בחיבור קיים ש-Hyperdrive כבר מחזיק עבורו. בנוסף, Hyperdrive מציע יכולת שמירת שאילתות במטמון (Query Caching) ישירות בנקודות הקצה של Cloudflare. שאילתות קריאה (SELECT) זהות לא יגיעו כלל לבסיס הנתונים שלכם, אלא יוחזרו ישירות מהמטמון הקרוב ביותר למשתמש תוך מילי-שניות בודדות.

      חשוב לזכור מגבלה קריטית אחת במסלול החינמי: Cloudflare מאפשרת עד 100,000 שאילתות ביום דרך Hyperdrive בחינם. כל הצהרת SQL נספרת במכסה הזו, כולל שאילתות שנשלפו ישירות מהמטמון ולא הגיעו לבסיס הנתונים שלכם פיזית. באפליקציות עתירות תנועה, המגבלה הזו עשויה להיגמר מהר, ולכן מומלץ לתכנן נכון את תדירות השאילתות או לשדרג למסלול בתשלום המבטל מגבלה זו לחלוטין.

      סביבת פיתוח מקומית ובדיקות עם Wrangler

      כדי לפתח, לבדוק ולנהל את החיבורים הללו, אנחנו משתמשים ב-**Wrangler** — כלי ה-CLI (ממשק שורת הפקודה) הרשמי של Cloudflare. במהלך הפיתוח המקומי, הפקודה wrangler dev מקימה שרת סימולציה מקומי המבוסס על מנוע Miniflare. מנוע זה מדמה את סביבת הריצה של ה-Worker ואת כל הקישורים שלו (Bindings) ל-KV, D1 ו-R2 בצורה מקומית לחלוטין במחשב שלכם. עבור Hyperdrive, סביבת הפיתוח המקומית יודעת לבצע מעקף חכם ולחבר אתכם ישירות לבסיס הנתונים המוגדר לצורך פיתוח, ללא צורך בהקמת שרת פרוקסי מורכב במחשב האישי.

      כאשר אתם צריכים לאבטח מידע רגיש כמו סיסמאות חיבור לבסיס הנתונים, אסור לכתוב אותן ישירות בקוד או בקובץ ההגדרות wrangler.toml שלכם. במקום זאת, נשתמש בפקודה wrangler secret put אשר מצפינה את המפתח ושומרת אותו בצורה מאובטחת בשרתי Cloudflare. בזמן הריצה בייצור, הסוד יהיה זמין ל-Worker שלכם כמשתנה סביבה מוצפן. לבסוף, כדי לראות מה קורה בזמן אמת בייצור ולאתר תקלות חמקמקות שאינן משתחזרות במחשב המקומי, תוכלו להשתמש בפקודה wrangler tail. פקודה זו פותחת ערוץ הזרמת לוגים חי ישירות מכל נקודות הקצה של Cloudflare ברחבי העולם אל תוך מסך המסוף שלכם, כך שכל הודעת שגיאה או console.log יוצגו לכם באופן מיידי.

      חיבור בסיס נתונים חיצוני והרצתו מקומית 15 דקות

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

      תנאים מקדימים: וודאו שמותקן אצלכם Node.js במחשב, שיש לכם חשבון Cloudflare, ושיש ברשותכם מחרוזת חיבור (Connection String) לבסיס נתונים PostgreSQL פעיל (למשל, בסיס נתונים חינמי מ-Neon.tech או Supabase).

      1. פתחו את מסוף הפקודות והתקינו את Wrangler באופן גלובלי (אם עדיין לא מותקן) ובצעו התחברות לחשבון שלכם:
        npm install -g wrangler
        wrangler login

        הדפדפן ייפתח ויבקש מכם לאשר את החיבור לחשבון Cloudflare שלכם. לאחר האישור, המסוף יציג הודעת הצלחה: Successfully logged in.

      2. צרו פרויקט Worker חדש וקל משקל באמצעות כלי הפיגומים הרשמי:
        npm create cloudflare@latest hyperdrive-demo -- --type=simple

        בחרו ב-TypeScript כאשר תישאלו על שפת הפיתוח, ואשרו את יצירת תיקיית הפרויקט.

      3. היכנסו לתיקיית הפרויקט והתקינו את חבילת החיבור לבסיס הנתונים pg ואת הגדרות הטיפוסים שלה עבור TypeScript:
        cd hyperdrive-demo
        npm install pg
        npm install --save-dev @types/pg
      4. כעת, ניצור את חיבור ה-Hyperdrive שלכם מול בסיס הנתונים החיצוני. החליפו את מחרוזת החיבור הפקטיבית במחרוזת האמיתית שלכם:
        wrangler hyperdrive create my-db-link --connection-string="postgres://user:password@ep-cool-pool-1234.eastus.neon.tech/neondb"

        פלט צפוי: המסוף יפלוט אובייקט JSON המכיל מזהה ייחודי ארוך תחת השדה id. שמרו את המזהה הזה בצד, נשתמש בו מיד.

      5. פתחו את הקובץ wrangler.toml שבשורש הפרויקט שלכם, ומחקו את תוכנו. הדביקו במקומו את הגדרות הפרויקט והגדרת ה-Hyperdrive החדש שלכם (החליפו את ה-id במזהה שקיבלתם בשלב הקודם):
        #:schema node_modules/wrangler/config-schema.json
        name = "hyperdrive-demo"
        main = "src/index.ts"
        compatibility_date = "2026-05-01"
        
        [[hyperdrive]]
        binding = "DB"
        id = "מחקו_והדביקו_כאן_את_ה-ID_שקיבלתם"
      6. פתחו את הקובץ src/index.ts והחליפו את כל הקוד הקיים בקוד הבא, המבצע חיבור לבסיס הנתונים דרך Hyperdrive ושולף את השעה הנוכחית בשרת:
        import { Client } from 'pg';
        
        export interface Env {
            DB: Hyperdrive;
        }
        
        export default {
            async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
                // Hyperdrive מספק לנו את מחרוזת החיבור המותאמת שלו דרך ה-Binding
                const client = new Client({ connectionString: env.DB.connectionString });
                
                try {
                    await client.connect();
                    const result = await client.query('SELECT NOW() as current_time;');
                    
                    return new Response(JSON.stringify({
                        success: true,
                        database_time: result.rows[0].current_time,
                        message: "Connected to PostgreSQL via Hyperdrive successfully!"
                    }), {
                        headers: { 'Content-Type': 'application/json' }
                    });
                } catch (err: any) {
                    return new Response(JSON.stringify({ 
                        success: false, 
                        error: err.message 
                    }), {
                        status: 500,
                        headers: { 'Content-Type': 'application/json' }
                    });
                } finally {
                    // סגירת החיבור בצורה אסינכרונית לאחר החזרת התגובה למשתמש
                    ctx.waitUntil(client.end());
                }
            },
        };
      7. הריצו את שרת הפיתוח המקומי שלכם לצורך בדיקה וסימולציה:
        wrangler dev

        פלט צפוי: השרת המקומי יתחיל לרוץ ויציג לכם כתובת מקומית, לרוב http://localhost:8787. פתחו את הדפדפן וגשו לכתובת זו.

      פלט נראה לעין שתסיים איתו: דף דפדפן המציג קובץ JSON תקין עם השעה המדויקת של שרת בסיס הנתונים שלכם: {"success":true,"database_time":"2026-05-29T14:32:01.123Z","message":"Connected to PostgreSQL via Hyperdrive successfully!"}. זהו אישור מוחלט לכך שהסימולציה המקומית הצליחה לפנות בצורה תקינה אל ה-Database החיצוני דרך הגדרות ה-Wrangler שלכם.

      שגרת עבודה
      תדירות פעולה
      לפני כל פריסה (Deploy) הרצת wrangler dev --remote כדי לוודא שכל ה-bindings (קישורים לשירותי הענן) והשירותים האמיתיים מתנהגים כמצופה בענן, ולא רק בסימולציה המקומית של המחשב שלכם.
      בסיום פיתוח פיצ'ר הרצת wrangler tail ובדיקת הלוגים בזמן אמת כדי לוודא שאין חריגה ממגבלת ה-10ms CPU (זמן עיבוד המעבד) ושאין שגיאות נסתרות ב-Subrequests (בקשות משנה).
      פעם בשבוע כניסה ל-Cloudflare Dashboard ובדיקת ה-Metrics (מדדים) של ה-Storage. מעקב אחרי קריאות D1, כתיבות KV וצריכת זיכרון של Durable Objects כדי לזהות חריגות תקציב מוקדם ככל הניתן.
      לפני מעבר ל-Production העברת כל מפתחות ה-API, הסיסמאות והטוקנים משרת הפיתוח למערכת הניהול המאובטחת של Cloudflare בעזרת wrangler secret put.
      דבר אחד שכדאי לעשות עכשיו

      הפעולה היחידה בעלת המינוף הגבוה ביותר למניעת קריסת האפליקציה שלכם תחת עומס היא ייעול שאילתות ה-D1 (בסיס הנתונים שלכם). פתחו את קוד ה-Worker שלכם וודאו שאין בו אף שאילתת SELECT ללא מגבלת LIMIT או תנאי WHERE מוגדר היטב. הוספת אינדקס (Index - מפתח ייחוס לחיפוש מהיר) פשוט על עמודות חיפוש נפוצות תציל אתכם מלהגיע למגבלת ה-5 מיליון שורות שנקראו (Read Rows) בתוך יום אחד.

      בדוק את עצמך — 5 שאלות
      1. מדוע Worker יכול לבצע קריאת fetch חיצונית שלוקחת 2 שניות, ועדיין לא לחרוג ממגבלת ה-10ms CPU של המסלול החינמי? (רמז: חשבו על ההבדל בין זמן ביצוע מעבד - CPU Execution Time, לבין זמן המתנה אסינכרוני - Wait Time שבו ה-Isolate פנוי לעבודות אחרות).
      2. אפליקציית הצ'אט שלכם משתמשת ב-KV כדי לשמור הודעות חדשות של משתמשים (כתיבה בכל הודעה). למה זו החלטה ארכיטקטונית שתגרום לחסימת האפליקציה במהירות? (רמז: זכרו את מגבלת ה-1,000 פעולות כתיבה ביום במסלול החינמי של KV לעומת העלות שלו).
      3. הרצתם שאילתת SELECT * FROM users WHERE active = 1 על טבלת D1 המכילה 50,000 שורות, והחזרתם רק 10 שורות. כמה שורות נקראו (Rows Read) בפועל מבחינת מכסת החיוב של Cloudflare? (רמז: ללא אינדקס מוגדר על עמודת active, בסיס הנתונים נאלץ לבצע סריקה מלאה - Full Table Scan).
      4. מהו רכיב ה-Storage היחיד ב-Cloudflare Workers שמאפשר עקביות חזקה בזמן אמת (Strong Consistency) ומונע מצב שבו שתי בקשות ממקומות שונים בעולם מעדכנות את אותו משתנה בו-זמנית? (רמז: חפשו את הרכיב שמנתב את כל הבקשות לכתובת גלובלית יחידה ומריץ תהליך חד-חוטי - Single-threaded).
      5. מדוע פיתוח ובדיקות מקומיות בלבד באמצעות הסימולטור המקומי (Miniflare) עלולים להטעות אתכם רגע לפני העלייה לאוויר? (רמז: חשבו על הבדלים בגישה ל-Secrets אמיתיים, זמני תגובה גלובליים, והתנהגות של Eventual Consistency ב-KV בענן לעומת המחשב האישי).
      סיכום הפרק

      בפרק זה העמקנו אל מאחורי הקלעים של מודל ה-Isolate של Cloudflare Workers, והבנו כיצד ארכיטקטורה ללא שרת (Serverless) המסוגלת לפעול בתוך פחות ממילישנייה אחת, משנה את כללי המשחק של מהירות האפליקציה. למדנו לתמרן בין מגבלת ה-10ms CPU הקשוחה לבין ביצוע Subrequests חכמים במקביל (Promise.all), וראינו כיצד לבנות ארכיטקטורת נתונים נכונה באמצעות כלי האחסון השונים של Cloudflare.

      ניתחנו את ההבדלים הקריטיים בין KV המיועד לקריאות מהירות ואינטנסיביות, D1 המביא את כוחו של ה-SQL היחסתי לקצה, R2 שמבטל את עלויות תעבורת הנתונים (Egress Fees), ו-Durable Objects המאפשרים ניהול מצב (State) מורכב בזמן אמת. בעזרת מחשבון המגבלות שניתחנו וקובץ ה-wrangler.toml המאוחד, יש לכם כעת תבנית עבודה מלאה לבניית אפליקציות חסינות שברור לכם בדיוק מתי הן ידרשו מעבר למסלול בתשלום.

      כעת, כשיש לנו שרת קצה מהיר במיוחד ובסיס נתונים חזק ומבוזר שמגבה אותו, אנחנו מוכנים להוסיף את המוח של המערכת. בפרק הבא נלמד לחבר את ה-Worker שלמדנו לייצר אל מודלי בינה מלאכותית (Workers AI) המופעלים ישירות באדג' ונלמד כיצד לחשב את צריכת ה-Neurons (נוירונים) שלנו כדי להריץ מודלי שפה, יצירת תמונות וסיווג טקסט בעלות אפסית ובמהירות שיא.

      צ'קליסט — סיכום

      • הבנתי את מודל ה-Isolate והסיבות לכך שאין בו Cold Starts (אתחול קר) או גישה ישירה למערכת הקבצים המקומית.
      • הגדרתי את קובץ ה-wrangler.toml עם 4 bindings פעילים עבור KV, D1, R2 ו-Durable Objects.
      • הגבלתי את זמן עיבוד ה-CPU של ה-Worker על ידי הימנעות מלולאות ארוכות ועיבודי regex כבדים ללא צורך.
      • החלפתי קריאות שרשרת (Cascade Fetch) בקריאות מקביליות באמצעות Promise.all כדי למנוע חריגה ממכסת ה-Subrequests.
      • יצרתי Namespace של KV והגדרתי אותו כראוי לשימוש עבור נתונים בעלי קצב קריאה גבוה וכתיבה נמוכה (Read-heavy).
      • הקמתי בסיס נתונים D1 ב-Cloudflare וקישרתי אותו ל-Worker שלי לצורך שאילתות SQL יחסיות בקצה.
      • הוספתי אינדקסים (Indexes) לעמודות החיפוש ב-D1 כדי למנוע סריקות טבלה מלאות (Full-table scans) שמבזבזות את מכסת ה-Rows Read.
      • הגדרתי Bucket (מאגר) של R2 לאחסון קבצים ומדיה גדולים כדי ליהנות מאפס עלויות Egress (תעבורה יוצאת).
      • מימשתי Durable Object עבור רכיב הדורש סנכרון בזמן אמת ועקביות נתונים חזקה (Strong Consistency).
      • העברתי את כל מפתחות ה-API והסודות הרגישים של האפליקציה למערכת ה-Secrets המאובטחת באמצעות wrangler secret put.
      • בדקתי את הקוד בענן האמיתי באמצעות פקודת wrangler dev --remote כדי לוודא שאין פערים מול הסימולציה המקומית.
      • הרצתי wrangler tail כדי לנטר את השרת בזמן אמת ולאתר שגיאות חריגה של 10ms CPU במהלך השימוש באפליקציה.
      תרגיל מעשי: הקמת ה-KV הראשון שלכם ב-Cloudflare Workers 15 דקות

      בתרגיל זה נקים סביבת פיתוח מקומית של Cloudflare Workers, נחבר אותה למסד נתונים מסוג KV (מפתח-ערך המותאם לקריאה מהירה במיוחד), נכתוב קוד שקורא וכותב אליו, ונריץ בדיקה מקומית.

      1. התקנת הכלים והתחברות לחשבון Cloudflare:

        פתחו את הטרמינל (Terminal) והריצו את פקודת ההתקנה וההתחברות. אם אין לכם חשבון Cloudflare עדיין, הפקודה תנחה אתכם לפתוח אחד בחינם.

        npx wrangler login

        הפלט הצפוי: דפדפן ייפתח ויבקש מכם לאשר את החיבור לחשבון שלכם. לאחר האישור, בטרמינל תופיע ההודעה: Successfully logged in to Cloudflare CLI!

      2. יצירת פרויקט Worker חדש:

        נשתמש בכלי ה-CLI של Cloudflare שנקרא Wrangler כדי לייצר פרויקט חדש בשם vibe-kv-store:

        npm create cloudflare@latest vibe-kv-store -- --framework=none --type=common --js

        במהלך ההתקנה תשאלו האם להשתמש ב-Git (בחרו Yes) והאם לעשות Deployment (בחרו No, נעשה זאת בהמשך).

        הפלט הצפוי: תיקייה חדשה תיווצר עם קבצי הבסיס של ה-Worker, ובטרמינל תופיע ההודעה: Success: Created project vibe-kv-store.

      3. כניסה לתיקיית הפרויקט ויצירת בסיס הנתונים (KV Namespace) המקומי:

        נעבור לתיקייה שיצרנו ונריץ פקודה ליצירת מרחב שמות (Namespace) עבור ה-KV שלנו:

        cd vibe-kv-store
        npx wrangler kv:namespace create MY_KV

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

        [[kv_namespaces]]
        binding = "MY_KV"
        id = "xxxx_your_kv_id_xxxx"
      4. עדכון קובץ ההגדרות wranger.json:

        פתחו את הקובץ wrangler.json (או wrangler.toml אם נוצר) בתיקיית השורש של הפרויקט, והחליפו את תוכנו בקוד הבא כדי לחבר את ה-Worker ל-KV שיצרנו:

        {
          "name": "vibe-kv-store",
          "main": "src/index.js",
          "compatibility_date": "2024-01-01",
          "kv_namespaces": [
            {
              "binding": "MY_KV",
              "id": "LOCAL_DEV_ID"
            }
          ]
        }

        הפלט הצפוי: שמירת הקובץ תגדיר ל-Worker שקיים לו משתנה גלובלי (Binding) בשם MY_KV שדרכו ניתן לפנות ל-Storage.

      5. כתיבת קוד ה-Worker (קריאה וכתיבה מ-KV):

        פתחו את הקובץ src/index.js והחליפו את כל תוכנו בקוד הבא. הקוד בודק אם קיים מפתח בשם visits ב-KV. אם לא, הוא מייצר אותו עם ערך 1. אם כן, הוא מעלה את הערך ב-1 ומחזיר את התוצאה למשתמש:

        export default {
          async fetch(request, env, ctx) {
            // קריאת הערך הנוכחי מה-KV באמצעות ה-Binding שהגדרנו
            let visits = await env.MY_KV.get("visits");
            
            if (visits === null) {
              visits = 1;
            } else {
              visits = parseInt(visits) + 1;
            }
            
            // שמירת הערך המעודכן בחזרה ל-KV
            await env.MY_KV.put("visits", visits.toString());
            
            return new Response(`שלום Vibe Coder! ביקרת כאן ${visits} פעמים.`, {
              headers: { "content-type": "text/plain; charset=utf-8" },
            });
          },
        };
      6. הרצת שרת הפיתוח המקומי ובדיקה:

        נריץ את ה-Worker במצב פיתוח מקומי (Local Development Mode) המדמה את סביבת הענן של Cloudflare:

        npx wrangler dev

        הפלט הצפוי: הטרמינל יציג הודעה שהשרת רץ בכתובת המקומית: http://localhost:8787. לחצו על הקישור או פתחו אותו בדפדפן. ברענון הראשון תראו את ההודעה "שלום Vibe Coder! ביקרת כאן 1 פעמים.". בכל רענון נוסף (F5), מספר הביקורים יעלה בזמן אמת מתחת ל-10ms בזכות השימוש ב-KV המקומי!

      פלט נראה לעין שתסיים איתו: דפדפן פתוח בכתובת http://localhost:8787 המציג את הטקסט הדינמי המבוסס על הנתונים שנשמרו ב-KV שלכם: "שלום Vibe Coder! ביקרת כאן 5 פעמים." לאחר 5 רענונים של העמוד.

      טעות נפוצה: שימוש ב-KV עבור נתונים דינמיים וביצוע פילטורים כבדים בקוד

      למה זה מפתה: שירות Cloudflare KV (שירות אחסון מפתח-ערך מהיר במיוחד - Key-Value Store) נראה כמו הדרך הכי קלה וזולה לשמור מידע של האפליקציה שלכם. קל פשוט לזרוק לשם אובייקטים גדולים של JSON, וכשצריך לחפש או לסנן נתונים, פשוט למשוך את הכל לתוך ה-Worker ולתת לקוד JavaScript (שג'נרטור ה-AI כתב לכם בשניות) לבצע לופים וסינונים בזיכרון.

      למה זה טעות: הגישה הזו תתנגש במהירות ב"קיר ה-10ms" — מגבלת זמן ה-CPU (זמן מעבד פעיל, לא כולל המתנה לרשת) הנוקשה של Cloudflare Workers במסלול החינמי. עיבוד נתונים, סינון מערכים גדולים או פענוח JSON כבד בתוך קוד ה-Worker ישרפו את ה-10ms הללו ברגע, ויגרמו ל-Worker שלכם לקרוס עם שגיאת מערכת (Error 1102). בנוסף, ל-KV יש תכונה שנקראת Eventual Consistency (עקביות בסופו של דבר), מה שאומר ששינוי שכתבתם לא יופיע מיד בכל השרתים בעולם, ומשתמשים עלולים לקבל מידע ישן ולא מעודכן.

      מה לעשות במקום: התאימו את כלי האחסון לסוג המשימה. אם אתם צריכים לבצע שאילתות, סינונים וחיפושים, השתמשו ב-D1 (מסד הנתונים היחסי מבוסס SQL של Cloudflare) — הוא יבצע את הסינון בצד השרת ויחזיר ל-Worker רק את השורות המדויקות, בלי לבזבז זמן CPU יקר. השתמשו ב-KV אך ורק לנתונים סטטיים יחסית שנקראים המון ונכתבים לעיתים רחוקות מאוד (כמו הגדרות מערכת או תרגומי שפות).