Реалізація симуляції реального трафіку для навантажувального тестування

Наша компанія займається розробкою, підтримкою та обслуговуванням сайтів будь-якої складності. Від простих односторінкових сайтів до масштабних кластерних систем, побудованих на мікро сервісах. Досвід розробників підтверджено сертифікатами від вендорів.

Розробка та обслуговування будь-яких видів сайтів:

Інформаційні сайти або веб-програми
Сайти візитки, landing page, корпоративні сайти, онлайн каталоги, квіз, промо-сайти, блоги, ресурси новин, інформаційні портали, форуми, агрегатори
Сайти або веб-програми електронної комерції
Інтернет-магазини, B2B-портали, маркетплейси, онлайн-обмінники, кешбек-сайти, біржі, дропшиппінг-платформи, парсери товарів
Веб-програми для управління бізнес-процесами
CRM-системи, ERP-системи, корпоративні портали, системи управління виробництвом, парсери інформації
Сайти або веб-програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, конструктори сайтів, портали надання електронних послуг, відеохостинги, тематичні портали

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Пропоновані послуги
Показано 1 з 1 послугУсі 2065 послуг
Реалізація симуляції реального трафіку для навантажувального тестування
Середня
~2-3 робочих дні
Часті питання

Наші компетенції:

Етапи розробки
Останні роботи
  • image_website-b2b-advance_0.png
    Розробка сайту компанії B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    874
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Розробка веб-сайту для компанії ФІКСПЕР
    851

Симуляція реального трафіку в нагрузному тестуванні

Рівномірний трафік 100 VU — не те, що відбувається в дійсності. Реальний трафік: ранкові та вечірні піки, різні типи користувачів (браузери/мобільні/API), сеансове поведінку, випадкові паузи, розподіл Парето. Реалістична симуляція виявляє проблеми, які пропускають синтетичні тести.

Аналіз реального трафіку як основа тесту

# Витягти паттерни з nginx access log
import re
from collections import Counter, defaultdict
import json

