Files
sless/sqs-operator/sqs_load_test.sh

427 lines
16 KiB
Bash
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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 "<Message>" || 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 "<Message>" || 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 "<Message>" || 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 "<Message>" || 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 '(?<=<Code>)[^<]+' | 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 '(?<=<MD5OfMessageBody>)[^<]+' | 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"