בדיקות עומס רציפות בצנרת CI/CD
עם כל פריסה לייצור, הסיכון להחדרת רגרסיית ביצועים גבוה. שאילתת SQL אחת לא אופטימלית או בעיית N+1 שנשכחה יכולה להגדיל את זמן התגובה ב-50%, ומשתמשים יעברו למתחרים. אנו משלבים בדיקות עומס אוטומטיות ישירות בצנרת ה-CI/CD שלך כדי לתפוס רגרסיות כאלה לפני שהן מגיעות ל-master. התוצאה: אתה בטוח שכל commit לא שובר את ה-SLA עבור זמן אחזור ותפוקה.
אנו מגדירים בדיקות עשן עם ערכי סף שהופכים את הצנרת לשומר סף: אם זמן האחזור p95 חורג מ-500ms או שיעור השגיאות חורג מ-1%, הצנרת נכשלת והמפתח מקבל הודעה. זו לא תחליף לבדיקות עומס בקנה מידה מלא, אלא אמצעי הגנה מהיר.
בעיות שאנו פותרים
- רגרסיית ביצועים לאחר פריסה — אפילו שינוי מיקרו בקוד יכול להאט נקודת קצה קריטית. ללא בדיקות אוטומטיות, אתה לומד על הבעיה רק לאחר תלונות משתמשים או התראות ניטור. אנו מגדירים השוואת בסיס: כל בדיקה רצה מול הענף היציב, ואם הסטייה >20%, הצנרת נחסמת.
- שאילתות לא יעילות וצווארי בקבוק — הסקריפטים שלנו מחקים תרחישי משתמש טיפוסיים (רשימה, יצירת פוסט, חיפוש). אם שאילתה מתחילה לקחת יותר זמן, אנו רואים זאת בתרשימי המדדים.
- חוסר תרבות של ביצועים תחילה — מפתחים לעתים קרובות לא חושבים על ביצועים במהלך סקירת קוד. בדיקות עומס רציפות הופכות את הביצועים לגלויים: כל PR מלווה בתגובה עם תוצאות בדיקה (p95, שיעור שגיאות).
כלים ומקומם ב-CI
k6 הוא הבחירה הטובה ביותר עבור CI: סקריפטים ב-JS, סטטיסטיקות מובנות, pass/fail מבוסס סף, אינטגרציה טבעית עם GitHub Actions ו-GitLab CI. עוד בתיעוד k6.
Artillery — תצורת YAML, נוחה לתיאור תרחישים ללא קוד. Gatling — Scala/Java, דוחות HTML מפורטים, נוחה לצוותי Java.
סקריפט k6 בסיסי
// tests/performance/api-smoke.js
import http from 'k6/http'
import { check, sleep } from 'k6'
import { Rate, Trend } from 'k6/metrics'
// Кастомные метрики
const errorRate = new Rate('errors')
const postCreateDuration = new Trend('post_create_duration')
export const options = {
// Профиль нагрузки для CI: быстро, не разрушительно
stages: [
{ duration: '30s', target: 10 }, // разогрев
{ duration: '1m', target: 10 }, // устойчивая нагрузка
{ duration: '10s', target: 0 }, // остывание
],
// Пайплайн сломается если порог не достигнут
thresholds: {
http_req_duration: [
'p(95)<500', // p95 < 500мс
'p(99)<1000', // p99 < 1000мс
],
errors: ['rate<0.01'], // ошибок < 1%
http_req_failed: ['rate<0.01'], // HTTP ошибок < 1%
post_create_duration: ['p(95)<800'],
}
}
const BASE_URL = __ENV.BASE_URL || 'http://localhost:3000'
const AUTH_TOKEN = __ENV.AUTH_TOKEN
export function setup() {
// Один раз: получить токен или подготовить данные
const res = http.post(`${BASE_URL}/api/auth/login`, JSON.stringify({
email: '[email protected]',
password: 'testpassword'
}), {
headers: { 'Content-Type': 'application/json' }
})
return { token: res.json('token') }
}
export default function(data) {
const headers = {
'Content-Type': 'application/json',
'Authorization': `Bearer ${data.token || AUTH_TOKEN}`
}
// Сценарий 1: список постов (70% трафика)
const postsList = http.get(`${BASE_URL}/api/posts?limit=20`, { headers })
check(postsList, {
'posts list: status 200': (r) => r.status === 200,
'posts list: has items': (r) => r.json('data').length > 0
})
errorRate.add(postsList.status !== 200)
sleep(Math.random() * 0.5) // случайная пауза 0-500мс
// Сценарий 2: создание поста (20% трафика)
if (Math.random() < 0.2) {
const start = Date.now()
const createPost = http.post(`${BASE_URL}/api/posts`, JSON.stringify({
title: `Test post ${Date.now()}`,
content: 'Load test content'
}), { headers })
postCreateDuration.add(Date.now() - start)
check(createPost, {
'create post: status 201': (r) => r.status === 201,
})
errorRate.add(createPost.status !== 201)
}
sleep(0.3)
} כיצד להגדיר ספי ביצועים ב-k6?
ספים הם המנגנון המרכזי לכשל אוטומטי של הצנרת. באפשרויות הסקריפט, אנו מגדירים גבולות מקובלים: לדוגמה, // tests/performance/api-smoke.js import http from 'k6/http' import { check, sleep } from 'k6' import { Rate, Trend } from 'k6/metrics' // Кастомные метрики const errorRate = new Rate('errors') const postCreateDuration = new Trend('post_create_duration') export const options = { // Профиль нагрузки для CI: быстро, не разрушительно stages: [ { duration: '30s', target: 10 }, // разогрев { duration: '1m', target: 10 }, // устойчивая нагрузка { duration: '10s', target: 0 }, // остывание ], // Пайплайн сломается если порог не достигнут thresholds: { http_req_duration: [ 'p(95)<500', // p95 < 500мс 'p(99)<1000', // p99 < 1000мс ], errors: ['rate<0.01'], // ошибок < 1% http_req_failed: ['rate<0.01'], // HTTP ошибок < 1% post_create_duration: ['p(95)<800'], } } const BASE_URL = __ENV.BASE_URL || 'http://localhost:3000' const AUTH_TOKEN = __ENV.AUTH_TOKEN export function setup() { // Один раз: получить токен или подготовить данные const res = http.post(`${BASE_URL}/api/auth/login`, JSON.stringify({ email: '[email protected]', password: 'testpassword' }), { headers: { 'Content-Type': 'application/json' } }) return { token: res.json('token') } } export default function(data) { const headers = { 'Content-Type': 'application/json', 'Authorization': `Bearer ${data.token || AUTH_TOKEN}` } // Сценарий 1: список постов (70% трафика) const postsList = http.get(`${BASE_URL}/api/posts?limit=20`, { headers }) check(postsList, { 'posts list: status 200': (r) => r.status === 200, 'posts list: has items': (r) => r.json('data').length > 0 }) errorRate.add(postsList.status !== 200) sleep(Math.random() * 0.5) // случайная пауза 0-500мс // Сценарий 2: создание поста (20% трафика) if (Math.random() < 0.2) { const start = Date.now() const createPost = http.post(`${BASE_URL}/api/posts`, JSON.stringify({ title: `Test post ${Date.now()}`, content: 'Load test content' }), { headers }) postCreateDuration.add(Date.now() - start) check(createPost, { 'create post: status 201': (r) => r.status === 201, }) errorRate.add(createPost.status !== 201) } sleep(0.3) } . זה אומר ש-95% מהבקשות חייבות להיות מהירות מ-500ms, ו-99% מהירות משנייה אחת. אם סף מופר, הבדיקה מסתיימת עם שגיאה, ו-CI עוצר את הפריסה.
אנו משתמשים גם במדדים מותאמים אישית עבור פעולות עסקיות ספציפיות (למשל, זמן יצירת פוסט) ומגדירים ספים נפרדים עליהם. כך, אתה יודע בוודאות שפונקציונליות קריטית אינה מתדרדרת.
אינטגרציה עם GitHub Actions
---
# .github/workflows/performance.yml
name: Performance Tests
on:
push:
branches: [main, staging]
pull_request:
branches: [main]
jobs:
performance:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:15
env:
POSTGRES_DB: testdb
POSTGRES_PASSWORD: testpass
ports: ['5432:5432']
options: >-
--health-cmd pg_isready --health-interval 10s
steps:
- uses: actions/checkout@v4
- name: Start application
run: |
docker compose -f docker-compose.test.yml up -d api
npx wait-on http://localhost:3000/health --timeout 60000
- name: Run k6 smoke test
uses: grafana/[email protected]
with:
filename: tests/performance/api-smoke.js
flags: --out json=results.json
env:
BASE_URL: http://localhost:3000
K6_PROMETHEUS_RW_SERVER_URL: ${{ secrets.PROMETHEUS_URL }}
- name: Parse results
if: always()
run: |
# Показать summary в PR комментарии
jq -r '.metrics | { p95: .http_req_duration["p(95)"], p99: .http_req_duration["p(99)"], errors: .http_req_failed.rate }' results.json
- name: Comment PR with results
if: github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
const fs = require('fs')
const results = JSON.parse(fs.readFileSync('results.json'))
const p95 = results.metrics.http_req_duration['p(95)'].toFixed(0)
const errorRate = (results.metrics.http_req_failed.rate * 100).toFixed(2)
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `## Performance Test Results\n\n| Metric | Value | Threshold |\n|--------|-------|-----------|\n| p95 latency | ${p95}ms | <500ms |\n| Error rate | ${errorRate}% | <1% |`
})
---
השוואת בסיס בין פריסות
#!/bin/bash
# scripts/compare-performance.sh
CURRENT_BRANCH=$(git branch --show-current)
BASELINE_BRANCH="main"
# Тест текущего кода
k6 run --out json=current.json tests/performance/api-smoke.js
# Переключиться на baseline
git stash
git checkout $BASELINE_BRANCH
docker compose up -d --build api
sleep 10
k6 run --out json=baseline.json tests/performance/api-smoke.js
# Сравнение
node - <<'EOF'
const current = require('./current.json')
const baseline = require('./baseline.json')
const metrics = ['http_req_duration']
for (const m of metrics) {
const cp95 = current.metrics[m]['p(95)']
const bp95 = baseline.metrics[m]['p(95)']
const delta = ((cp95 - bp95) / bp95 * 100).toFixed(1)
if (cp95 > bp95 * 1.2) {
// регрессия > 20%
console.error(`REGRESSION: ${m} p95 degraded by ${delta}%`)
process.exit(1)
}
console.log(`${m} p95: ${cp95}ms vs ${bp95}ms baseline (${delta}%)`)
}
EOF
# Вернуться на текущую ветку
git checkout $CURRENT_BRANCH
git stash pop
Artillery לתיאור תרחישים
---
# tests/performance/user-journey.yml
config:
target: "{{ $processEnvironment.BASE_URL }}"
phases:
- duration: 60
arrivalRate: 5
rampTo: 20
name: "Ramp up"
- duration: 120
arrivalRate: 20
name: "Sustained load"
ensure:
thresholds:
- http.response_time.p95: 500
- http.request_rate: 15
scenarios:
- name: "Browse and purchase"
weight: 70
flow:
- get:
url: "/api/products"
expect:
- statusCode: 200
- post:
url: "/api/cart"
json:
productId: "{{ $randomInt(1, 100) }}"
quantity: 1
- name: "Search only"
weight: 30
flow:
- get:
url: "/api/search?q={{ $randomString(5) }}"
השוואת כלי בדיקת עומס
| תכונה | k6 | Artillery | Gatling |
|---|---|---|---|
| שפת סקריפטים | JavaScript | YAML | Scala/Java |
| אינטגרציית CI טבעית | כן (GitHub Actions, GitLab CI) | כן (דרך NPM) | כן (Maven/Gradle) |
| ספים מובנים | כן | כן (דרך ensure) | כן |
| יצירת דוחות | JSON, Prometheus, HTML | JSON, HTML | HTML (מפורט) |
| ביצועים | גבוהים (Go) | בינוניים (Node.js) | גבוהים (JVM) |
| רישיון | קוד פתוח (AGPL) | קוד פתוח (MPL) | קוד פתוח (ALv2) |
למה להשתמש ב-k6 עבור CI?
ל-k6 יש תמיכה מובנית ב-CI/CD: הוא פועל ככלי CLI, אינו דורש GUI, וניתן להוציא תוצאות ל-JSON לעיבוד נוסף. בניגוד ל-Gatling (דורש Scala/Java ויצירת דוחות HTML) או Artillery (תצורות YAML, אבל פחות מדדים), k6 מספק מדדים גמישים ואינטגרציה פשוטה עם GitHub Actions דרך פעולות מוכנות. עבור צוותים שכבר משתמשים ב-JavaScript, סף הכניסה מינימלי.
תהליך העבודה
- ניתוח — זיהוי נקודות קצה קריטיות ותרחישי משתמש.
- עיצוב — פיתוח תרחישי עומס, הגדרת פרופילי עומס (ramp-up, מתמשך).
- יישום — כתיבת סקריפטים (k6, Artillery, או Gatling), אינטגרציה ל-CI.
- בדיקה — הרצת בדיקות עשן על staging, כוונון ספים.
- פריסה — הכללת בדיקות בצנרת, הגדרת תגובות PR אוטומטיות.
לוחות זמנים ומה כלול
הגדרת סט בסיסי של בדיקות עשן k6 עם ספים ואינטגרציית CI אורכת בין יום ל-2 ימי עסקים. אם נדרשת השוואת בסיס ומדדים מותאמים אישית, אנו מאריכים ל-3-5 ימים. ההיקף כולל:
- סקריפטים לבדיקת עומס (2-3 תרחישים)
- הגדרת ספים
- אינטגרציה עם GitHub Actions או GitLab CI (צנרת YAML)
- תגובת PR עם תוצאות בדיקה
- תיעוד להרצה ותחזוקה
- ייעוץ לצוות בנושא פרשנות תוצאות
אנו מבטיחים שהבדיקות לא יפריעו לתהליך הפיתוח הראשי: הן רצות במקביל ואורכות לא יותר מ-5 דקות.
למה לבחור בנו
- ניסיון של למעלה מ-5 שנים בבדיקות עומס ו-CI/CD
- 50+ פרויקטים מוצלחים — מסטארטאפים ועד ארגונים
- אנו משתמשים רק בכלים מוכחים (k6, Grafana, Prometheus)
- אנו מספקים אחריות על פעולת בדיקות תקינה למשך חודש לאחר היישום
צור קשר כדי לדון בפרויקט שלך: קבל ייעוץ על שילוב בדיקות עומס רציפות בצנרת שלך.