def analyze_access_log(log_file: str):
    pattern = re.compile(
        r'(?P<ip>\S+) .+ \[(?P<time>[^\]]+)\] '
        r'"(?P<method>\w+) (?P<path>[^"]+) HTTP/\d+" '
        r'(?P<status>\d+) (?P<bytes>\d+)'
    )

    endpoint_counts = Counter()
    method_counts = Counter()
    hourly_traffic = defaultdict(int)

    with open(log_file) as f:
        for line in f:
            m = pattern.match(line)
            if not m:
                continue

            # Нормалізувати path (видалити ID)
            path = re.sub(r'/\d+', '/{id}', m.group('path').split('?')[0])
            endpoint_counts[f"{m.group('method')} {path}"] += 1
            method_counts[m.group('method')] += 1

            # Погодинний розподіл
            hour = m.group('time').split(':')[1]
            hourly_traffic[hour] += 1

    total = sum(endpoint_counts.values())

    print("=== Top Endpoints (% of traffic) ===")
    for endpoint, count in endpoint_counts.most_common(20):
        pct = count / total * 100
        print(f"  {pct:.1f}% {endpoint}")

    print("\n=== Hourly Distribution ===")
    for hour in sorted(hourly_traffic):
        bar = '█' * (hourly_traffic[hour] // 100)
        print(f"  {hour}:00 {bar} {hourly_traffic[hour]}")

    # Експорт для k6 сценарію
    weights = {ep: round(cnt/total, 3) for ep, cnt in endpoint_counts.most_common(20)}
    return weights

k6 сценарій з реалістичною поведінкою

// tests/realistic/user-journey.js
import http from 'k6/http'
import { check, sleep } from 'k6'
import { SharedArray } from 'k6/data'
import { randomItem, randomIntBetween } from 'https://jslib.k6.io/k6-utils/1.4.0/index.js'

// Загрузити тестові дані з CSV
const users = new SharedArray('users', function() {
  return open('./data/test-users.csv').split('\n')
    .slice(1)
    .map(row => {
      const [email, token, userId] = row.split(',')
      return { email, token, userId }
    })
})

const searchTerms = new SharedArray('searches', function() {
  return open('./data/popular-searches.txt').split('\n').filter(Boolean)
})

export const options = {
  scenarios: {
    // Анонімні браузери (40% трафіку)
    anonymous_browse: {
      executor: 'ramping-vus',
      startVUs: 0,
      stages: [
        { duration: '5m', target: 40 },
        { duration: '30m', target: 40 },
        { duration: '5m', target: 0 }
      ],
      exec: 'anonymousBrowse'
    },

    // Авторизовані користувачі (50% трафіку)
    logged_in_users: {
      executor: 'ramping-vus',
      startVUs: 0,
      stages: [
        { duration: '5m', target: 50 },
        { duration: '30m', target: 50 },
        { duration: '5m', target: 0 }
      ],
      exec: 'loggedInJourney'
    },

    // API-клієнти (10% трафіку)
    api_clients: {
      executor: 'constant-arrival-rate',
      rate: 10,
      timeUnit: '1s',
      duration: '40m',
      preAllocatedVUs: 20,
      exec: 'apiClient'
    }
  },

  thresholds: {
    http_req_duration: ['p(95)<800'],
    http_req_failed: ['rate<0.01'],
  }
}

const BASE = __ENV.BASE_URL || 'https://staging.example.com'

// Сценарій: анонімний браузер
export function anonymousBrowse() {
  // Лендинг → каталог → товар → вихід
  http.get(`${BASE}/`)
  sleep(randomIntBetween(1, 4))

  const category = randomItem(['electronics', 'clothing', 'books', 'sports'])
  http.get(`${BASE}/api/products?category=${category}&limit=20`)
  sleep(randomIntBetween(2, 8))

  // 30% уходят сразу, 70% смотрят товар
  if (Math.random() > 0.3) {
    const productId = randomIntBetween(1, 500)
    http.get(`${BASE}/api/products/${productId}`)
    sleep(randomIntBetween(3, 15))
  }

  // 20% роблять пошук
  if (Math.random() < 0.2) {
    const term = randomItem(searchTerms)
    http.get(`${BASE}/api/search?q=${encodeURIComponent(term)}`)
    sleep(randomIntBetween(1, 5))
  }
}

// Сценарій: авторизований користувач
export function loggedInJourney() {
  const user = randomItem(users)
  const headers = {
    'Authorization': `Bearer ${user.token}`,
    'Content-Type': 'application/json'
  }

  // Профіль
  http.get(`${BASE}/api/me`, { headers })
  sleep(randomIntBetween(1, 3))

  // Переглядання товарів
  for (let i = 0; i < randomIntBetween(2, 8); i++) {
    const productId = randomIntBetween(1, 500)
    http.get(`${BASE}/api/products/${productId}`, { headers })
    sleep(randomIntBetween(2, 10))
  }

  // 40% додають у кошик
  if (Math.random() < 0.4) {
    http.post(`${BASE}/api/cart/items`, JSON.stringify({
      productId: randomIntBetween(1, 500),
      quantity: randomIntBetween(1, 3)
    }), { headers })
    sleep(randomIntBetween(1, 3))

    // 60% з додавших — оформляють замовлення
    if (Math.random() < 0.6) {
      http.get(`${BASE}/api/cart`, { headers })
      sleep(randomIntBetween(2, 5))

      const checkout = http.post(`${BASE}/api/orders`, JSON.stringify({
        paymentMethod: 'saved_card',
        shippingAddressId: 1
      }), { headers })
      check(checkout, { 'order created': (r) => r.status === 201 })
    }
  }
}

// Сценарій: API-клієнт (інтеграція)
export function apiClient() {
  const apiKey = __ENV.API_KEY
  const headers = {
    'X-API-Key': apiKey,
    'Content-Type': 'application/json'
  }

  // Синхронізація продуктів
  const r = http.get(`${BASE}/api/v1/products?since=${Date.now() - 3600000}`,
    { headers })
  check(r, { 'api: 200': (r) => r.status === 200 })
}

Розподіл Pareto (80/20)

Реальний трафік: 20% сторінок отримують 80% трафіку:

// Генератор Pareto-розподілу для ID
function paretoId(maxId, shape = 1.5) {
  // Power law: переважна більшість запитів до популярних ID
  const u = Math.random()
  return Math.ceil(maxId * Math.pow(1 - u, 1 / shape))
}

// Використання
const productId = paretoId(10000)  // переважно ID 1-200, рідко ID 9000+

Запис реального трафіку для відтворення

# Записати реальні запити до HAR файлу через Nginx
# nginx.conf
log_format har_format escape=json
  '{"startedDateTime":"$time_iso8601",'
  '"request":{"method":"$request_method","url":"$request_uri",'
  '"headers":{"Authorization":"$http_authorization"}},'
  '"response":{"status":$status}}';

access_log /var/log/nginx/har.log har_format;

# Конвертувати в k6 сценарій
npm install -g har-to-k6
har-to-k6 nginx-har.log -o tests/recorded-traffic.js

Теплова карта трафіку по годинам

# Задати профіль навантаження по реальним даним про трафік
HOURLY_WEIGHTS = {
    0: 0.2, 1: 0.1, 2: 0.1, 3: 0.1, 4: 0.1, 5: 0.15,
    6: 0.3, 7: 0.5, 8: 0.7, 9: 0.9, 10: 1.0, 11: 1.0,
    12: 0.95, 13: 0.9, 14: 0.85, 15: 0.85, 16: 0.9, 17: 0.95,
    18: 1.0, 19: 0.95, 20: 0.9, 21: 0.75, 22: 0.5, 23: 0.35
}

BASE_VUS = 100  # VU у пиковий час

def generate_k6_stages():
    stages = []
    for hour in range(24):
        vus = int(BASE_VUS * HOURLY_WEIGHTS[hour])
        stages.append(f'{{ duration: "1h", target: {vus} }}')
    return ',\n  '.join(stages)

print(f"stages: [\n  {generate_k6_stages()}\n]")

Часовий графік

Розробка реалістичного сценарію нагрузного тесту на основі аналізу реального трафіку — 2–3 робочих дні.