מדוע בדיקות עומס נכשלות גם לאחר שחרור מוצלח?
השחרור עבר חלק. שלושה ימים לאחר מכן — מבצע, התנועה מזנקת פי 10. ארבע שעות לאחר תחילת המבצע, מסד הנתונים מגיע ל-lock wait timeout, ה-API מחזיר 504, והתמיכה טובעת בכרטיסים. ובכל זאת, בדיקת העומס בסביבת הבדיקות עברה "בהצלחה" — כי בדקו עם 50 משתמשים, בעוד שהתנועה האמיתית הביאה 5000. התרחיש הזה חוזר על עצמו בכל פרויקט שני שמגיע אלינו. אנו רואים את אותן טעויות: בדיקות נכתבות לתרחיש יחיד, שאילתות איטיות על פספוס מטמון לא נשקלות, והתנהגות המערכת תחת כשלים של שירותים תלויים זוכה להתעלמות מוחלטת. בדיקת עומס אינה תיבת סימון לפני שחרור. זהו התהליך של מציאת הנקודה שבה המערכת מתחילה להתדרדר והבנה למה דווקא שם. המהנדסים שלנו עם 10 שנות ניסיון בבדיקות ביצועים מבטיחים שאחרי העבודה איתנו, תכירו את גבול המערכת שלכם.
איך לכתוב סקריפטים של k6 מפשוטים לרמת production?
k6 הוא הכלי הנוח ביותר לכתיבת בדיקות עומס כקוד. תחביר JavaScript, מדדים מובנים, אינטגרציה טבעית עם Grafana דרך k6 cloud או xk6-output-prometheus-remote.
סקריפט בסיסי פותר משימה פשוטה. בעיות אמיתיות מתחילות כשצריך לדמות תרחישי משתמש מורכבים.
דוגמה: בדיקה עם אימות ובקשות תלויות:
import http from 'k6/http'; import { check, sleep } from 'k6'; import { SharedArray } from 'k6/data'; const users = new SharedArray('users', function () { return JSON.parse(open('./users.json')); }); export const options = { scenarios: { warm_up: { executor: 'ramping-vus', startVUs: 0, stages: [ { duration: '2m', target: 100 }, { duration: '5m', target: 100 }, { duration: '2m', target: 0 }, ], }, spike: { executor: 'ramping-arrival-rate', startRate: 0, timeUnit: '1s', preAllocatedVUs: 500, stages: [ { duration: '30s', target: 500 }, { duration: '1m', target: 500 }, { duration: '30s', target: 0 }, ], startTime: '9m', }, }, thresholds: { http_req_duration: ['p(95)<500', 'p(99)<1500'], http_req_failed: ['rate<0.01'], }, }; export default function () { const user = users[Math.floor(Math.random() * users.length)]; const loginRes = http.post(`${__ENV.BASE_URL}/api/auth/login`, JSON.stringify({ email: user.email, password: user.password, }), { headers: { 'Content-Type': 'application/json' } }); check(loginRes, { 'login 200': (r) => r.status === 200 }); const token = loginRes.json('access_token'); if (!token) return; const headers = { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }; const catalogRes = http.get(`${__ENV.BASE_URL}/api/catalog?page=1`, { headers }); check(catalogRes, { 'catalog 200': (r) => r.status === 200 }); sleep(Math.random() * 2 + 1); } כמה פרטים חשובים בסקריפט הזה. import http from 'k6/http'; import { check, sleep } from 'k6'; import { SharedArray } from 'k6/data'; const users = new SharedArray('users', function () { return JSON.parse(open('./users.json')); }); export const options = { scenarios: { warm_up: { executor: 'ramping-vus', startVUs: 0, stages: [ { duration: '2m', target: 100 }, { duration: '5m', target: 100 }, { duration: '2m', target: 0 }, ], }, spike: { executor: 'ramping-arrival-rate', startRate: 0, timeUnit: '1s', preAllocatedVUs: 500, stages: [ { duration: '30s', target: 500 }, { duration: '1m', target: 500 }, { duration: '30s', target: 0 }, ], startTime: '9m', }, }, thresholds: { http_req_duration: ['p(95)<500', 'p(99)<1500'], http_req_failed: ['rate<0.01'], }, }; export default function () { const user = users[Math.floor(Math.random() * users.length)]; const loginRes = http.post(`${__ENV.BASE_URL}/api/auth/login`, JSON.stringify({ email: user.email, password: user.password, }), { headers: { 'Content-Type': 'application/json' } }); check(loginRes, { 'login 200': (r) => r.status === 200 }); const token = loginRes.json('access_token'); if (!token) return; const headers = { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }; const catalogRes = http.get(`${__ENV.BASE_URL}/api/catalog?page=1`, { headers }); check(catalogRes, { 'catalog 200': (r) => r.status === 200 }); sleep(Math.random() * 2 + 1); } טוען נתונים פעם אחת עבור כל ה-VUs במקום להעתיק לכל אחד — עם 500 VUs וקובץ של 10,000 שורות, ההבדל בזיכרון משמעותי. SharedArray מדמה עומס אמיתי במונחי RPS, לא מספר משתמשים — זה חשוב: 100 VUs עם ramping-arrival-rate נותנים ~100 RPS, אבל 100 VUs ללא sleep יכולים לתת אלפים, מה שלא משקף התנהגות אמיתית.
Thresholds הם כלי ה-pass/fail המרכזי. sleep(1) אומר ש-95% מהבקשות חייבות להסתיים תוך 500ms. אם ה-threshold לא מתקיים, p(95)<500 יוצא עם קוד 1 — צינור ה-CI יכול לתפוס את זה.
k6 — הזרמת מדדים בזמן אמת. בשילוב עם דשבורד Grafana (ID 2587 מ-grafana.com), מקבלים תצוגה חיה של הבדיקה: RPS, אחוזוני השהיה, ספירת שגיאות, VUs פעילים.
מה לבחור: JMeter, Gatling או k6?
| כלי | שפת סקריפטים | צריכת משאבים | קנה מידה | קהילה |
|---|---|---|---|---|
| k6 | JavaScript | נמוכה (מבוסס Go) | מובנה, Kubernetes-native | פעילה, קוד פתוח |
| JMeter | XML/Java GUI | גבוהה (threads של Java) | מבוזר (jmeter-server) | גדולה, ארגונית |
| Gatling | Scala DSL | בינונית (JVM, אבל יעיל יותר מ-JMeter) | מבוזר (injectors) | צומחת, Java/Scala |
בחירת הכלי נקבעת לעיתים קרובות לא לפי מאפיינים טכניים, אלא לפי מי כותב את הבדיקות: מפתחים רגילים לקוד (k6/Gatling), צוותי QA ל-GUI (JMeter). k6 צורך בערך פי 5 פחות זיכרון מ-JMeter עם אותו מספר משתמשים וירטואליים, מה שהופך אותו לבחירה הטובה ביותר לבדיקות רציפות ב-CI/CD.
Chaos Engineering: שברו בצורה מבוקרת כדי להימנע מכשלים בלתי צפויים
Chaos Engineering הוא השלב הבא אחרי בדיקות עומס. בדיקת עומס בודקת שהמערכת מתמודדת עם תנועה. Chaos בודק שהמערכת מתדרדרת כראוי כשרכיבים נכשלים.
Chaos Engineering — הכלי הבוגר ביותר עבור Kubernetes. מותקן כ-CRD ומנוהל דרך YAML או Web UI.
תרחיש: מה קורה כשאחת משלוש מופעי מסד הנתונים מפסיקה להגיב?
apiVersion: chaos-mesh.org/v1alpha1 kind: NetworkChaos metadata: name: db-network-delay namespace: production spec: action: delay mode: one selector: namespaces: - production labelSelectors: app: postgresql delay: latency: "200ms" correlation: "25" jitter: "50ms" duration: "10m" scheduler: cron: "@every 1h" המניפסט הזה מוסיף עיכוב רשת של 200ms ל-pod אקראי אחד עם התווית k6 run --out influxdb=http://localhost:8086/k6 script.js פעם בשעה למשך 10 דקות. הריצו אותו במקביל לבדיקת עומס — ראו כיצד apiVersion: chaos-mesh.org/v1alpha1 kind: NetworkChaos metadata: name: db-network-delay namespace: production spec: action: delay mode: one selector: namespaces: - production labelSelectors: app: postgresql delay: latency: "200ms" correlation: "25" jitter: "50ms" duration: "10m" scheduler: cron: "@every 1h" השהיה של האפליקציה משתנה.
PodChaos לבדיקת עמידות מול אתחולים:
apiVersion: chaos-mesh.org/v1alpha1 kind: PodChaos metadata: name: api-pod-kill spec: action: pod-kill mode: fixed-percent value: "33" selector: namespaces: [production] labelSelectors: app: api-service gracePeriod: 0 duration: "5m" 33% מ-pods של שירות ה-api נהרגים מיידית (ללא כיבוי מסודר) פעם אחת לכל ריצה. תוצאה צפויה: מדדי השגיאות עולים זמנית, אבל Kubernetes משחזר את ה-pods, ואחרי 30–60 שניות השגיאות נעלמות. אם השגיאות נמשכות, יש בעיה עם readiness probes או deployment.
עקרון GameDay: ניסויי Chaos צריכים להתבצע בשעות העבודה עם נוכחות הצוות, לא לבד בלילה. המטרה היא לא לשבור את ה-production, אלא לבנות זיכרון שרירי לתגובה לאירועים.
מה לבדוק קודם עם Chaos Engineering?
- כשל של אזור זמינות אחד (NetworkChaos על כל ה-namespace של AZ אחד)
- מיצוי זיכרון של שירות קריטי (StressChaos עם
app: postgresql) - השהיה ל-API חיצוני (HTTPChaos עם עיכוב בבקשות יוצאות)
- דיסק מלא (IOChaos + קונטיינר נוסף שכותב זבל)
Chaos Engineering אינו על לשבור הכל. זה על לדעת מראש מה בדיוק ישבר ובאיזה סדר. המהנדסים המוסמכים שלנו ביצעו יותר מ-50 ניסויים כאלה על פרויקטים ב-production.
איך לפרש תוצאות של בדיקת עומס?
הבדיקה הסתיימה, המספרים זמינים. לאחר הסיום, אנו מנתחים שלושה תרחישים מרכזיים.
p(99) הוא תקין, אבל apiVersion: chaos-mesh.org/v1alpha1 kind: PodChaos metadata: name: api-pod-kill spec: action: pod-kill mode: fixed-percent value: "33" selector: namespaces: [production] labelSelectors: app: api-service gracePeriod: 0 duration: "5m" הוא קטסטרופלי. זה מצביע על התפלגות זנב — בקשות שפוגעות בנתיב איטי. לעיתים קרובות זה פספוס מטמון, סריקת טבלה מלאה עם פרמטרים מסוימים, או הפסקת GC ב-JVM.
שגיאות memoryWorkers: 4, memoryBytes: "1GB" תחת עומס — מגבלת החיבורים מוצתה. בדקו p(50) על השרת, p(99), הגדרות connection reset by peer ב-Nginx/HAProxy, גודל ה-pool במאגר החיבורים של האפליקציה.
העומס גדל ליניארית, השהיה יציבה עד 60% CPU, ואז קופצת בחדות — סימן קלאסי לצוואר בקבוק מבוסס CPU. פרופיילר (async-profiler ל-JVM, ulimit -n ל-Python) יראה מה צורך את ה-CPU. אבחון כזה מפחית את עלויות תשתית הענן בממוצע פי 2 עבור הלקוחות שלנו.
מה כלול בבדיקת עומס?
- ניתוח ארכיטקטורה ותרחישים עסקיים (זיהוי נקודות קצה קריטיות)
- כתיבת סקריפטים וניפוי שגיאות (k6/JMeter/Gatling)
- בדיקת עשן, בדיקת עומס, בדיקת ספייק, בדיקת סאוק
- ניסויי Chaos (אם נדרש)
- דוח מפורט עם גרפים: אחוזוני השהיה, RPS, שגיאות, רכיבי צוואר בקבוק
- המלצות אופטימיזציה (configs, תשתית, קוד)
- אינטגרציה של בדיקות לתוך ה-CI/CD שלכם (GitLab CI / GitHub Actions)
- תמיכה לאחר שחרור: מדידות production
איך לשלב בדיקות ביצועים ב-CI/CD?
בדיקות ביצועים רציפות מבטיחות שכל שינוי קוד מוערך לגבי הידרדרות ביצועים. אנו עוזרים לשלב בדיקות k6 או Gatling בצינור שלכם, תוך השוואה אוטומטית של תוצאות מול קווי בסיס ו-thresholds. זה תופס רגרסיות לפני שהן מגיעות ל-production.
לוחות זמנים ופורמט
| סוג בדיקה | היקף | משך |
|---|---|---|
| בדיקת עשן + עומס | 1–3 תרחישים, שירות יחיד | 1–2 ימים |
| בדיקת עומס מלאה | 10+ תרחישים, מיקרוסרביסים | 1–2 שבועות |
| Chaos Engineering | Kubernetes, 5–10 ניסויים | 1–3 שבועות |
| בדיקות ביצועים רציפות | אינטגרציה ל-CI/CD | 2–4 שבועות |
התמחור מחושב באופן אישי לאחר ניתוח הארכיטקטורה ויעדי הבדיקה. קבלו הערכת פרויקט — צרו קשר לייעוץ. הזמינו בדיקת עומס turnkey והבטיחו זיהוי צווארי בקבוק לפני שה-production מוצא אותם.







