fix: stress_test.sh — правильный формат queue URL, парсинг tenant API, subshell-safe массивы

This commit is contained in:
Naeel
2026-04-11 16:28:59 +03:00
parent 60931fd2fd
commit f937b7fccb
+97 -53
View File
@@ -1,6 +1,7 @@
#!/bin/bash
# stress_test.sh — стресс-тест shared-sqs: конкурентность, Redis resilience, падение пода
# Created: 2026-04-11
# Updated: 2026-04-11 — fix: правильный формат queue URL, парсинг tenant API response
# Покрывает:
# 1. Параллельная отправка сообщений (N воркеров, M сообщений)
# 2. Параллельное чтение и удаление (гонка за сообщения)
@@ -36,6 +37,11 @@ TMPDIR_STRESS=$(mktemp -d /tmp/sqs_stress_XXXXX)
# ХЕЛПЕРЫ
# ═══════════════════════════════════════════
# json_field — извлекает значение поля из JSON (без зависимости от jq)
json_field() {
python3 -c "import sys,json; print(json.load(sys.stdin).get('$1',''))" 2>/dev/null
}
check() {
local name="$1" output="$2" pattern="$3"
TOTAL=$((TOTAL + 1))
@@ -152,31 +158,43 @@ echo "╚═══════════════════════
echo ""
# ═══════════════════════════════════════════════════════════
# 1. ПОДГОТОВКА — создаём тенантов и очереди
# 1. ПОДГОТОВКА — создаём тенантов и очереди через Admin API
# ═══════════════════════════════════════════════════════════
echo "═══ 1. Подготовка — тенанты и очереди ═══"
# Создаём тенантов для теста
# Создаём тенантов — API сам генерирует access_key/secret_key
declare -a TENANT_AK
declare -a TENANT_SK
declare -a TENANT_ID
for i in $(seq 1 $TENANT_COUNT); do
R=$(admin_api POST "/admin/tenants" "{\"name\":\"stress-t${i}-${TS}\",\"access_key\":\"STRESSAK${i}${TS}\",\"secret_key\":\"STRESSSK${i}${TS}\"}")
TENANT_AK[$i]="STRESSAK${i}${TS}"
TENANT_SK[$i]="STRESSSK${i}${TS}"
echo " Тенант ${i}: AK=${TENANT_AK[$i]}"
R=$(admin_api POST "/admin/tenants" "{\"name\":\"stress-t${i}-${TS}\",\"max_queues\":50}")
TENANT_AK[$i]=$(echo "$R" | json_field access_key)
TENANT_SK[$i]=$(echo "$R" | json_field secret_key)
TENANT_ID[$i]=$(echo "$R" | json_field id)
echo " Тенант ${i}: ID=${TENANT_ID[$i]} AK=${TENANT_AK[$i]}"
if [[ -z "${TENANT_AK[$i]}" ]]; then
echo " ⛔ ОШИБКА: не удалось создать тенанта ${i}. Ответ: $R"
exit 1
fi
done
# Очередь для concurrency-теста
# qurl — формирует правильный Queue URL: BASE_URL/TID/QUEUE_NAME
# Формат: https://qu.kube5s.ru/{tenant_id}/{queue_name}
qurl() {
echo "${BASE_URL}/${1}/${2}"
}
# Очередь для concurrency-теста (одно имя, но у каждого тенанта — своя изолированная)
STRESS_Q="stress-concurrent-${TS}"
for i in $(seq 1 $TENANT_COUNT); do
R=$(sqs "${TENANT_AK[$i]}" "${TENANT_SK[$i]}" create-queue --queue-name "$STRESS_Q")
echo " Тенант ${i}: создана очередь $STRESS_Q$(echo "$R" | json_field QueueUrl)"
done
echo " Очередь: $STRESS_Q создана для $TENANT_COUNT тенантов"
# Дополнительная очередь для burst-теста
BURST_Q="stress-burst-${TS}"
R=$(sqs "${TENANT_AK[1]}" "${TENANT_SK[1]}" create-queue --queue-name "$BURST_Q")
echo " Очередь: $BURST_Q (burst)"
echo " Burst очередь: $BURST_Q"
echo ""
# ═══════════════════════════════════════════════════════════
@@ -187,14 +205,21 @@ echo "═══ 2. Конкурентная отправка: ${CONCURRENT_WORKE
TOTAL_MSGS=$((CONCURRENT_WORKERS * MESSAGES_PER_WORKER))
echo " Всего будет отправлено: $TOTAL_MSGS сообщений"
# Сохраняем credentials первого тенанта для удобства в subshell
T1_AK="${TENANT_AK[1]}"
T1_SK="${TENANT_SK[1]}"
T1_ID="${TENANT_ID[1]}"
STRESS_QURL=$(qurl "$T1_ID" "$STRESS_Q")
BURST_QURL=$(qurl "$T1_ID" "$BURST_Q")
# Запускаем воркеров параллельно
for w in $(seq 1 $CONCURRENT_WORKERS); do
(
ok=0
fail=0
for m in $(seq 1 $MESSAGES_PER_WORKER); do
R=$(sqs "${TENANT_AK[1]}" "${TENANT_SK[1]}" send-message \
--queue-url "${BASE_URL}/queue/${STRESS_Q}" \
R=$(sqs "$T1_AK" "$T1_SK" send-message \
--queue-url "$STRESS_QURL" \
--message-body "worker-${w}-msg-${m}-${TS}" 2>&1)
if echo "$R" | grep -q "MessageId"; then
ok=$((ok + 1))
@@ -223,8 +248,8 @@ check_eq "Все сообщения отправлены" "$SEND_OK" "$TOTAL_MSG
# Проверяем через GetQueueAttributes — ApproximateNumberOfMessages
sleep 2
R=$(sqs "${TENANT_AK[1]}" "${TENANT_SK[1]}" get-queue-attributes \
--queue-url "${BASE_URL}/queue/${STRESS_Q}" \
R=$(sqs "$T1_AK" "$T1_SK" get-queue-attributes \
--queue-url "$STRESS_QURL" \
--attribute-names ApproximateNumberOfMessages 2>&1)
APPROX=$(echo "$R" | grep -oP '"ApproximateNumberOfMessages"\s*:\s*"\K[0-9]+')
echo " Очередь сообщает: ~${APPROX} сообщений"
@@ -244,8 +269,8 @@ for w in $(seq 1 $CONCURRENT_WORKERS); do
empty_rounds=0
# Читаем пока есть сообщения (max 10 пустых подряд — выход)
while [[ $empty_rounds -lt 10 ]]; do
R=$(sqs "${TENANT_AK[1]}" "${TENANT_SK[1]}" receive-message \
--queue-url "${BASE_URL}/queue/${STRESS_Q}" \
R=$(sqs "$T1_AK" "$T1_SK" receive-message \
--queue-url "$STRESS_QURL" \
--max-number-of-messages 10 \
--wait-time-seconds 1 2>&1)
# Считаем полученные
@@ -259,8 +284,8 @@ for w in $(seq 1 $CONCURRENT_WORKERS); do
# Удаляем каждое полученное
HANDLES=$(echo "$R" | grep -oP '"ReceiptHandle"\s*:\s*"\K[^"]+')
while IFS= read -r handle; do
DR=$(sqs "${TENANT_AK[1]}" "${TENANT_SK[1]}" delete-message \
--queue-url "${BASE_URL}/queue/${STRESS_Q}" \
DR=$(sqs "$T1_AK" "$T1_SK" delete-message \
--queue-url "$STRESS_QURL" \
--receipt-handle "$handle" 2>&1)
deleted=$((deleted + 1))
done <<< "$HANDLES"
@@ -289,8 +314,8 @@ check_eq "Удалено = отправлено (нет дубликатов)" "
# Очередь должна быть пуста
sleep 2
R=$(sqs "${TENANT_AK[1]}" "${TENANT_SK[1]}" get-queue-attributes \
--queue-url "${BASE_URL}/queue/${STRESS_Q}" \
R=$(sqs "$T1_AK" "$T1_SK" get-queue-attributes \
--queue-url "$STRESS_QURL" \
--attribute-names ApproximateNumberOfMessages 2>&1)
APPROX=$(echo "$R" | grep -oP '"ApproximateNumberOfMessages"\s*:\s*"\K[0-9]+')
check_eq "Очередь пуста после чтения" "${APPROX:-0}" "0"
@@ -302,13 +327,24 @@ echo ""
echo "═══ 4. Multi-tenant изоляция: ${TENANT_COUNT} тенантов, параллельная нагрузка ═══"
MSGS_PER_TENANT=30
# Каждый тенант отправляет уникальные сообщения в одноимённую очередь
# Сохраняем массивы в файлы — subshell не наследует bash-массивы надёжно
for i in $(seq 1 $TENANT_COUNT); do
echo "${TENANT_AK[$i]}" > "${TMPDIR_STRESS}/tak_${i}"
echo "${TENANT_SK[$i]}" > "${TMPDIR_STRESS}/tsk_${i}"
echo "${TENANT_ID[$i]}" > "${TMPDIR_STRESS}/tid_${i}"
done
# Каждый тенант отправляет уникальные сообщения в свою изолированную очередь
for i in $(seq 1 $TENANT_COUNT); do
(
tak=$(cat "${TMPDIR_STRESS}/tak_${i}")
tsk=$(cat "${TMPDIR_STRESS}/tsk_${i}")
tid=$(cat "${TMPDIR_STRESS}/tid_${i}")
my_qurl="${BASE_URL}/${tid}/${STRESS_Q}"
ok=0
for m in $(seq 1 $MSGS_PER_TENANT); do
R=$(sqs "${TENANT_AK[$i]}" "${TENANT_SK[$i]}" send-message \
--queue-url "${BASE_URL}/queue/${STRESS_Q}" \
R=$(sqs "$tak" "$tsk" send-message \
--queue-url "$my_qurl" \
--message-body "tenant-${i}-isolation-${m}" 2>&1)
if echo "$R" | grep -q "MessageId"; then ok=$((ok + 1)); fi
done
@@ -320,12 +356,16 @@ wait
# Каждый тенант читает — должен видеть ТОЛЬКО свои сообщения
for i in $(seq 1 $TENANT_COUNT); do
(
tak=$(cat "${TMPDIR_STRESS}/tak_${i}")
tsk=$(cat "${TMPDIR_STRESS}/tsk_${i}")
tid=$(cat "${TMPDIR_STRESS}/tid_${i}")
my_qurl="${BASE_URL}/${tid}/${STRESS_Q}"
own=0
foreign=0
empty_rounds=0
while [[ $empty_rounds -lt 5 ]]; do
R=$(sqs "${TENANT_AK[$i]}" "${TENANT_SK[$i]}" receive-message \
--queue-url "${BASE_URL}/queue/${STRESS_Q}" \
R=$(sqs "$tak" "$tsk" receive-message \
--queue-url "$my_qurl" \
--max-number-of-messages 10 \
--wait-time-seconds 1 2>&1)
MSG_COUNT=$(echo "$R" | grep -c '"Body"' || true)
@@ -345,8 +385,8 @@ for i in $(seq 1 $TENANT_COUNT); do
# Удаляем
HANDLES=$(echo "$R" | grep -oP '"ReceiptHandle"\s*:\s*"\K[^"]+')
while IFS= read -r handle; do
sqs "${TENANT_AK[$i]}" "${TENANT_SK[$i]}" delete-message \
--queue-url "${BASE_URL}/queue/${STRESS_Q}" \
sqs "$tak" "$tsk" delete-message \
--queue-url "$my_qurl" \
--receipt-handle "$handle" >/dev/null 2>&1
done <<< "$HANDLES"
done
@@ -378,8 +418,8 @@ echo "═══ 5. Burst: ${BURST_SIZE} сообщений одновремен
# Отправляем BURST_SIZE сообщений максимально параллельно
for b in $(seq 1 $BURST_SIZE); do
(
R=$(sqs "${TENANT_AK[1]}" "${TENANT_SK[1]}" send-message \
--queue-url "${BASE_URL}/queue/${BURST_Q}" \
R=$(sqs "$T1_AK" "$T1_SK" send-message \
--queue-url "$BURST_QURL" \
--message-body "burst-${b}-${TS}" 2>&1)
if echo "$R" | grep -q "MessageId"; then echo "1"; else echo "0"; fi \
> "${TMPDIR_STRESS}/burst_${b}.result"
@@ -400,8 +440,8 @@ check_ge "Burst: >= 90% успешно" "$BURST_OK" "$((BURST_SIZE * 90 / 100))"
# Проверяем что очередь получила все
sleep 3
R=$(sqs "${TENANT_AK[1]}" "${TENANT_SK[1]}" get-queue-attributes \
--queue-url "${BASE_URL}/queue/${BURST_Q}" \
R=$(sqs "$T1_AK" "$T1_SK" get-queue-attributes \
--queue-url "$BURST_QURL" \
--attribute-names ApproximateNumberOfMessages 2>&1)
BURST_APPROX=$(echo "$R" | grep -oP '"ApproximateNumberOfMessages"\s*:\s*"\K[0-9]+')
echo " Очередь burst: ~${BURST_APPROX} сообщений"
@@ -420,8 +460,8 @@ echo " Сообщений в burst-очереди до kill: $BEFORE_KILL"
# Отправляем ещё пачку — чтобы были "свежие" данные в Redis
EXTRA_MSGS=15
for m in $(seq 1 $EXTRA_MSGS); do
sqs "${TENANT_AK[1]}" "${TENANT_SK[1]}" send-message \
--queue-url "${BASE_URL}/queue/${BURST_Q}" \
sqs "$T1_AK" "$T1_SK" send-message \
--queue-url "$BURST_QURL" \
--message-body "pre-kill-${m}-${TS}" >/dev/null 2>&1
done
EXPECTED_AFTER=$((BEFORE_KILL + EXTRA_MSGS))
@@ -441,8 +481,8 @@ wait_for_healthy
# Проверяем что burst-очередь восстановилась из Redis
sleep 3
R=$(sqs "${TENANT_AK[1]}" "${TENANT_SK[1]}" get-queue-attributes \
--queue-url "${BASE_URL}/queue/${BURST_Q}" \
R=$(sqs "$T1_AK" "$T1_SK" get-queue-attributes \
--queue-url "$BURST_QURL" \
--attribute-names ApproximateNumberOfMessages 2>&1)
AFTER_KILL=$(echo "$R" | grep -oP '"ApproximateNumberOfMessages"\s*:\s*"\K[0-9]+')
echo " Сообщений после рестарта: ${AFTER_KILL:-0}"
@@ -518,8 +558,8 @@ echo " NetworkPolicy удалена — Redis доступен снова"
sleep 5
# Проверяем что после восстановления Redis сервис работает
R=$(sqs "${TENANT_AK[1]}" "${TENANT_SK[1]}" send-message \
--queue-url "${BASE_URL}/queue/${BURST_Q}" \
R=$(sqs "$T1_AK" "$T1_SK" send-message \
--queue-url "$BURST_QURL" \
--message-body "after-redis-recovery-${TS}" 2>&1)
check "Сервис работает после восстановления Redis" "$R" "MessageId"
echo ""
@@ -530,12 +570,13 @@ echo ""
echo "═══ 8. Смешанная нагрузка: send + receive + delete одновременно ═══"
MIXED_Q="stress-mixed-${TS}"
R=$(sqs "${TENANT_AK[1]}" "${TENANT_SK[1]}" create-queue --queue-name "$MIXED_Q")
R=$(sqs "$T1_AK" "$T1_SK" create-queue --queue-name "$MIXED_Q")
MIXED_QURL=$(qurl "$T1_ID" "$MIXED_Q")
# Заполняем 50 сообщений
for m in $(seq 1 50); do
sqs "${TENANT_AK[1]}" "${TENANT_SK[1]}" send-message \
--queue-url "${BASE_URL}/queue/${MIXED_Q}" \
sqs "$T1_AK" "$T1_SK" send-message \
--queue-url "$MIXED_QURL" \
--message-body "mixed-init-${m}" >/dev/null 2>&1
done
echo " Начальная загрузка: 50 сообщений"
@@ -549,8 +590,8 @@ for w in $(seq 1 5); do
end=$((SECONDS + MIXED_DURATION))
ok=0
while [[ $SECONDS -lt $end ]]; do
R=$(sqs "${TENANT_AK[1]}" "${TENANT_SK[1]}" send-message \
--queue-url "${BASE_URL}/queue/${MIXED_Q}" \
R=$(sqs "$T1_AK" "$T1_SK" send-message \
--queue-url "$MIXED_QURL" \
--message-body "mixed-send-${w}-$(date +%s%N)" 2>&1)
if echo "$R" | grep -q "MessageId"; then ok=$((ok + 1)); fi
done
@@ -564,14 +605,14 @@ for w in $(seq 1 5); do
end=$((SECONDS + MIXED_DURATION))
recv=0
while [[ $SECONDS -lt $end ]]; do
R=$(sqs "${TENANT_AK[1]}" "${TENANT_SK[1]}" receive-message \
--queue-url "${BASE_URL}/queue/${MIXED_Q}" \
R=$(sqs "$T1_AK" "$T1_SK" receive-message \
--queue-url "$MIXED_QURL" \
--max-number-of-messages 10 --wait-time-seconds 1 2>&1)
HANDLES=$(echo "$R" | grep -oP '"ReceiptHandle"\s*:\s*"\K[^"]+')
while IFS= read -r handle; do
[[ -z "$handle" ]] && continue
sqs "${TENANT_AK[1]}" "${TENANT_SK[1]}" delete-message \
--queue-url "${BASE_URL}/queue/${MIXED_Q}" \
sqs "$T1_AK" "$T1_SK" delete-message \
--queue-url "$MIXED_QURL" \
--receipt-handle "$handle" >/dev/null 2>&1
recv=$((recv + 1))
done <<< "$HANDLES"
@@ -587,8 +628,8 @@ for w in $(seq 1 2); do
ok=0
fail=0
while [[ $SECONDS -lt $end ]]; do
R=$(sqs "${TENANT_AK[1]}" "${TENANT_SK[1]}" get-queue-attributes \
--queue-url "${BASE_URL}/queue/${MIXED_Q}" \
R=$(sqs "$T1_AK" "$T1_SK" get-queue-attributes \
--queue-url "$MIXED_QURL" \
--attribute-names All 2>&1)
if echo "$R" | grep -q "ApproximateNumberOfMessages"; then
ok=$((ok + 1))
@@ -634,13 +675,16 @@ echo ""
echo "═══ 9. Cleanup ═══"
for i in $(seq 1 $TENANT_COUNT); do
sqs "${TENANT_AK[$i]}" "${TENANT_SK[$i]}" delete-queue \
--queue-url "${BASE_URL}/queue/${STRESS_Q}" >/dev/null 2>&1
tak=$(cat "${TMPDIR_STRESS}/tak_${i}")
tsk=$(cat "${TMPDIR_STRESS}/tsk_${i}")
tid=$(cat "${TMPDIR_STRESS}/tid_${i}")
sqs "$tak" "$tsk" delete-queue \
--queue-url "${BASE_URL}/${tid}/${STRESS_Q}" >/dev/null 2>&1
done
sqs "${TENANT_AK[1]}" "${TENANT_SK[1]}" delete-queue \
--queue-url "${BASE_URL}/queue/${BURST_Q}" >/dev/null 2>&1
sqs "${TENANT_AK[1]}" "${TENANT_SK[1]}" delete-queue \
--queue-url "${BASE_URL}/queue/${MIXED_Q}" >/dev/null 2>&1
sqs "$T1_AK" "$T1_SK" delete-queue \
--queue-url "$BURST_QURL" >/dev/null 2>&1
sqs "$T1_AK" "$T1_SK" delete-queue \
--queue-url "$MIXED_QURL" >/dev/null 2>&1
echo " Очереди и тенанты очищены"
echo ""