fix: stress_test.sh — правильный формат queue URL, парсинг tenant API, subshell-safe массивы
This commit is contained in:
+97
-53
@@ -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 ""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user