#!/bin/bash # 2026-04-09 — Нагрузочное тестирование SQS API # Замеряет: throughput, latency, конкурентность, стабильность под нагрузкой # Запуск: bash sqs_load_test.sh set -euo pipefail # ═══════════════════════════════════════════ # КОНФИГУРАЦИЯ # ═══════════════════════════════════════════ export PATH="$HOME/.local/bin:$PATH" SQS_HOST="https://sqs.kube5s.ru" SQS_BASE="${SQS_HOST}/sqs/test001" export AWS_ACCESS_KEY_ID="SQSAK-test001-ae4dc8" export AWS_SECRET_ACCESS_KEY="J8mElXq8Nwww8iFRtGJCm8KfxM9XSTluTqS2A67xsEE" export AWS_DEFAULT_REGION="us-east-1" ENDPOINT="--endpoint-url ${SQS_BASE}" AUTH="${AWS_ACCESS_KEY_ID}:${AWS_SECRET_ACCESS_KEY}" VERSION="2012-11-05" TS=$(date +%s) TMPDIR=$(mktemp -d /tmp/sqs-load-XXXXX) # Параметры нагрузки MESSAGES_SINGLE=100 # send/receive в одну очередь MESSAGES_BURST=50 # burst за раз PARALLEL_WORKERS=5 # параллельные воркеры QUEUES_COUNT=10 # кол-во очередей для multi-queue теста echo "╔══════════════════════════════════════════════════════╗" echo "║ SQS Load Test — $(date '+%Y-%m-%d %H:%M:%S') ║" echo "║ Endpoint: ${SQS_BASE} ║" echo "║ Temp: ${TMPDIR} ║" echo "╚══════════════════════════════════════════════════════╝" echo "" # Утилита замера времени (ms) ms_now() { date +%s%N | cut -b1-13; } sqs_call() { curl -s --max-time 30 -u "$AUTH" "${SQS_BASE}?${1}&Version=${VERSION}" 2>&1 } # ═══════════════════════════════════════════ # ТЕСТ 1: Latency отдельных операций # ═══════════════════════════════════════════ echo "── ТЕСТ 1: Latency отдельных операций ──" Q1="loadq-latency-${TS}" sqs_call "Action=CreateQueue&QueueName=${Q1}" >/dev/null # SendMessage latency (20 вызовов) SEND_TIMES=() for i in $(seq 1 20); do T1=$(ms_now) sqs_call "Action=SendMessage&QueueUrl=${SQS_BASE}/test001/${Q1}&MessageBody=latency-test-${i}" >/dev/null T2=$(ms_now) SEND_TIMES+=( $((T2 - T1)) ) done # Статистика SEND_TOTAL=0 SEND_MIN=999999 SEND_MAX=0 for t in "${SEND_TIMES[@]}"; do SEND_TOTAL=$((SEND_TOTAL + t)) [ "$t" -lt "$SEND_MIN" ] && SEND_MIN=$t [ "$t" -gt "$SEND_MAX" ] && SEND_MAX=$t done SEND_AVG=$((SEND_TOTAL / ${#SEND_TIMES[@]})) echo " SendMessage (20x): avg=${SEND_AVG}ms min=${SEND_MIN}ms max=${SEND_MAX}ms" # ReceiveMessage latency (20 вызовов) RECV_TIMES=() for i in $(seq 1 20); do T1=$(ms_now) sqs_call "Action=ReceiveMessage&QueueUrl=${SQS_BASE}/test001/${Q1}&MaxNumberOfMessages=1" >/dev/null T2=$(ms_now) RECV_TIMES+=( $((T2 - T1)) ) done RECV_TOTAL=0 RECV_MIN=999999 RECV_MAX=0 for t in "${RECV_TIMES[@]}"; do RECV_TOTAL=$((RECV_TOTAL + t)) [ "$t" -lt "$RECV_MIN" ] && RECV_MIN=$t [ "$t" -gt "$RECV_MAX" ] && RECV_MAX=$t done RECV_AVG=$((RECV_TOTAL / ${#RECV_TIMES[@]})) echo " ReceiveMessage (20x): avg=${RECV_AVG}ms min=${RECV_MIN}ms max=${RECV_MAX}ms" # ListQueues latency LIST_TIMES=() for i in $(seq 1 10); do T1=$(ms_now) sqs_call "Action=ListQueues" >/dev/null T2=$(ms_now) LIST_TIMES+=( $((T2 - T1)) ) done LIST_TOTAL=0 for t in "${LIST_TIMES[@]}"; do LIST_TOTAL=$((LIST_TOTAL + t)); done LIST_AVG=$((LIST_TOTAL / ${#LIST_TIMES[@]})) echo " ListQueues (10x): avg=${LIST_AVG}ms" sqs_call "Action=PurgeQueue&QueueUrl=${SQS_BASE}/test001/${Q1}" >/dev/null sqs_call "Action=DeleteQueue&QueueUrl=${SQS_BASE}/test001/${Q1}" >/dev/null echo "" # ═══════════════════════════════════════════ # ТЕСТ 2: Последовательный throughput — 100 сообщений # ═══════════════════════════════════════════ echo "── ТЕСТ 2: Последовательный throughput (${MESSAGES_SINGLE} сообщений) ──" Q2="loadq-seq-${TS}" sqs_call "Action=CreateQueue&QueueName=${Q2}" >/dev/null # Отправка T_START=$(ms_now) SEND_OK=0 SEND_ERR=0 for i in $(seq 1 $MESSAGES_SINGLE); do R=$(sqs_call "Action=SendMessage&QueueUrl=${SQS_BASE}/test001/${Q2}&MessageBody=seq-msg-${i}") if echo "$R" | grep -q "MessageId"; then SEND_OK=$((SEND_OK + 1)) else SEND_ERR=$((SEND_ERR + 1)) fi done T_END=$(ms_now) SEND_ELAPSED=$((T_END - T_START)) SEND_RPS=$(( MESSAGES_SINGLE * 1000 / (SEND_ELAPSED + 1) )) echo " Send: ${SEND_OK} OK / ${SEND_ERR} ERR за ${SEND_ELAPSED}ms (${SEND_RPS} msg/s)" # Получение T_START=$(ms_now) RECV_OK=0 EMPTY=0 for attempt in $(seq 1 $((MESSAGES_SINGLE + 50))); do R=$(sqs_call "Action=ReceiveMessage&QueueUrl=${SQS_BASE}/test001/${Q2}&MaxNumberOfMessages=10") COUNT=$(echo "$R" | grep -c "" || true) if [ "$COUNT" -gt 0 ]; then RECV_OK=$((RECV_OK + COUNT)) else EMPTY=$((EMPTY + 1)) [ "$EMPTY" -ge 5 ] && break fi [ "$RECV_OK" -ge "$MESSAGES_SINGLE" ] && break done T_END=$(ms_now) RECV_ELAPSED=$((T_END - T_START)) RECV_RPS=$(( RECV_OK * 1000 / (RECV_ELAPSED + 1) )) echo " Recv: ${RECV_OK}/${MESSAGES_SINGLE} за ${RECV_ELAPSED}ms (${RECV_RPS} msg/s)" sqs_call "Action=DeleteQueue&QueueUrl=${SQS_BASE}/test001/${Q2}" >/dev/null echo "" # ═══════════════════════════════════════════ # ТЕСТ 3: Параллельная отправка (N воркеров) # ═══════════════════════════════════════════ echo "── ТЕСТ 3: Параллельная отправка (${PARALLEL_WORKERS} воркеров × ${MESSAGES_BURST} msg) ──" Q3="loadq-par-${TS}" sqs_call "Action=CreateQueue&QueueName=${Q3}" >/dev/null # Функция воркера — записывает результат в файл worker_send() { local wid=$1 local ok=0 local err=0 for i in $(seq 1 $MESSAGES_BURST); do R=$(curl -s --max-time 30 -u "$AUTH" "${SQS_BASE}?Action=SendMessage&QueueUrl=${SQS_BASE}/test001/${Q3}&MessageBody=w${wid}-msg${i}&Version=${VERSION}" 2>&1) if echo "$R" | grep -q "MessageId"; then ok=$((ok + 1)) else err=$((err + 1)) fi done echo "${ok}:${err}" > "${TMPDIR}/worker-${wid}.result" } T_START=$(ms_now) for w in $(seq 1 $PARALLEL_WORKERS); do worker_send $w & done wait T_END=$(ms_now) PAR_ELAPSED=$((T_END - T_START)) # Собираем результаты PAR_OK=0 PAR_ERR=0 for w in $(seq 1 $PARALLEL_WORKERS); do if [ -f "${TMPDIR}/worker-${w}.result" ]; then RES=$(cat "${TMPDIR}/worker-${w}.result") PAR_OK=$((PAR_OK + ${RES%%:*})) PAR_ERR=$((PAR_ERR + ${RES##*:})) fi done PAR_TOTAL=$((PARALLEL_WORKERS * MESSAGES_BURST)) PAR_RPS=$(( PAR_OK * 1000 / (PAR_ELAPSED + 1) )) echo " Send: ${PAR_OK}/${PAR_TOTAL} OK, ${PAR_ERR} ERR за ${PAR_ELAPSED}ms (${PAR_RPS} msg/s)" # Параллельное получение worker_recv() { local wid=$1 local ok=0 local empty=0 for _ in $(seq 1 $((MESSAGES_BURST + 20))); do R=$(curl -s --max-time 30 -u "$AUTH" "${SQS_BASE}?Action=ReceiveMessage&QueueUrl=${SQS_BASE}/test001/${Q3}&MaxNumberOfMessages=10&Version=${VERSION}" 2>&1) COUNT=$(echo "$R" | grep -c "" || true) if [ "$COUNT" -gt 0 ]; then ok=$((ok + COUNT)) else empty=$((empty + 1)) [ "$empty" -ge 3 ] && break fi done echo "${ok}" > "${TMPDIR}/recv-${wid}.result" } T_START=$(ms_now) for w in $(seq 1 $PARALLEL_WORKERS); do worker_recv $w & done wait T_END=$(ms_now) RECV_PAR_ELAPSED=$((T_END - T_START)) RECV_PAR_OK=0 for w in $(seq 1 $PARALLEL_WORKERS); do if [ -f "${TMPDIR}/recv-${w}.result" ]; then RECV_PAR_OK=$((RECV_PAR_OK + $(cat "${TMPDIR}/recv-${w}.result"))) fi done RECV_PAR_RPS=$(( RECV_PAR_OK * 1000 / (RECV_PAR_ELAPSED + 1) )) echo " Recv: ${RECV_PAR_OK}/${PAR_TOTAL} за ${RECV_PAR_ELAPSED}ms (${RECV_PAR_RPS} msg/s)" sqs_call "Action=DeleteQueue&QueueUrl=${SQS_BASE}/test001/${Q3}" >/dev/null echo "" # ═══════════════════════════════════════════ # ТЕСТ 4: Много очередей одновременно # ═══════════════════════════════════════════ echo "── ТЕСТ 4: Создание ${QUEUES_COUNT} очередей + send/receive в каждую ──" T_START=$(ms_now) CREATE_OK=0 for i in $(seq 1 $QUEUES_COUNT); do R=$(sqs_call "Action=CreateQueue&QueueName=loadq-multi-${TS}-${i}") if echo "$R" | grep -q "CreateQueueResult"; then CREATE_OK=$((CREATE_OK + 1)) fi done T_CRE=$(ms_now) echo " Create: ${CREATE_OK}/${QUEUES_COUNT} за $((T_CRE - T_START))ms" # Send 5 msg в каждую параллельно multi_send() { local qi=$1 local ok=0 for m in $(seq 1 5); do R=$(curl -s --max-time 30 -u "$AUTH" "${SQS_BASE}?Action=SendMessage&QueueUrl=${SQS_BASE}/test001/loadq-multi-${TS}-${qi}&MessageBody=multi-${qi}-${m}&Version=${VERSION}" 2>&1) echo "$R" | grep -q "MessageId" && ok=$((ok + 1)) done echo "$ok" > "${TMPDIR}/multi-send-${qi}.result" } T_START=$(ms_now) for i in $(seq 1 $QUEUES_COUNT); do multi_send $i & done wait T_SEND=$(ms_now) MULTI_SEND_OK=0 for i in $(seq 1 $QUEUES_COUNT); do [ -f "${TMPDIR}/multi-send-${i}.result" ] && MULTI_SEND_OK=$((MULTI_SEND_OK + $(cat "${TMPDIR}/multi-send-${i}.result"))) done echo " Send (5 × ${QUEUES_COUNT}): ${MULTI_SEND_OK}/$((QUEUES_COUNT * 5)) за $((T_SEND - T_START))ms" # Receive из каждой multi_recv() { local qi=$1 local ok=0 for _ in $(seq 1 10); do R=$(curl -s --max-time 30 -u "$AUTH" "${SQS_BASE}?Action=ReceiveMessage&QueueUrl=${SQS_BASE}/test001/loadq-multi-${TS}-${qi}&MaxNumberOfMessages=10&Version=${VERSION}" 2>&1) COUNT=$(echo "$R" | grep -c "" || true) ok=$((ok + COUNT)) [ "$COUNT" -eq 0 ] && break done echo "$ok" > "${TMPDIR}/multi-recv-${qi}.result" } T_START=$(ms_now) for i in $(seq 1 $QUEUES_COUNT); do multi_recv $i & done wait T_RECV=$(ms_now) MULTI_RECV_OK=0 for i in $(seq 1 $QUEUES_COUNT); do [ -f "${TMPDIR}/multi-recv-${i}.result" ] && MULTI_RECV_OK=$((MULTI_RECV_OK + $(cat "${TMPDIR}/multi-recv-${i}.result"))) done echo " Recv: ${MULTI_RECV_OK}/$((QUEUES_COUNT * 5)) за $((T_RECV - T_START))ms" # Удаление T_START=$(ms_now) for i in $(seq 1 $QUEUES_COUNT); do sqs_call "Action=DeleteQueue&QueueUrl=${SQS_BASE}/test001/loadq-multi-${TS}-${i}" >/dev/null & done wait T_DEL=$(ms_now) echo " Delete ${QUEUES_COUNT} queues: $((T_DEL - T_START))ms" echo "" # ═══════════════════════════════════════════ # ТЕСТ 5: Стресс — быстрая send+receive петля # ═══════════════════════════════════════════ echo "── ТЕСТ 5: Stress loop — send+receive 60 сек ──" Q5="loadq-stress-${TS}" sqs_call "Action=CreateQueue&QueueName=${Q5}" >/dev/null STRESS_DURATION=60 STRESS_SEND=0 STRESS_RECV=0 STRESS_ERR=0 T_START=$(date +%s) while true; do NOW=$(date +%s) ELAPSED=$((NOW - T_START)) [ "$ELAPSED" -ge "$STRESS_DURATION" ] && break # Send R=$(sqs_call "Action=SendMessage&QueueUrl=${SQS_BASE}/test001/${Q5}&MessageBody=stress-${STRESS_SEND}") if echo "$R" | grep -q "MessageId"; then STRESS_SEND=$((STRESS_SEND + 1)) else STRESS_ERR=$((STRESS_ERR + 1)) fi # Receive R=$(sqs_call "Action=ReceiveMessage&QueueUrl=${SQS_BASE}/test001/${Q5}&MaxNumberOfMessages=10") COUNT=$(echo "$R" | grep -c "" || true) STRESS_RECV=$((STRESS_RECV + COUNT)) done T_END=$(date +%s) STRESS_REAL=$((T_END - T_START)) STRESS_SEND_RPS=$((STRESS_SEND / (STRESS_REAL + 1))) STRESS_RECV_RPS=$((STRESS_RECV / (STRESS_REAL + 1))) echo " Duration: ${STRESS_REAL}s" echo " Sent: ${STRESS_SEND} (${STRESS_SEND_RPS} msg/s)" echo " Received: ${STRESS_RECV} (${STRESS_RECV_RPS} msg/s)" echo " Errors: ${STRESS_ERR}" sqs_call "Action=DeleteQueue&QueueUrl=${SQS_BASE}/test001/${Q5}" >/dev/null echo "" # ═══════════════════════════════════════════ # ТЕСТ 6: Большие сообщения # ═══════════════════════════════════════════ echo "── ТЕСТ 6: Размер сообщений ──" Q6="loadq-size-${TS}" sqs_call "Action=CreateQueue&QueueName=${Q6}" >/dev/null for SIZE in 1 1024 10240 65536 131072 262144; do BODY=$(python3 -c "print('X' * ${SIZE})" 2>/dev/null) T1=$(ms_now) R=$(curl -s --max-time 30 -u "$AUTH" -X POST "${SQS_BASE}?Action=SendMessage&QueueUrl=${SQS_BASE}/test001/${Q6}&Version=${VERSION}" --data-urlencode "MessageBody=${BODY}" 2>&1) T2=$(ms_now) LAT=$((T2 - T1)) if echo "$R" | grep -q "MessageId"; then echo " ${SIZE} bytes: ✅ OK (${LAT}ms)" else ERR_TYPE=$(echo "$R" | grep -oP '(?<=)[^<]+' | head -1) echo " ${SIZE} bytes: ❌ REJECTED — ${ERR_TYPE:-unknown} (${LAT}ms)" fi done sqs_call "Action=PurgeQueue&QueueUrl=${SQS_BASE}/test001/${Q6}" >/dev/null sqs_call "Action=DeleteQueue&QueueUrl=${SQS_BASE}/test001/${Q6}" >/dev/null echo "" # ═══════════════════════════════════════════ # ТЕСТ 7: Целостность данных # ═══════════════════════════════════════════ echo "── ТЕСТ 7: Целостность данных (MD5 проверка) ──" Q7="loadq-md5-${TS}" sqs_call "Action=CreateQueue&QueueName=${Q7}" >/dev/null MD5_OK=0 MD5_FAIL=0 for i in $(seq 1 20); do MSG="integrity-check-${i}-$(head -c 16 /dev/urandom | base64 | tr -d '/+=')" EXPECTED_MD5=$(echo -n "$MSG" | md5sum | awk '{print $1}') R=$(sqs_call "Action=SendMessage&QueueUrl=${SQS_BASE}/test001/${Q7}&MessageBody=${MSG}") RETURNED_MD5=$(echo "$R" | grep -oP '(?<=)[^<]+' | head -1) if [ "$EXPECTED_MD5" = "$RETURNED_MD5" ]; then MD5_OK=$((MD5_OK + 1)) else MD5_FAIL=$((MD5_FAIL + 1)) echo " MISMATCH msg=${MSG}: expected=${EXPECTED_MD5} got=${RETURNED_MD5}" fi done echo " MD5 check: ${MD5_OK}/20 OK, ${MD5_FAIL} FAIL" sqs_call "Action=DeleteQueue&QueueUrl=${SQS_BASE}/test001/${Q7}" >/dev/null echo "" # ═══════════════════════════════════════════ # ИТОГО # ═══════════════════════════════════════════ echo "╔══════════════════════════════════════════════════════╗" echo "║ НАГРУЗОЧНЫЙ ТЕСТ ЗАВЕРШЁН ║" echo "║ $(date '+%Y-%m-%d %H:%M:%S') ║" echo "╚══════════════════════════════════════════════════════╝" # Очистка temp rm -rf "$TMPDIR"