chore: переместить исторические doc в doc/legacy, удалить tests и пустые легаси-папки
This commit is contained in:
@@ -1,5 +0,0 @@
|
|||||||
# ⚠️ ЛЕГАСИ — НЕ ИСПОЛЬЗОВАТЬ
|
|
||||||
|
|
||||||
Исторические решения этапа разработки (апрель 2026).
|
|
||||||
**ЭТИМ НЕ РУКОВОДСТВОВАТЬСЯ.** Только для истории.
|
|
||||||
Помечено: 2026-08-13.
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
# ⚠️ ЛЕГАСИ — НЕ ИСПОЛЬЗОВАТЬ
|
|
||||||
|
|
||||||
Исторические инциденты/ошибки этапа разработки.
|
|
||||||
**ЭТИМ НЕ РУКОВОДСТВОВАТЬСЯ.** Только для истории.
|
|
||||||
Помечено: 2026-08-13.
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
# ⚠️ ЛЕГАСИ — НЕ ИСПОЛЬЗОВАТЬ
|
|
||||||
|
|
||||||
Исторические размышления/заметки этапа разработки.
|
|
||||||
**ЭТИМ НЕ РУКОВОДСТВОВАТЬСЯ.** Только для истории.
|
|
||||||
Помечено: 2026-08-13.
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
# ⚠️ ЛЕГАСИ — НЕ ИСПОЛЬЗОВАТЬ
|
|
||||||
|
|
||||||
Старые тестовые скрипты (этап k8s-деплоя, домен qu.kube5s.ru).
|
|
||||||
**ЭТИМ НЕ РУКОВОДСТВОВАТЬСЯ.** Только для истории.
|
|
||||||
Помечено: 2026-08-13.
|
|
||||||
@@ -1,380 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
# Updated: 2026-04-12 00:00 UTC
|
|
||||||
# benchmark_compare_32k.sh — сравнительный benchmark Yandex MQ vs shared-sqs только до 32KB.
|
|
||||||
|
|
||||||
set -uo pipefail
|
|
||||||
|
|
||||||
# ysqs выполняет aws sqs для Yandex MQ.
|
|
||||||
ysqs() {
|
|
||||||
AWS_ACCESS_KEY_ID="$Y_AK" AWS_SECRET_ACCESS_KEY="$Y_SK" \
|
|
||||||
AWS_DEFAULT_REGION="$Y_REGION" \
|
|
||||||
aws --endpoint-url "$Y_EP" --output json sqs "$@" 2>&1
|
|
||||||
}
|
|
||||||
|
|
||||||
# osqs выполняет aws sqs для shared-sqs.
|
|
||||||
osqs() {
|
|
||||||
AWS_ACCESS_KEY_ID="$O_AK" AWS_SECRET_ACCESS_KEY="$O_SK" \
|
|
||||||
AWS_DEFAULT_REGION="$O_REGION" \
|
|
||||||
aws --endpoint-url "$O_EP" --output json sqs "$@" 2>&1
|
|
||||||
}
|
|
||||||
|
|
||||||
# ms_now возвращает текущее время в миллисекундах.
|
|
||||||
ms_now() {
|
|
||||||
date +%s%3N
|
|
||||||
}
|
|
||||||
|
|
||||||
# calc_stats считает min/avg/max/p95 по файлу со значениями latency.
|
|
||||||
calc_stats() {
|
|
||||||
local file_path="$1"
|
|
||||||
sort -n "$file_path" | awk '
|
|
||||||
{ values[NR] = $1; total += $1 }
|
|
||||||
END {
|
|
||||||
count = NR
|
|
||||||
if (count == 0) {
|
|
||||||
print "0 0 0 0"
|
|
||||||
exit
|
|
||||||
}
|
|
||||||
avg = int(total / count)
|
|
||||||
p95_index = int(count * 0.95)
|
|
||||||
if (p95_index < 1) p95_index = 1
|
|
||||||
printf "%d %d %d %d\n", values[1], avg, values[count], values[p95_index]
|
|
||||||
}'
|
|
||||||
}
|
|
||||||
|
|
||||||
# gen_payload создаёт payload фиксированного размера для SendMessage.
|
|
||||||
gen_payload() {
|
|
||||||
local payload_size="$1"
|
|
||||||
head -c "$payload_size" /dev/urandom | base64 | head -c "$payload_size"
|
|
||||||
}
|
|
||||||
|
|
||||||
# run_latency_series выполняет команду несколько раз и пишет latency в файл.
|
|
||||||
run_latency_series() {
|
|
||||||
local output_file="$1"
|
|
||||||
local iterations="$2"
|
|
||||||
shift 2
|
|
||||||
|
|
||||||
: > "$output_file"
|
|
||||||
for _ in $(seq 1 "$iterations"); do
|
|
||||||
local started_at
|
|
||||||
local attempt=1
|
|
||||||
while [[ "$attempt" -le 3 ]]; do
|
|
||||||
started_at=$(ms_now)
|
|
||||||
if "$@" > /dev/null; then
|
|
||||||
echo $(( $(ms_now) - started_at )) >> "$output_file"
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
attempt=$((attempt + 1))
|
|
||||||
done
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
# extract_receipt_handles возвращает receipt handles из ответа receive-message.
|
|
||||||
extract_receipt_handles() {
|
|
||||||
python3 -c 'import json,sys
|
|
||||||
data=json.load(sys.stdin)
|
|
||||||
for msg in data.get("Messages", []):
|
|
||||||
handle = msg.get("ReceiptHandle")
|
|
||||||
if handle:
|
|
||||||
print(handle)
|
|
||||||
'
|
|
||||||
}
|
|
||||||
|
|
||||||
# extract_delete_entries превращает receive output в batch delete entries JSON.
|
|
||||||
extract_delete_entries() {
|
|
||||||
python3 -c 'import json,sys
|
|
||||||
data=json.load(sys.stdin)
|
|
||||||
entries=[]
|
|
||||||
for index,msg in enumerate(data.get("Messages", []), start=1):
|
|
||||||
handle = msg.get("ReceiptHandle")
|
|
||||||
if handle:
|
|
||||||
entries.append({"Id": str(index), "ReceiptHandle": handle})
|
|
||||||
print(json.dumps(entries))
|
|
||||||
'
|
|
||||||
}
|
|
||||||
|
|
||||||
# extract_visibility_entries превращает receive output в batch visibility entries JSON.
|
|
||||||
extract_visibility_entries() {
|
|
||||||
python3 -c 'import json,sys
|
|
||||||
data=json.load(sys.stdin)
|
|
||||||
entries=[]
|
|
||||||
for index,msg in enumerate(data.get("Messages", []), start=1):
|
|
||||||
handle = msg.get("ReceiptHandle")
|
|
||||||
if handle:
|
|
||||||
entries.append({"Id": str(index), "ReceiptHandle": handle, "VisibilityTimeout": 45})
|
|
||||||
print(json.dumps(entries))
|
|
||||||
'
|
|
||||||
}
|
|
||||||
|
|
||||||
# cleanup удаляет временные очереди и временный каталог, чтобы не оставлять мусор.
|
|
||||||
cleanup() {
|
|
||||||
set +e
|
|
||||||
if [[ -n "${Y_QURL:-}" ]]; then
|
|
||||||
ysqs delete-queue --queue-url "$Y_QURL" > /dev/null 2>&1 || true
|
|
||||||
fi
|
|
||||||
if [[ -n "${O_QURL:-}" ]]; then
|
|
||||||
osqs delete-queue --queue-url "$O_QURL" > /dev/null 2>&1 || true
|
|
||||||
fi
|
|
||||||
rm -rf "$TMPDIR"
|
|
||||||
}
|
|
||||||
|
|
||||||
trap cleanup EXIT
|
|
||||||
|
|
||||||
Y_AK="YCAJEQDz_Eg_i4C4M7TAen2fd"
|
|
||||||
Y_SK="YCMDfD8OKFK51knPyydwQOYts7Q81_3YBhv4sd_j"
|
|
||||||
Y_REGION="ru-central1"
|
|
||||||
Y_EP="https://message-queue.api.cloud.yandex.net"
|
|
||||||
|
|
||||||
O_AK="SSAK-ed0b0c64dcc135adad9e11be"
|
|
||||||
O_SK="f917c133e4ad74cf37c8e3ba29d74a39f42f117a202bd672436c92fdb6b6f3dc"
|
|
||||||
O_REGION="us-east-1"
|
|
||||||
O_EP="https://qu.kube5s.ru"
|
|
||||||
O_TID="t-e0ce25e83be94c58"
|
|
||||||
|
|
||||||
ITERATIONS=7
|
|
||||||
THROUGHPUT_WORKERS=5
|
|
||||||
THROUGHPUT_MSGS=10
|
|
||||||
TS=$(date +%s)
|
|
||||||
TMPDIR=$(mktemp -d /tmp/sqs_compare_32k_XXXXX)
|
|
||||||
|
|
||||||
Y_QNAME="bench-32k-${TS}"
|
|
||||||
O_QNAME="bench-32k-${TS}"
|
|
||||||
|
|
||||||
Y_CREATE_RAW=$(ysqs create-queue --queue-name "$Y_QNAME" --attributes '{"VisibilityTimeout":"30","ReceiveMessageWaitTimeSeconds":"0"}')
|
|
||||||
Y_QURL=$(echo "$Y_CREATE_RAW" | python3 -c 'import json,sys; print(json.load(sys.stdin)["QueueUrl"])')
|
|
||||||
|
|
||||||
osqs create-queue --queue-name "$O_QNAME" --attributes '{"VisibilityTimeout":"30","ReceiveMessageWaitTimeSeconds":"0"}' > /dev/null
|
|
||||||
O_QURL="${O_EP}/${O_TID}/${O_QNAME}"
|
|
||||||
|
|
||||||
PAYLOAD_1K_FILE="$TMPDIR/payload_1k.txt"
|
|
||||||
PAYLOAD_10K_FILE="$TMPDIR/payload_10k.txt"
|
|
||||||
PAYLOAD_32K_FILE="$TMPDIR/payload_32k.txt"
|
|
||||||
gen_payload 1024 > "$PAYLOAD_1K_FILE"
|
|
||||||
gen_payload 10240 > "$PAYLOAD_10K_FILE"
|
|
||||||
gen_payload 32768 > "$PAYLOAD_32K_FILE"
|
|
||||||
|
|
||||||
echo "# Benchmark 32KB Max"
|
|
||||||
echo "generated_at=$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
||||||
echo "iterations=${ITERATIONS}"
|
|
||||||
echo "throughput_workers=${THROUGHPUT_WORKERS}"
|
|
||||||
echo "throughput_msgs=${THROUGHPUT_MSGS}"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
echo "## api_latency_ms"
|
|
||||||
echo "operation|provider|min|avg|max|p95|note"
|
|
||||||
|
|
||||||
run_latency_series "$TMPDIR/y_get_url.txt" "$ITERATIONS" ysqs get-queue-url --queue-name "$Y_QNAME"
|
|
||||||
run_latency_series "$TMPDIR/o_get_url.txt" "$ITERATIONS" osqs get-queue-url --queue-name "$O_QNAME"
|
|
||||||
run_latency_series "$TMPDIR/y_list.txt" "$ITERATIONS" ysqs list-queues --queue-name-prefix bench-32k-
|
|
||||||
run_latency_series "$TMPDIR/o_list.txt" "$ITERATIONS" osqs list-queues --queue-name-prefix bench-32k-
|
|
||||||
run_latency_series "$TMPDIR/y_attr.txt" "$ITERATIONS" ysqs get-queue-attributes --queue-url "$Y_QURL" --attribute-names All
|
|
||||||
run_latency_series "$TMPDIR/o_attr.txt" "$ITERATIONS" osqs get-queue-attributes --queue-url "$O_QURL" --attribute-names All
|
|
||||||
run_latency_series "$TMPDIR/y_set_attr.txt" "$ITERATIONS" ysqs set-queue-attributes --queue-url "$Y_QURL" --attributes '{"VisibilityTimeout":"45"}'
|
|
||||||
run_latency_series "$TMPDIR/o_set_attr.txt" "$ITERATIONS" osqs set-queue-attributes --queue-url "$O_QURL" --attributes '{"VisibilityTimeout":"45"}'
|
|
||||||
|
|
||||||
run_latency_series "$TMPDIR/y_send_1k.txt" "$ITERATIONS" ysqs send-message --queue-url "$Y_QURL" --message-body "file://$PAYLOAD_1K_FILE"
|
|
||||||
run_latency_series "$TMPDIR/o_send_1k.txt" "$ITERATIONS" osqs send-message --queue-url "$O_QURL" --message-body "file://$PAYLOAD_1K_FILE"
|
|
||||||
run_latency_series "$TMPDIR/y_send_10k.txt" "$ITERATIONS" ysqs send-message --queue-url "$Y_QURL" --message-body "file://$PAYLOAD_10K_FILE"
|
|
||||||
run_latency_series "$TMPDIR/o_send_10k.txt" "$ITERATIONS" osqs send-message --queue-url "$O_QURL" --message-body "file://$PAYLOAD_10K_FILE"
|
|
||||||
run_latency_series "$TMPDIR/y_send_32k.txt" "$ITERATIONS" ysqs send-message --queue-url "$Y_QURL" --message-body "file://$PAYLOAD_32K_FILE"
|
|
||||||
run_latency_series "$TMPDIR/o_send_32k.txt" "$ITERATIONS" osqs send-message --queue-url "$O_QURL" --message-body "file://$PAYLOAD_32K_FILE"
|
|
||||||
|
|
||||||
BATCH_ENTRIES='[{"Id":"1","MessageBody":"b1"},{"Id":"2","MessageBody":"b2"},{"Id":"3","MessageBody":"b3"},{"Id":"4","MessageBody":"b4"},{"Id":"5","MessageBody":"b5"},{"Id":"6","MessageBody":"b6"},{"Id":"7","MessageBody":"b7"},{"Id":"8","MessageBody":"b8"},{"Id":"9","MessageBody":"b9"},{"Id":"10","MessageBody":"b10"}]'
|
|
||||||
run_latency_series "$TMPDIR/y_batch_send.txt" "$ITERATIONS" ysqs send-message-batch --queue-url "$Y_QURL" --entries "$BATCH_ENTRIES"
|
|
||||||
run_latency_series "$TMPDIR/o_batch_send.txt" "$ITERATIONS" osqs send-message-batch --queue-url "$O_QURL" --entries "$BATCH_ENTRIES"
|
|
||||||
|
|
||||||
for index in $(seq 1 "$ITERATIONS"); do
|
|
||||||
ysqs send-message --queue-url "$Y_QURL" --message-body "recv-y-$index" > /dev/null
|
|
||||||
osqs send-message --queue-url "$O_QURL" --message-body "recv-o-$index" > /dev/null
|
|
||||||
done
|
|
||||||
|
|
||||||
: > "$TMPDIR/y_receive.txt"
|
|
||||||
: > "$TMPDIR/o_receive.txt"
|
|
||||||
: > "$TMPDIR/y_delete.txt"
|
|
||||||
: > "$TMPDIR/o_delete.txt"
|
|
||||||
for _ in $(seq 1 "$ITERATIONS"); do
|
|
||||||
started_at=$(ms_now)
|
|
||||||
y_receive_raw=$(ysqs receive-message --queue-url "$Y_QURL" --max-number-of-messages 1 --wait-time-seconds 1)
|
|
||||||
echo $(( $(ms_now) - started_at )) >> "$TMPDIR/y_receive.txt"
|
|
||||||
y_handle=$(echo "$y_receive_raw" | extract_receipt_handles | head -1)
|
|
||||||
if [[ -n "$y_handle" ]]; then
|
|
||||||
started_at=$(ms_now)
|
|
||||||
ysqs delete-message --queue-url "$Y_QURL" --receipt-handle "$y_handle" > /dev/null
|
|
||||||
echo $(( $(ms_now) - started_at )) >> "$TMPDIR/y_delete.txt"
|
|
||||||
fi
|
|
||||||
|
|
||||||
started_at=$(ms_now)
|
|
||||||
o_receive_raw=$(osqs receive-message --queue-url "$O_QURL" --max-number-of-messages 1 --wait-time-seconds 1)
|
|
||||||
echo $(( $(ms_now) - started_at )) >> "$TMPDIR/o_receive.txt"
|
|
||||||
o_handle=$(echo "$o_receive_raw" | extract_receipt_handles | head -1)
|
|
||||||
if [[ -n "$o_handle" ]]; then
|
|
||||||
started_at=$(ms_now)
|
|
||||||
osqs delete-message --queue-url "$O_QURL" --receipt-handle "$o_handle" > /dev/null
|
|
||||||
echo $(( $(ms_now) - started_at )) >> "$TMPDIR/o_delete.txt"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
for index in $(seq 1 10); do
|
|
||||||
ysqs send-message --queue-url "$Y_QURL" --message-body "batch-delete-y-$index" > /dev/null
|
|
||||||
osqs send-message --queue-url "$O_QURL" --message-body "batch-delete-o-$index" > /dev/null
|
|
||||||
done
|
|
||||||
y_batch_delete_receive=$(ysqs receive-message --queue-url "$Y_QURL" --max-number-of-messages 10 --wait-time-seconds 1)
|
|
||||||
o_batch_delete_receive=$(osqs receive-message --queue-url "$O_QURL" --max-number-of-messages 10 --wait-time-seconds 1)
|
|
||||||
y_delete_entries=$(echo "$y_batch_delete_receive" | extract_delete_entries)
|
|
||||||
o_delete_entries=$(echo "$o_batch_delete_receive" | extract_delete_entries)
|
|
||||||
: > "$TMPDIR/y_batch_delete.txt"
|
|
||||||
: > "$TMPDIR/o_batch_delete.txt"
|
|
||||||
for _ in $(seq 1 "$ITERATIONS"); do
|
|
||||||
for index in $(seq 1 10); do
|
|
||||||
ysqs send-message --queue-url "$Y_QURL" --message-body "batch-delete-y-${RANDOM}-${index}" > /dev/null
|
|
||||||
osqs send-message --queue-url "$O_QURL" --message-body "batch-delete-o-${RANDOM}-${index}" > /dev/null
|
|
||||||
done
|
|
||||||
y_batch_delete_receive=$(ysqs receive-message --queue-url "$Y_QURL" --max-number-of-messages 10 --wait-time-seconds 1)
|
|
||||||
o_batch_delete_receive=$(osqs receive-message --queue-url "$O_QURL" --max-number-of-messages 10 --wait-time-seconds 1)
|
|
||||||
y_delete_entries=$(echo "$y_batch_delete_receive" | extract_delete_entries)
|
|
||||||
o_delete_entries=$(echo "$o_batch_delete_receive" | extract_delete_entries)
|
|
||||||
started_at=$(ms_now)
|
|
||||||
ysqs delete-message-batch --queue-url "$Y_QURL" --entries "$y_delete_entries" > /dev/null
|
|
||||||
echo $(( $(ms_now) - started_at )) >> "$TMPDIR/y_batch_delete.txt"
|
|
||||||
started_at=$(ms_now)
|
|
||||||
osqs delete-message-batch --queue-url "$O_QURL" --entries "$o_delete_entries" > /dev/null
|
|
||||||
echo $(( $(ms_now) - started_at )) >> "$TMPDIR/o_batch_delete.txt"
|
|
||||||
done
|
|
||||||
|
|
||||||
for index in $(seq 1 "$ITERATIONS"); do
|
|
||||||
ysqs send-message --queue-url "$Y_QURL" --message-body "visibility-y-$index" > /dev/null
|
|
||||||
osqs send-message --queue-url "$O_QURL" --message-body "visibility-o-$index" > /dev/null
|
|
||||||
done
|
|
||||||
|
|
||||||
: > "$TMPDIR/y_visibility.txt"
|
|
||||||
: > "$TMPDIR/o_visibility.txt"
|
|
||||||
for _ in $(seq 1 "$ITERATIONS"); do
|
|
||||||
y_visibility_receive=$(ysqs receive-message --queue-url "$Y_QURL" --max-number-of-messages 1 --wait-time-seconds 1)
|
|
||||||
y_visibility_handle=$(echo "$y_visibility_receive" | extract_receipt_handles | head -1)
|
|
||||||
if [[ -n "$y_visibility_handle" ]]; then
|
|
||||||
started_at=$(ms_now)
|
|
||||||
ysqs change-message-visibility --queue-url "$Y_QURL" --receipt-handle "$y_visibility_handle" --visibility-timeout 45 > /dev/null
|
|
||||||
echo $(( $(ms_now) - started_at )) >> "$TMPDIR/y_visibility.txt"
|
|
||||||
fi
|
|
||||||
|
|
||||||
o_visibility_receive=$(osqs receive-message --queue-url "$O_QURL" --max-number-of-messages 1 --wait-time-seconds 1)
|
|
||||||
o_visibility_handle=$(echo "$o_visibility_receive" | extract_receipt_handles | head -1)
|
|
||||||
if [[ -n "$o_visibility_handle" ]]; then
|
|
||||||
started_at=$(ms_now)
|
|
||||||
osqs change-message-visibility --queue-url "$O_QURL" --receipt-handle "$o_visibility_handle" --visibility-timeout 45 > /dev/null
|
|
||||||
echo $(( $(ms_now) - started_at )) >> "$TMPDIR/o_visibility.txt"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
|
|
||||||
: > "$TMPDIR/y_visibility_batch.txt"
|
|
||||||
: > "$TMPDIR/o_visibility_batch.txt"
|
|
||||||
for _ in $(seq 1 "$ITERATIONS"); do
|
|
||||||
for index in $(seq 1 10); do
|
|
||||||
ysqs send-message --queue-url "$Y_QURL" --message-body "visibility-batch-y-${RANDOM}-${index}" > /dev/null
|
|
||||||
osqs send-message --queue-url "$O_QURL" --message-body "visibility-batch-o-${RANDOM}-${index}" > /dev/null
|
|
||||||
done
|
|
||||||
y_visibility_batch_receive=$(ysqs receive-message --queue-url "$Y_QURL" --max-number-of-messages 10 --wait-time-seconds 1)
|
|
||||||
o_visibility_batch_receive=$(osqs receive-message --queue-url "$O_QURL" --max-number-of-messages 10 --wait-time-seconds 1)
|
|
||||||
y_visibility_entries=$(echo "$y_visibility_batch_receive" | extract_visibility_entries)
|
|
||||||
o_visibility_entries=$(echo "$o_visibility_batch_receive" | extract_visibility_entries)
|
|
||||||
started_at=$(ms_now)
|
|
||||||
ysqs change-message-visibility-batch --queue-url "$Y_QURL" --entries "$y_visibility_entries" > /dev/null
|
|
||||||
echo $(( $(ms_now) - started_at )) >> "$TMPDIR/y_visibility_batch.txt"
|
|
||||||
started_at=$(ms_now)
|
|
||||||
osqs change-message-visibility-batch --queue-url "$O_QURL" --entries "$o_visibility_entries" > /dev/null
|
|
||||||
echo $(( $(ms_now) - started_at )) >> "$TMPDIR/o_visibility_batch.txt"
|
|
||||||
done
|
|
||||||
|
|
||||||
: > "$TMPDIR/y_purge.txt"
|
|
||||||
: > "$TMPDIR/o_purge.txt"
|
|
||||||
ysqs send-message --queue-url "$Y_QURL" --message-body "purge-y-${RANDOM}" > /dev/null
|
|
||||||
osqs send-message --queue-url "$O_QURL" --message-body "purge-o-${RANDOM}" > /dev/null
|
|
||||||
started_at=$(ms_now)
|
|
||||||
ysqs purge-queue --queue-url "$Y_QURL" > /dev/null
|
|
||||||
echo $(( $(ms_now) - started_at )) >> "$TMPDIR/y_purge.txt"
|
|
||||||
started_at=$(ms_now)
|
|
||||||
osqs purge-queue --queue-url "$O_QURL" > /dev/null
|
|
||||||
echo $(( $(ms_now) - started_at )) >> "$TMPDIR/o_purge.txt"
|
|
||||||
|
|
||||||
print_stats_row() {
|
|
||||||
local operation_name="$1"
|
|
||||||
local provider_name="$2"
|
|
||||||
local stats_file="$3"
|
|
||||||
local note="$4"
|
|
||||||
local stats_line
|
|
||||||
stats_line=$(calc_stats "$stats_file")
|
|
||||||
echo "${operation_name}|${provider_name}|$(echo "$stats_line" | awk '{print $1"|"$2"|"$3"|"$4}')|${note}"
|
|
||||||
}
|
|
||||||
|
|
||||||
print_stats_row "GetQueueUrl" "yandex" "$TMPDIR/y_get_url.txt" "control-plane"
|
|
||||||
print_stats_row "GetQueueUrl" "shared-sqs" "$TMPDIR/o_get_url.txt" "control-plane"
|
|
||||||
print_stats_row "ListQueues" "yandex" "$TMPDIR/y_list.txt" "control-plane"
|
|
||||||
print_stats_row "ListQueues" "shared-sqs" "$TMPDIR/o_list.txt" "control-plane"
|
|
||||||
print_stats_row "GetQueueAttributes" "yandex" "$TMPDIR/y_attr.txt" "control-plane"
|
|
||||||
print_stats_row "GetQueueAttributes" "shared-sqs" "$TMPDIR/o_attr.txt" "control-plane"
|
|
||||||
print_stats_row "SetQueueAttributes" "yandex" "$TMPDIR/y_set_attr.txt" "control-plane"
|
|
||||||
print_stats_row "SetQueueAttributes" "shared-sqs" "$TMPDIR/o_set_attr.txt" "control-plane"
|
|
||||||
print_stats_row "SendMessage-1KB" "yandex" "$TMPDIR/y_send_1k.txt" "payload"
|
|
||||||
print_stats_row "SendMessage-1KB" "shared-sqs" "$TMPDIR/o_send_1k.txt" "payload"
|
|
||||||
print_stats_row "SendMessage-10KB" "yandex" "$TMPDIR/y_send_10k.txt" "payload"
|
|
||||||
print_stats_row "SendMessage-10KB" "shared-sqs" "$TMPDIR/o_send_10k.txt" "payload"
|
|
||||||
print_stats_row "SendMessage-32KB" "yandex" "$TMPDIR/y_send_32k.txt" "payload"
|
|
||||||
print_stats_row "SendMessage-32KB" "shared-sqs" "$TMPDIR/o_send_32k.txt" "payload"
|
|
||||||
print_stats_row "SendMessageBatch-10" "yandex" "$TMPDIR/y_batch_send.txt" "data-plane"
|
|
||||||
print_stats_row "SendMessageBatch-10" "shared-sqs" "$TMPDIR/o_batch_send.txt" "data-plane"
|
|
||||||
print_stats_row "ReceiveMessage" "yandex" "$TMPDIR/y_receive.txt" "data-plane"
|
|
||||||
print_stats_row "ReceiveMessage" "shared-sqs" "$TMPDIR/o_receive.txt" "data-plane"
|
|
||||||
print_stats_row "DeleteMessage" "yandex" "$TMPDIR/y_delete.txt" "data-plane"
|
|
||||||
print_stats_row "DeleteMessage" "shared-sqs" "$TMPDIR/o_delete.txt" "data-plane"
|
|
||||||
print_stats_row "DeleteMessageBatch-10" "yandex" "$TMPDIR/y_batch_delete.txt" "data-plane"
|
|
||||||
print_stats_row "DeleteMessageBatch-10" "shared-sqs" "$TMPDIR/o_batch_delete.txt" "data-plane"
|
|
||||||
print_stats_row "ChangeMessageVisibility" "yandex" "$TMPDIR/y_visibility.txt" "data-plane"
|
|
||||||
print_stats_row "ChangeMessageVisibility" "shared-sqs" "$TMPDIR/o_visibility.txt" "data-plane"
|
|
||||||
print_stats_row "ChangeMessageVisibilityBatch-10" "yandex" "$TMPDIR/y_visibility_batch.txt" "data-plane"
|
|
||||||
print_stats_row "ChangeMessageVisibilityBatch-10" "shared-sqs" "$TMPDIR/o_visibility_batch.txt" "data-plane"
|
|
||||||
print_stats_row "PurgeQueue" "yandex" "$TMPDIR/y_purge.txt" "control-plane"
|
|
||||||
print_stats_row "PurgeQueue" "shared-sqs" "$TMPDIR/o_purge.txt" "control-plane"
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "## throughput_send_1kb"
|
|
||||||
echo "provider|ok|total_ms|rps|workers|msgs_per_worker"
|
|
||||||
|
|
||||||
run_throughput() {
|
|
||||||
local provider_name="$1"
|
|
||||||
local queue_url="$2"
|
|
||||||
local output_prefix="$3"
|
|
||||||
local runner="$4"
|
|
||||||
local started_at
|
|
||||||
started_at=$(ms_now)
|
|
||||||
for worker in $(seq 1 "$THROUGHPUT_WORKERS"); do
|
|
||||||
(
|
|
||||||
ok_count=0
|
|
||||||
for msg_index in $(seq 1 "$THROUGHPUT_MSGS"); do
|
|
||||||
if "$runner" send-message --queue-url "$queue_url" --message-body "file://$PAYLOAD_1K_FILE" > /dev/null; then
|
|
||||||
ok_count=$((ok_count + 1))
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
echo "$ok_count" > "$TMPDIR/${output_prefix}_${worker}.txt"
|
|
||||||
) &
|
|
||||||
done
|
|
||||||
wait
|
|
||||||
local total_ms=$(( $(ms_now) - started_at ))
|
|
||||||
local ok_total=0
|
|
||||||
for worker in $(seq 1 "$THROUGHPUT_WORKERS"); do
|
|
||||||
ok_total=$((ok_total + $(cat "$TMPDIR/${output_prefix}_${worker}.txt")))
|
|
||||||
done
|
|
||||||
local rps=$(( ok_total * 1000 / (total_ms + 1) ))
|
|
||||||
echo "${provider_name}|${ok_total}|${total_ms}|${rps}|${THROUGHPUT_WORKERS}|${THROUGHPUT_MSGS}"
|
|
||||||
}
|
|
||||||
|
|
||||||
run_throughput "yandex" "$Y_QURL" "tp_y" ysqs
|
|
||||||
run_throughput "shared-sqs" "$O_QURL" "tp_o" osqs
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "## unsupported_in_yandex"
|
|
||||||
echo "TagQueue"
|
|
||||||
echo "UntagQueue"
|
|
||||||
echo "ListQueueTags"
|
|
||||||
echo ""
|
|
||||||
echo "## excluded_by_scope"
|
|
||||||
echo "SendMessage payloads above 32KB are intentionally excluded from this report."
|
|
||||||
@@ -1,788 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
# benchmark_full.sh — полный сравнительный бенчмарк Yandex MQ vs shared-sqs
|
|
||||||
# Все 17 SQS команд, latency, throughput, message sizes, batch, long poll, sustained
|
|
||||||
# Запуск из ЛОКАЛИ для равных условий
|
|
||||||
# Created: 2026-04-11
|
|
||||||
|
|
||||||
set -uo pipefail
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════
|
|
||||||
# CREDENTIALS
|
|
||||||
# ═══════════════════════════════════════
|
|
||||||
|
|
||||||
# Yandex MQ
|
|
||||||
Y_AK="YCAJEQDz_Eg_i4C4M7TAen2fd"
|
|
||||||
Y_SK="YCMDfD8OKFK51knPyydwQOYts7Q81_3YBhv4sd_j"
|
|
||||||
Y_REGION="ru-central1"
|
|
||||||
Y_EP="https://message-queue.api.cloud.yandex.net"
|
|
||||||
|
|
||||||
# Our SQS (shared-sqs)
|
|
||||||
O_AK="SSAK-ed0b0c64dcc135adad9e11be"
|
|
||||||
O_SK="f917c133e4ad74cf37c8e3ba29d74a39f42f117a202bd672436c92fdb6b6f3dc"
|
|
||||||
O_REGION="us-east-1"
|
|
||||||
O_EP="https://qu.kube5s.ru"
|
|
||||||
O_TID="t-e0ce25e83be94c58"
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════
|
|
||||||
# ПАРАМЕТРЫ
|
|
||||||
# ═══════════════════════════════════════
|
|
||||||
LATENCY_ITERS=5 # итераций для замера latency каждой команды
|
|
||||||
THROUGHPUT_WORKERS="1 5 10" # уровни параллельности
|
|
||||||
THROUGHPUT_MSGS=10 # сообщений на воркер
|
|
||||||
SUSTAINED_DURATION=120 # секунд sustained load
|
|
||||||
QUEUE_FLOOD_COUNT=10 # очередей для create/delete теста
|
|
||||||
TS=$(date +%s)
|
|
||||||
|
|
||||||
TMPDIR_B=$(mktemp -d /tmp/sqs_bench_XXXXX)
|
|
||||||
trap "rm -rf $TMPDIR_B" EXIT
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════
|
|
||||||
# ХЕЛПЕРЫ
|
|
||||||
# ═══════════════════════════════════════
|
|
||||||
ysqs() {
|
|
||||||
AWS_ACCESS_KEY_ID="$Y_AK" AWS_SECRET_ACCESS_KEY="$Y_SK" \
|
|
||||||
AWS_DEFAULT_REGION="$Y_REGION" \
|
|
||||||
aws --endpoint-url "$Y_EP" --output json sqs "$@" 2>&1
|
|
||||||
}
|
|
||||||
|
|
||||||
osqs() {
|
|
||||||
AWS_ACCESS_KEY_ID="$O_AK" AWS_SECRET_ACCESS_KEY="$O_SK" \
|
|
||||||
AWS_DEFAULT_REGION="$O_REGION" \
|
|
||||||
aws --endpoint-url "$O_EP" --output json sqs "$@" 2>&1
|
|
||||||
}
|
|
||||||
|
|
||||||
ms_now() { date +%s%3N; }
|
|
||||||
|
|
||||||
# time_cmd — замеряет время выполнения команды, возвращает ms
|
|
||||||
# Использование: elapsed=$(time_cmd ysqs send-message ...)
|
|
||||||
time_cmd() {
|
|
||||||
local s=$(ms_now)
|
|
||||||
"$@" > "${TMPDIR_B}/_last_out" 2>&1
|
|
||||||
echo $(( $(ms_now) - s ))
|
|
||||||
}
|
|
||||||
|
|
||||||
# calc_stats — из файла с числами (по строке) выдаёт min/avg/max/p95
|
|
||||||
calc_stats() {
|
|
||||||
local file="$1"
|
|
||||||
sort -n "$file" | awk '
|
|
||||||
{a[NR]=$1; s+=$1}
|
|
||||||
END {
|
|
||||||
n=NR;
|
|
||||||
if(n==0){print "0 0 0 0"; exit}
|
|
||||||
avg=int(s/n);
|
|
||||||
p95_idx=int(n*0.95); if(p95_idx<1) p95_idx=1;
|
|
||||||
printf "%d %d %d %d\n", a[1], avg, a[n], a[p95_idx]
|
|
||||||
}'
|
|
||||||
}
|
|
||||||
|
|
||||||
# gen_payload — генерирует строку заданного размера в байтах
|
|
||||||
gen_payload() {
|
|
||||||
local size=$1
|
|
||||||
head -c "$size" /dev/urandom | base64 | head -c "$size"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Прогресс-индикаторы
|
|
||||||
SECTION=0
|
|
||||||
total_y_ok=0; total_y_fail=0; total_o_ok=0; total_o_fail=0
|
|
||||||
|
|
||||||
section() {
|
|
||||||
SECTION=$((SECTION + 1))
|
|
||||||
echo ""
|
|
||||||
echo "═══ $SECTION. $1 ═══"
|
|
||||||
}
|
|
||||||
|
|
||||||
echo "╔══════════════════════════════════════════════════════════════════════╗"
|
|
||||||
echo "║ FULL BENCHMARK: Yandex MQ vs shared-sqs ║"
|
|
||||||
echo "║ $(date '+%Y-%m-%d %H:%M:%S') | Из локали, равные условия ║"
|
|
||||||
echo "╚══════════════════════════════════════════════════════════════════════╝"
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════
|
|
||||||
# 0. SETUP — создаём тестовые очереди
|
|
||||||
# ═══════════════════════════════════════
|
|
||||||
section "Setup — тестовые очереди"
|
|
||||||
|
|
||||||
# Yandex
|
|
||||||
Y_QNAME="bench-${TS}"
|
|
||||||
R=$(ysqs create-queue --queue-name "$Y_QNAME" --attributes '{"VisibilityTimeout":"30","ReceiveMessageWaitTimeSeconds":"0"}')
|
|
||||||
Y_QURL=$(echo "$R" | grep -oP '"QueueUrl"\s*:\s*"\K[^"]+')
|
|
||||||
echo " Yandex queue: $Y_QURL"
|
|
||||||
|
|
||||||
# Ours
|
|
||||||
O_QNAME="bench-${TS}"
|
|
||||||
R=$(osqs create-queue --queue-name "$O_QNAME" --attributes '{"VisibilityTimeout":"30","ReceiveMessageWaitTimeSeconds":"0"}')
|
|
||||||
O_QURL="${O_EP}/${O_TID}/${O_QNAME}"
|
|
||||||
echo " Ours queue: $O_QURL"
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════
|
|
||||||
# 1. ВСЕ 17 КОМАНД — функциональная проверка
|
|
||||||
# ═══════════════════════════════════════
|
|
||||||
section "Все SQS команды — функциональная корректность"
|
|
||||||
|
|
||||||
declare -A Y_CMD_OK O_CMD_OK
|
|
||||||
|
|
||||||
run_func_test() {
|
|
||||||
local provider="$1" label="$2" cmd="$3"
|
|
||||||
local R
|
|
||||||
R=$(eval "$cmd" 2>&1)
|
|
||||||
local rc=$?
|
|
||||||
if echo "$R" | grep -qiE "error|Error|exception|Exception"; then
|
|
||||||
echo " ${provider}: ❌ ${label}"
|
|
||||||
echo " $(echo "$R" | head -1)"
|
|
||||||
return 1
|
|
||||||
else
|
|
||||||
echo " ${provider}: ✅ ${label}"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# --- SendMessage ---
|
|
||||||
echo " --- SendMessage ---"
|
|
||||||
R=$(ysqs send-message --queue-url "$Y_QURL" --message-body "func-test-1")
|
|
||||||
echo "$R" | grep -q "MessageId" && echo " Yandex: ✅ SendMessage" || echo " Yandex: ❌ SendMessage"
|
|
||||||
R=$(osqs send-message --queue-url "$O_QURL" --message-body "func-test-1")
|
|
||||||
echo "$R" | grep -q "MessageId" && echo " Ours: ✅ SendMessage" || echo " Ours: ❌ SendMessage"
|
|
||||||
|
|
||||||
# --- SendMessageBatch ---
|
|
||||||
echo " --- SendMessageBatch ---"
|
|
||||||
R=$(ysqs send-message-batch --queue-url "$Y_QURL" --entries '[{"Id":"1","MessageBody":"batch1"},{"Id":"2","MessageBody":"batch2"}]')
|
|
||||||
echo "$R" | grep -q "Successful" && echo " Yandex: ✅ SendMessageBatch" || echo " Yandex: ❌ SendMessageBatch"
|
|
||||||
R=$(osqs send-message-batch --queue-url "$O_QURL" --entries '[{"Id":"1","MessageBody":"batch1"},{"Id":"2","MessageBody":"batch2"}]')
|
|
||||||
echo "$R" | grep -q "Successful" && echo " Ours: ✅ SendMessageBatch" || echo " Ours: ❌ SendMessageBatch"
|
|
||||||
|
|
||||||
# --- ReceiveMessage ---
|
|
||||||
echo " --- ReceiveMessage ---"
|
|
||||||
R=$(ysqs receive-message --queue-url "$Y_QURL" --max-number-of-messages 1 --wait-time-seconds 2)
|
|
||||||
echo "$R" | grep -q "MessageId" && echo " Yandex: ✅ ReceiveMessage" || echo " Yandex: ❌ ReceiveMessage"
|
|
||||||
Y_HANDLE=$(echo "$R" | grep -oP '"ReceiptHandle"\s*:\s*"\K[^"]+' | head -1)
|
|
||||||
R=$(osqs receive-message --queue-url "$O_QURL" --max-number-of-messages 1 --wait-time-seconds 2)
|
|
||||||
echo "$R" | grep -q "MessageId" && echo " Ours: ✅ ReceiveMessage" || echo " Ours: ❌ ReceiveMessage"
|
|
||||||
O_HANDLE=$(echo "$R" | grep -oP '"ReceiptHandle"\s*:\s*"\K[^"]+' | head -1)
|
|
||||||
|
|
||||||
# --- ChangeMessageVisibility ---
|
|
||||||
echo " --- ChangeMessageVisibility ---"
|
|
||||||
if [[ -n "$Y_HANDLE" ]]; then
|
|
||||||
R=$(ysqs change-message-visibility --queue-url "$Y_QURL" --receipt-handle "$Y_HANDLE" --visibility-timeout 60)
|
|
||||||
echo " Yandex: ✅ ChangeMessageVisibility"
|
|
||||||
else
|
|
||||||
echo " Yandex: ⚠️ ChangeMessageVisibility (no handle)"
|
|
||||||
fi
|
|
||||||
if [[ -n "$O_HANDLE" ]]; then
|
|
||||||
R=$(osqs change-message-visibility --queue-url "$O_QURL" --receipt-handle "$O_HANDLE" --visibility-timeout 60)
|
|
||||||
echo " Ours: ✅ ChangeMessageVisibility"
|
|
||||||
else
|
|
||||||
echo " Ours: ⚠️ ChangeMessageVisibility (no handle)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# --- DeleteMessage ---
|
|
||||||
echo " --- DeleteMessage ---"
|
|
||||||
if [[ -n "$Y_HANDLE" ]]; then
|
|
||||||
R=$(ysqs delete-message --queue-url "$Y_QURL" --receipt-handle "$Y_HANDLE")
|
|
||||||
echo " Yandex: ✅ DeleteMessage"
|
|
||||||
fi
|
|
||||||
if [[ -n "$O_HANDLE" ]]; then
|
|
||||||
R=$(osqs delete-message --queue-url "$O_QURL" --receipt-handle "$O_HANDLE")
|
|
||||||
echo " Ours: ✅ DeleteMessage"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# --- ReceiveMessage + DeleteMessageBatch ---
|
|
||||||
echo " --- DeleteMessageBatch ---"
|
|
||||||
# Отправим 3 сообщения и заберём
|
|
||||||
for i in 1 2 3; do ysqs send-message --queue-url "$Y_QURL" --message-body "delbatch-$i" >/dev/null 2>&1; done
|
|
||||||
sleep 1
|
|
||||||
R=$(ysqs receive-message --queue-url "$Y_QURL" --max-number-of-messages 3 --wait-time-seconds 3)
|
|
||||||
ENTRIES=$(echo "$R" | python3 -c "
|
|
||||||
import sys,json
|
|
||||||
try:
|
|
||||||
d=json.load(sys.stdin)
|
|
||||||
msgs=d.get('Messages',[])
|
|
||||||
entries=[{'Id':str(i),'ReceiptHandle':m['ReceiptHandle']} for i,m in enumerate(msgs)]
|
|
||||||
print(json.dumps(entries))
|
|
||||||
except: print('[]')
|
|
||||||
" 2>/dev/null)
|
|
||||||
if [[ "$ENTRIES" != "[]" && -n "$ENTRIES" ]]; then
|
|
||||||
R=$(ysqs delete-message-batch --queue-url "$Y_QURL" --entries "$ENTRIES")
|
|
||||||
echo "$R" | grep -q "Successful" && echo " Yandex: ✅ DeleteMessageBatch" || echo " Yandex: ❌ DeleteMessageBatch"
|
|
||||||
else
|
|
||||||
echo " Yandex: ⚠️ DeleteMessageBatch (no messages)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
for i in 1 2 3; do osqs send-message --queue-url "$O_QURL" --message-body "delbatch-$i" >/dev/null 2>&1; done
|
|
||||||
sleep 1
|
|
||||||
R=$(osqs receive-message --queue-url "$O_QURL" --max-number-of-messages 3 --wait-time-seconds 3)
|
|
||||||
ENTRIES=$(echo "$R" | python3 -c "
|
|
||||||
import sys,json
|
|
||||||
try:
|
|
||||||
d=json.load(sys.stdin)
|
|
||||||
msgs=d.get('Messages',[])
|
|
||||||
entries=[{'Id':str(i),'ReceiptHandle':m['ReceiptHandle']} for i,m in enumerate(msgs)]
|
|
||||||
print(json.dumps(entries))
|
|
||||||
except: print('[]')
|
|
||||||
" 2>/dev/null)
|
|
||||||
if [[ "$ENTRIES" != "[]" && -n "$ENTRIES" ]]; then
|
|
||||||
R=$(osqs delete-message-batch --queue-url "$O_QURL" --entries "$ENTRIES")
|
|
||||||
echo "$R" | grep -q "Successful" && echo " Ours: ✅ DeleteMessageBatch" || echo " Ours: ❌ DeleteMessageBatch"
|
|
||||||
else
|
|
||||||
echo " Ours: ⚠️ DeleteMessageBatch (no messages)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# --- ChangeMessageVisibilityBatch ---
|
|
||||||
echo " --- ChangeMessageVisibilityBatch ---"
|
|
||||||
osqs send-message --queue-url "$O_QURL" --message-body "cvb-test" >/dev/null 2>&1
|
|
||||||
sleep 1
|
|
||||||
R=$(osqs receive-message --queue-url "$O_QURL" --max-number-of-messages 1 --wait-time-seconds 2)
|
|
||||||
CVB_H=$(echo "$R" | grep -oP '"ReceiptHandle"\s*:\s*"\K[^"]+' | head -1)
|
|
||||||
if [[ -n "$CVB_H" ]]; then
|
|
||||||
R=$(osqs change-message-visibility-batch --queue-url "$O_QURL" --entries "[{\"Id\":\"0\",\"ReceiptHandle\":\"${CVB_H}\",\"VisibilityTimeout\":120}]")
|
|
||||||
echo "$R" | grep -q "Successful" && echo " Ours: ✅ ChangeMessageVisibilityBatch" || echo " Ours: ❌ ChangeMessageVisibilityBatch"
|
|
||||||
fi
|
|
||||||
# Yandex тоже поддерживает
|
|
||||||
ysqs send-message --queue-url "$Y_QURL" --message-body "cvb-test" >/dev/null 2>&1
|
|
||||||
sleep 1
|
|
||||||
R=$(ysqs receive-message --queue-url "$Y_QURL" --max-number-of-messages 1 --wait-time-seconds 2)
|
|
||||||
CVB_H=$(echo "$R" | grep -oP '"ReceiptHandle"\s*:\s*"\K[^"]+' | head -1)
|
|
||||||
if [[ -n "$CVB_H" ]]; then
|
|
||||||
R=$(ysqs change-message-visibility-batch --queue-url "$Y_QURL" --entries "[{\"Id\":\"0\",\"ReceiptHandle\":\"${CVB_H}\",\"VisibilityTimeout\":120}]")
|
|
||||||
echo "$R" | grep -q "Successful" && echo " Yandex: ✅ ChangeMessageVisibilityBatch" || echo " Yandex: ❌ ChangeMessageVisibilityBatch"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# --- GetQueueAttributes ---
|
|
||||||
echo " --- GetQueueAttributes ---"
|
|
||||||
R=$(ysqs get-queue-attributes --queue-url "$Y_QURL" --attribute-names All)
|
|
||||||
echo "$R" | grep -q "ApproximateNumberOfMessages" && echo " Yandex: ✅ GetQueueAttributes" || echo " Yandex: ❌ GetQueueAttributes"
|
|
||||||
R=$(osqs get-queue-attributes --queue-url "$O_QURL" --attribute-names All)
|
|
||||||
echo "$R" | grep -q "ApproximateNumberOfMessages" && echo " Ours: ✅ GetQueueAttributes" || echo " Ours: ❌ GetQueueAttributes"
|
|
||||||
|
|
||||||
# --- SetQueueAttributes ---
|
|
||||||
echo " --- SetQueueAttributes ---"
|
|
||||||
R=$(ysqs set-queue-attributes --queue-url "$Y_QURL" --attributes '{"VisibilityTimeout":"45"}')
|
|
||||||
echo " Yandex: ✅ SetQueueAttributes"
|
|
||||||
R=$(osqs set-queue-attributes --queue-url "$O_QURL" --attributes '{"VisibilityTimeout":"45"}')
|
|
||||||
echo " Ours: ✅ SetQueueAttributes"
|
|
||||||
|
|
||||||
# --- GetQueueUrl ---
|
|
||||||
echo " --- GetQueueUrl ---"
|
|
||||||
R=$(ysqs get-queue-url --queue-name "$Y_QNAME")
|
|
||||||
echo "$R" | grep -q "QueueUrl" && echo " Yandex: ✅ GetQueueUrl" || echo " Yandex: ❌ GetQueueUrl"
|
|
||||||
R=$(osqs get-queue-url --queue-name "$O_QNAME")
|
|
||||||
echo "$R" | grep -q "QueueUrl" && echo " Ours: ✅ GetQueueUrl" || echo " Ours: ❌ GetQueueUrl"
|
|
||||||
|
|
||||||
# --- ListQueues ---
|
|
||||||
echo " --- ListQueues ---"
|
|
||||||
R=$(ysqs list-queues)
|
|
||||||
echo "$R" | grep -q "QueueUrls\|QueueUrl\|bench-" && echo " Yandex: ✅ ListQueues" || echo " Yandex: ❌ ListQueues"
|
|
||||||
R=$(osqs list-queues)
|
|
||||||
echo "$R" | grep -q "QueueUrls\|QueueUrl\|bench-" && echo " Ours: ✅ ListQueues" || echo " Ours: ❌ ListQueues"
|
|
||||||
|
|
||||||
# --- PurgeQueue ---
|
|
||||||
echo " --- PurgeQueue ---"
|
|
||||||
ysqs send-message --queue-url "$Y_QURL" --message-body "purge-me" >/dev/null 2>&1
|
|
||||||
R=$(ysqs purge-queue --queue-url "$Y_QURL" 2>&1)
|
|
||||||
echo "$R" | grep -qi "error" && echo " Yandex: ❌ PurgeQueue ($R)" || echo " Yandex: ✅ PurgeQueue"
|
|
||||||
osqs send-message --queue-url "$O_QURL" --message-body "purge-me" >/dev/null 2>&1
|
|
||||||
R=$(osqs purge-queue --queue-url "$O_QURL" 2>&1)
|
|
||||||
echo "$R" | grep -qi "error" && echo " Ours: ❌ PurgeQueue ($R)" || echo " Ours: ✅ PurgeQueue"
|
|
||||||
|
|
||||||
# --- TagQueue / UntagQueue / ListQueueTags ---
|
|
||||||
echo " --- TagQueue (Ours only — Yandex не поддерживает) ---"
|
|
||||||
R=$(osqs tag-queue --queue-url "$O_QURL" --tags '{"env":"bench","test":"true"}' 2>&1)
|
|
||||||
echo "$R" | grep -qi "error" && echo " Ours: ❌ TagQueue" || echo " Ours: ✅ TagQueue"
|
|
||||||
echo " Yandex: ⚠️ TagQueue — not supported"
|
|
||||||
|
|
||||||
echo " --- ListQueueTags ---"
|
|
||||||
R=$(osqs list-queue-tags --queue-url "$O_QURL" 2>&1)
|
|
||||||
echo "$R" | grep -q "env" && echo " Ours: ✅ ListQueueTags" || echo " Ours: ❌ ListQueueTags"
|
|
||||||
echo " Yandex: ⚠️ ListQueueTags — not supported"
|
|
||||||
|
|
||||||
echo " --- UntagQueue ---"
|
|
||||||
R=$(osqs untag-queue --queue-url "$O_QURL" --tag-keys '["env"]' 2>&1)
|
|
||||||
echo "$R" | grep -qi "error" && echo " Ours: ❌ UntagQueue" || echo " Ours: ❌ UntagQueue"
|
|
||||||
echo " Ours: ✅ UntagQueue"
|
|
||||||
echo " Yandex: ⚠️ UntagQueue — not supported"
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════
|
|
||||||
# 2. LATENCY PER COMMAND — avg/min/max/p95
|
|
||||||
# ═══════════════════════════════════════
|
|
||||||
section "Latency per command (${LATENCY_ITERS} iters each)"
|
|
||||||
|
|
||||||
# Массив результатов для финальной таблицы
|
|
||||||
declare -A Y_LAT O_LAT
|
|
||||||
|
|
||||||
bench_latency() {
|
|
||||||
local name="$1" provider="$2" iterations="$3"
|
|
||||||
shift 3
|
|
||||||
local file="${TMPDIR_B}/lat_${provider}_${name}"
|
|
||||||
> "$file"
|
|
||||||
for i in $(seq 1 "$iterations"); do
|
|
||||||
local s=$(ms_now)
|
|
||||||
eval "$@" > /dev/null 2>&1
|
|
||||||
echo $(( $(ms_now) - s )) >> "$file"
|
|
||||||
echo -n "."
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
# SendMessage latency
|
|
||||||
echo -n " SendMessage — Y:"
|
|
||||||
bench_latency "send" "y" "$LATENCY_ITERS" "ysqs send-message --queue-url '$Y_QURL' --message-body 'latency-test'"
|
|
||||||
Y_SEND_STATS=$(calc_stats "${TMPDIR_B}/lat_y_send")
|
|
||||||
echo -n " O:"
|
|
||||||
bench_latency "send" "o" "$LATENCY_ITERS" "osqs send-message --queue-url '$O_QURL' --message-body 'latency-test'"
|
|
||||||
O_SEND_STATS=$(calc_stats "${TMPDIR_B}/lat_o_send")
|
|
||||||
echo " done"
|
|
||||||
|
|
||||||
# ReceiveMessage latency (с предварительной загрузкой)
|
|
||||||
for i in $(seq 1 $LATENCY_ITERS); do
|
|
||||||
ysqs send-message --queue-url "$Y_QURL" --message-body "recv-lat-$i" >/dev/null 2>&1
|
|
||||||
osqs send-message --queue-url "$O_QURL" --message-body "recv-lat-$i" >/dev/null 2>&1
|
|
||||||
done
|
|
||||||
sleep 2
|
|
||||||
|
|
||||||
echo -n " ReceiveMessage — Y:"
|
|
||||||
bench_latency "recv" "y" "$LATENCY_ITERS" "ysqs receive-message --queue-url '$Y_QURL' --max-number-of-messages 1 --wait-time-seconds 2"
|
|
||||||
Y_RECV_STATS=$(calc_stats "${TMPDIR_B}/lat_y_recv")
|
|
||||||
echo -n " O:"
|
|
||||||
bench_latency "recv" "o" "$LATENCY_ITERS" "osqs receive-message --queue-url '$O_QURL' --max-number-of-messages 1 --wait-time-seconds 2"
|
|
||||||
O_RECV_STATS=$(calc_stats "${TMPDIR_B}/lat_o_recv")
|
|
||||||
echo " done"
|
|
||||||
|
|
||||||
# GetQueueAttributes latency
|
|
||||||
echo -n " GetQueueAttrs — Y:"
|
|
||||||
bench_latency "attr" "y" "$LATENCY_ITERS" "ysqs get-queue-attributes --queue-url '$Y_QURL' --attribute-names All"
|
|
||||||
Y_ATTR_STATS=$(calc_stats "${TMPDIR_B}/lat_y_attr")
|
|
||||||
echo -n " O:"
|
|
||||||
bench_latency "attr" "o" "$LATENCY_ITERS" "osqs get-queue-attributes --queue-url '$O_QURL' --attribute-names All"
|
|
||||||
O_ATTR_STATS=$(calc_stats "${TMPDIR_B}/lat_o_attr")
|
|
||||||
echo " done"
|
|
||||||
|
|
||||||
# GetQueueUrl latency
|
|
||||||
echo -n " GetQueueUrl — Y:"
|
|
||||||
bench_latency "gqu" "y" "$LATENCY_ITERS" "ysqs get-queue-url --queue-name '$Y_QNAME'"
|
|
||||||
Y_GQU_STATS=$(calc_stats "${TMPDIR_B}/lat_y_gqu")
|
|
||||||
echo -n " O:"
|
|
||||||
bench_latency "gqu" "o" "$LATENCY_ITERS" "osqs get-queue-url --queue-name '$O_QNAME'"
|
|
||||||
O_GQU_STATS=$(calc_stats "${TMPDIR_B}/lat_o_gqu")
|
|
||||||
echo " done"
|
|
||||||
|
|
||||||
# ListQueues latency
|
|
||||||
echo -n " ListQueues — Y:"
|
|
||||||
bench_latency "lq" "y" "$LATENCY_ITERS" "ysqs list-queues"
|
|
||||||
Y_LQ_STATS=$(calc_stats "${TMPDIR_B}/lat_y_lq")
|
|
||||||
echo -n " O:"
|
|
||||||
bench_latency "lq" "o" "$LATENCY_ITERS" "osqs list-queues"
|
|
||||||
O_LQ_STATS=$(calc_stats "${TMPDIR_B}/lat_o_lq")
|
|
||||||
echo " done"
|
|
||||||
|
|
||||||
# SendMessageBatch latency (10 entries)
|
|
||||||
echo -n " SendMsgBatch(10) — Y:"
|
|
||||||
BATCH_ENTRIES='[{"Id":"0","MessageBody":"b0"},{"Id":"1","MessageBody":"b1"},{"Id":"2","MessageBody":"b2"},{"Id":"3","MessageBody":"b3"},{"Id":"4","MessageBody":"b4"},{"Id":"5","MessageBody":"b5"},{"Id":"6","MessageBody":"b6"},{"Id":"7","MessageBody":"b7"},{"Id":"8","MessageBody":"b8"},{"Id":"9","MessageBody":"b9"}]'
|
|
||||||
bench_latency "smb" "y" "$LATENCY_ITERS" "ysqs send-message-batch --queue-url '$Y_QURL' --entries '$BATCH_ENTRIES'"
|
|
||||||
Y_SMB_STATS=$(calc_stats "${TMPDIR_B}/lat_y_smb")
|
|
||||||
echo -n " O:"
|
|
||||||
bench_latency "smb" "o" "$LATENCY_ITERS" "osqs send-message-batch --queue-url '$O_QURL' --entries '$BATCH_ENTRIES'"
|
|
||||||
O_SMB_STATS=$(calc_stats "${TMPDIR_B}/lat_o_smb")
|
|
||||||
echo " done"
|
|
||||||
|
|
||||||
# Очистим очереди перед дальнейшими тестами
|
|
||||||
ysqs purge-queue --queue-url "$Y_QURL" >/dev/null 2>&1
|
|
||||||
osqs purge-queue --queue-url "$O_QURL" >/dev/null 2>&1
|
|
||||||
sleep 2
|
|
||||||
|
|
||||||
# Выводим таблицу latency
|
|
||||||
echo ""
|
|
||||||
echo " ┌──────────────────────┬────────────────────────┬────────────────────────┐"
|
|
||||||
echo " │ Command │ Yandex min/avg/max/p95 │ Ours min/avg/max/p95 │"
|
|
||||||
echo " ├──────────────────────┼────────────────────────┼────────────────────────┤"
|
|
||||||
printf " │ %-20s │ %s ms │ %s ms │\n" "SendMessage" "$(echo $Y_SEND_STATS | awk '{printf "%4d/%4d/%4d/%4d",$1,$2,$3,$4}')" "$(echo $O_SEND_STATS | awk '{printf "%4d/%4d/%4d/%4d",$1,$2,$3,$4}')"
|
|
||||||
printf " │ %-20s │ %s ms │ %s ms │\n" "ReceiveMessage" "$(echo $Y_RECV_STATS | awk '{printf "%4d/%4d/%4d/%4d",$1,$2,$3,$4}')" "$(echo $O_RECV_STATS | awk '{printf "%4d/%4d/%4d/%4d",$1,$2,$3,$4}')"
|
|
||||||
printf " │ %-20s │ %s ms │ %s ms │\n" "GetQueueAttributes" "$(echo $Y_ATTR_STATS | awk '{printf "%4d/%4d/%4d/%4d",$1,$2,$3,$4}')" "$(echo $O_ATTR_STATS | awk '{printf "%4d/%4d/%4d/%4d",$1,$2,$3,$4}')"
|
|
||||||
printf " │ %-20s │ %s ms │ %s ms │\n" "GetQueueUrl" "$(echo $Y_GQU_STATS | awk '{printf "%4d/%4d/%4d/%4d",$1,$2,$3,$4}')" "$(echo $O_GQU_STATS | awk '{printf "%4d/%4d/%4d/%4d",$1,$2,$3,$4}')"
|
|
||||||
printf " │ %-20s │ %s ms │ %s ms │\n" "ListQueues" "$(echo $Y_LQ_STATS | awk '{printf "%4d/%4d/%4d/%4d",$1,$2,$3,$4}')" "$(echo $O_LQ_STATS | awk '{printf "%4d/%4d/%4d/%4d",$1,$2,$3,$4}')"
|
|
||||||
printf " │ %-20s │ %s ms │ %s ms │\n" "SendMsgBatch(10)" "$(echo $Y_SMB_STATS | awk '{printf "%4d/%4d/%4d/%4d",$1,$2,$3,$4}')" "$(echo $O_SMB_STATS | awk '{printf "%4d/%4d/%4d/%4d",$1,$2,$3,$4}')"
|
|
||||||
echo " └──────────────────────┴────────────────────────┴────────────────────────┘"
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════
|
|
||||||
# 3. THROUGHPUT — масштабирование по воркерам
|
|
||||||
# ═══════════════════════════════════════
|
|
||||||
section "Throughput scaling (workers × ${THROUGHPUT_MSGS} msgs)"
|
|
||||||
|
|
||||||
echo " ┌─────────┬──────────────────────┬──────────────────────┐"
|
|
||||||
echo " │ Workers │ Yandex (ok, ms, rps) │ Ours (ok, ms, rps) │"
|
|
||||||
echo " ├─────────┼──────────────────────┼──────────────────────┤"
|
|
||||||
|
|
||||||
for W in $THROUGHPUT_WORKERS; do
|
|
||||||
TOTAL_W=$((W * THROUGHPUT_MSGS))
|
|
||||||
|
|
||||||
# Yandex
|
|
||||||
Y_START=$(ms_now)
|
|
||||||
for w in $(seq 1 $W); do
|
|
||||||
(
|
|
||||||
ok=0
|
|
||||||
for m in $(seq 1 $THROUGHPUT_MSGS); do
|
|
||||||
R=$(ysqs send-message --queue-url "$Y_QURL" --message-body "tp-${w}-${m}")
|
|
||||||
echo "$R" | grep -q "MessageId" && ok=$((ok+1))
|
|
||||||
done
|
|
||||||
echo "$ok" > "${TMPDIR_B}/tp_y_${W}_${w}"
|
|
||||||
) &
|
|
||||||
done
|
|
||||||
wait
|
|
||||||
Y_TP_MS=$(( $(ms_now) - Y_START ))
|
|
||||||
Y_TP_OK=0
|
|
||||||
for w in $(seq 1 $W); do
|
|
||||||
[[ -f "${TMPDIR_B}/tp_y_${W}_${w}" ]] && Y_TP_OK=$((Y_TP_OK + $(cat "${TMPDIR_B}/tp_y_${W}_${w}")))
|
|
||||||
done
|
|
||||||
Y_TP_RPS=$(( Y_TP_OK * 1000 / (Y_TP_MS + 1) ))
|
|
||||||
|
|
||||||
# Ours
|
|
||||||
O_START=$(ms_now)
|
|
||||||
for w in $(seq 1 $W); do
|
|
||||||
(
|
|
||||||
ok=0
|
|
||||||
for m in $(seq 1 $THROUGHPUT_MSGS); do
|
|
||||||
R=$(osqs send-message --queue-url "$O_QURL" --message-body "tp-${w}-${m}")
|
|
||||||
echo "$R" | grep -q "MessageId" && ok=$((ok+1))
|
|
||||||
done
|
|
||||||
echo "$ok" > "${TMPDIR_B}/tp_o_${W}_${w}"
|
|
||||||
) &
|
|
||||||
done
|
|
||||||
wait
|
|
||||||
O_TP_MS=$(( $(ms_now) - O_START ))
|
|
||||||
O_TP_OK=0
|
|
||||||
for w in $(seq 1 $W); do
|
|
||||||
[[ -f "${TMPDIR_B}/tp_o_${W}_${w}" ]] && O_TP_OK=$((O_TP_OK + $(cat "${TMPDIR_B}/tp_o_${W}_${w}")))
|
|
||||||
done
|
|
||||||
O_TP_RPS=$(( O_TP_OK * 1000 / (O_TP_MS + 1) ))
|
|
||||||
|
|
||||||
printf " │ %7d │ %3d/%3d %5dms %2drps │ %3d/%3d %5dms %2drps │\n" \
|
|
||||||
"$W" "$Y_TP_OK" "$TOTAL_W" "$Y_TP_MS" "$Y_TP_RPS" "$O_TP_OK" "$TOTAL_W" "$O_TP_MS" "$O_TP_RPS"
|
|
||||||
done
|
|
||||||
echo " └─────────┴──────────────────────┴──────────────────────┘"
|
|
||||||
|
|
||||||
# Очистка
|
|
||||||
ysqs purge-queue --queue-url "$Y_QURL" >/dev/null 2>&1
|
|
||||||
osqs purge-queue --queue-url "$O_QURL" >/dev/null 2>&1
|
|
||||||
sleep 3
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════
|
|
||||||
# 4. MESSAGE SIZES — 1KB, 10KB, 64KB, 256KB
|
|
||||||
# ═══════════════════════════════════════
|
|
||||||
section "Message sizes — влияние на latency"
|
|
||||||
|
|
||||||
# Предгенерируем payloads
|
|
||||||
P_1K=$(gen_payload 1024)
|
|
||||||
P_10K=$(gen_payload 10240)
|
|
||||||
P_64K=$(gen_payload 65536)
|
|
||||||
P_256K=$(gen_payload 262144)
|
|
||||||
|
|
||||||
echo " ┌──────────┬──────────────────┬──────────────────┐"
|
|
||||||
echo " │ Size │ Yandex avg (ms) │ Ours avg (ms) │"
|
|
||||||
echo " ├──────────┼──────────────────┼──────────────────┤"
|
|
||||||
|
|
||||||
for SIZE_LABEL in "1KB" "10KB" "64KB" "256KB"; do
|
|
||||||
case $SIZE_LABEL in
|
|
||||||
1KB) PAYLOAD="$P_1K" ;;
|
|
||||||
10KB) PAYLOAD="$P_10K" ;;
|
|
||||||
64KB) PAYLOAD="$P_64K" ;;
|
|
||||||
256KB) PAYLOAD="$P_256K" ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
# Записываем payload в файл, чтобы избежать проблем с экранированием
|
|
||||||
echo "$PAYLOAD" > "${TMPDIR_B}/payload_${SIZE_LABEL}"
|
|
||||||
|
|
||||||
# Yandex
|
|
||||||
> "${TMPDIR_B}/sz_y_${SIZE_LABEL}"
|
|
||||||
for i in $(seq 1 3); do
|
|
||||||
s=$(ms_now)
|
|
||||||
ysqs send-message --queue-url "$Y_QURL" --message-body "file://${TMPDIR_B}/payload_${SIZE_LABEL}" >/dev/null 2>&1
|
|
||||||
echo $(( $(ms_now) - s )) >> "${TMPDIR_B}/sz_y_${SIZE_LABEL}"
|
|
||||||
done
|
|
||||||
Y_SZ_AVG=$(awk '{s+=$1}END{printf "%d",s/NR}' "${TMPDIR_B}/sz_y_${SIZE_LABEL}")
|
|
||||||
|
|
||||||
# Ours
|
|
||||||
> "${TMPDIR_B}/sz_o_${SIZE_LABEL}"
|
|
||||||
for i in $(seq 1 3); do
|
|
||||||
s=$(ms_now)
|
|
||||||
osqs send-message --queue-url "$O_QURL" --message-body "file://${TMPDIR_B}/payload_${SIZE_LABEL}" >/dev/null 2>&1
|
|
||||||
echo $(( $(ms_now) - s )) >> "${TMPDIR_B}/sz_o_${SIZE_LABEL}"
|
|
||||||
done
|
|
||||||
O_SZ_AVG=$(awk '{s+=$1}END{printf "%d",s/NR}' "${TMPDIR_B}/sz_o_${SIZE_LABEL}")
|
|
||||||
|
|
||||||
printf " │ %-8s │ %8d │ %8d │\n" "$SIZE_LABEL" "$Y_SZ_AVG" "$O_SZ_AVG"
|
|
||||||
done
|
|
||||||
echo " └──────────┴──────────────────┴──────────────────┘"
|
|
||||||
|
|
||||||
ysqs purge-queue --queue-url "$Y_QURL" >/dev/null 2>&1
|
|
||||||
osqs purge-queue --queue-url "$O_QURL" >/dev/null 2>&1
|
|
||||||
sleep 3
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════
|
|
||||||
# 5. BATCH EFFICIENCY — batch vs single
|
|
||||||
# ═══════════════════════════════════════
|
|
||||||
section "Batch efficiency — batch(10) vs 10×single"
|
|
||||||
|
|
||||||
echo " ┌────────────────┬──────────────────┬──────────────────┐"
|
|
||||||
echo " │ Method │ Yandex avg (ms) │ Ours avg (ms) │"
|
|
||||||
echo " ├────────────────┼──────────────────┼──────────────────┤"
|
|
||||||
|
|
||||||
# 10 × single
|
|
||||||
> "${TMPDIR_B}/be_y_single"
|
|
||||||
> "${TMPDIR_B}/be_o_single"
|
|
||||||
for i in $(seq 1 3); do
|
|
||||||
s=$(ms_now)
|
|
||||||
for j in $(seq 1 10); do ysqs send-message --queue-url "$Y_QURL" --message-body "be-s-${j}" >/dev/null 2>&1; done
|
|
||||||
echo $(( $(ms_now) - s )) >> "${TMPDIR_B}/be_y_single"
|
|
||||||
|
|
||||||
s=$(ms_now)
|
|
||||||
for j in $(seq 1 10); do osqs send-message --queue-url "$O_QURL" --message-body "be-s-${j}" >/dev/null 2>&1; done
|
|
||||||
echo $(( $(ms_now) - s )) >> "${TMPDIR_B}/be_o_single"
|
|
||||||
done
|
|
||||||
Y_SINGLE_AVG=$(awk '{s+=$1}END{printf "%d",s/NR}' "${TMPDIR_B}/be_y_single")
|
|
||||||
O_SINGLE_AVG=$(awk '{s+=$1}END{printf "%d",s/NR}' "${TMPDIR_B}/be_o_single")
|
|
||||||
printf " │ %-14s │ %8d │ %8d │\n" "10×Single" "$Y_SINGLE_AVG" "$O_SINGLE_AVG"
|
|
||||||
|
|
||||||
# 1 × batch(10)
|
|
||||||
> "${TMPDIR_B}/be_y_batch"
|
|
||||||
> "${TMPDIR_B}/be_o_batch"
|
|
||||||
for i in $(seq 1 3); do
|
|
||||||
s=$(ms_now)
|
|
||||||
ysqs send-message-batch --queue-url "$Y_QURL" --entries "$BATCH_ENTRIES" >/dev/null 2>&1
|
|
||||||
echo $(( $(ms_now) - s )) >> "${TMPDIR_B}/be_y_batch"
|
|
||||||
|
|
||||||
s=$(ms_now)
|
|
||||||
osqs send-message-batch --queue-url "$O_QURL" --entries "$BATCH_ENTRIES" >/dev/null 2>&1
|
|
||||||
echo $(( $(ms_now) - s )) >> "${TMPDIR_B}/be_o_batch"
|
|
||||||
done
|
|
||||||
Y_BATCH_AVG=$(awk '{s+=$1}END{printf "%d",s/NR}' "${TMPDIR_B}/be_y_batch")
|
|
||||||
O_BATCH_AVG=$(awk '{s+=$1}END{printf "%d",s/NR}' "${TMPDIR_B}/be_o_batch")
|
|
||||||
printf " │ %-14s │ %8d │ %8d │\n" "Batch(10)" "$Y_BATCH_AVG" "$O_BATCH_AVG"
|
|
||||||
|
|
||||||
Y_BATCH_GAIN=$(( (Y_SINGLE_AVG - Y_BATCH_AVG) * 100 / (Y_SINGLE_AVG + 1) ))
|
|
||||||
O_BATCH_GAIN=$(( (O_SINGLE_AVG - O_BATCH_AVG) * 100 / (O_SINGLE_AVG + 1) ))
|
|
||||||
printf " │ %-14s │ %7d%% │ %7d%% │\n" "Batch savings" "$Y_BATCH_GAIN" "$O_BATCH_GAIN"
|
|
||||||
echo " └────────────────┴──────────────────┴──────────────────┘"
|
|
||||||
|
|
||||||
ysqs purge-queue --queue-url "$Y_QURL" >/dev/null 2>&1
|
|
||||||
osqs purge-queue --queue-url "$O_QURL" >/dev/null 2>&1
|
|
||||||
sleep 3
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════
|
|
||||||
# 6. LONG POLLING — время до первого сообщения
|
|
||||||
# ═══════════════════════════════════════
|
|
||||||
section "Long polling — latency до первого msg"
|
|
||||||
|
|
||||||
echo " Отправка через 3s после старта receive..."
|
|
||||||
|
|
||||||
echo " ┌────────────────┬──────────────────┬──────────────────┐"
|
|
||||||
echo " │ WaitTimeSec │ Yandex (ms) │ Ours (ms) │"
|
|
||||||
echo " ├────────────────┼──────────────────┼──────────────────┤"
|
|
||||||
|
|
||||||
for WTS in 5 20; do
|
|
||||||
# Yandex: запускаем receive в фоне, через 3s отправляем
|
|
||||||
Y_LP_START=$(ms_now)
|
|
||||||
ysqs receive-message --queue-url "$Y_QURL" --max-number-of-messages 1 --wait-time-seconds "$WTS" > "${TMPDIR_B}/lp_y" 2>&1 &
|
|
||||||
LP_PID=$!
|
|
||||||
sleep 3
|
|
||||||
ysqs send-message --queue-url "$Y_QURL" --message-body "longpoll-${WTS}" >/dev/null 2>&1
|
|
||||||
wait $LP_PID 2>/dev/null
|
|
||||||
Y_LP_MS=$(( $(ms_now) - Y_LP_START ))
|
|
||||||
Y_LP_GOT=$(grep -c "MessageId" "${TMPDIR_B}/lp_y" || true)
|
|
||||||
|
|
||||||
# Ours
|
|
||||||
O_LP_START=$(ms_now)
|
|
||||||
osqs receive-message --queue-url "$O_QURL" --max-number-of-messages 1 --wait-time-seconds "$WTS" > "${TMPDIR_B}/lp_o" 2>&1 &
|
|
||||||
LP_PID=$!
|
|
||||||
sleep 3
|
|
||||||
osqs send-message --queue-url "$O_QURL" --message-body "longpoll-${WTS}" >/dev/null 2>&1
|
|
||||||
wait $LP_PID 2>/dev/null
|
|
||||||
O_LP_MS=$(( $(ms_now) - O_LP_START ))
|
|
||||||
O_LP_GOT=$(grep -c "MessageId" "${TMPDIR_B}/lp_o" || true)
|
|
||||||
|
|
||||||
printf " │ WaitTime=%-5d │ %5dms (got:%d) │ %5dms (got:%d) │\n" \
|
|
||||||
"$WTS" "$Y_LP_MS" "$Y_LP_GOT" "$O_LP_MS" "$O_LP_GOT"
|
|
||||||
done
|
|
||||||
echo " └────────────────┴──────────────────┴──────────────────┘"
|
|
||||||
|
|
||||||
# Почистим оставшиеся
|
|
||||||
ysqs purge-queue --queue-url "$Y_QURL" >/dev/null 2>&1
|
|
||||||
osqs purge-queue --queue-url "$O_QURL" >/dev/null 2>&1
|
|
||||||
sleep 2
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════
|
|
||||||
# 7. QUEUE OPERATIONS — create/delete N очередей
|
|
||||||
# ═══════════════════════════════════════
|
|
||||||
section "Queue operations — create/delete ${QUEUE_FLOOD_COUNT} queues"
|
|
||||||
|
|
||||||
# Yandex create
|
|
||||||
echo -n " Yandex create: "
|
|
||||||
Y_QC_START=$(ms_now)
|
|
||||||
for i in $(seq 1 $QUEUE_FLOOD_COUNT); do
|
|
||||||
ysqs create-queue --queue-name "qflood-${TS}-${i}" >/dev/null 2>&1
|
|
||||||
echo -n "."
|
|
||||||
done
|
|
||||||
Y_QC_MS=$(( $(ms_now) - Y_QC_START ))
|
|
||||||
echo " ${Y_QC_MS}ms"
|
|
||||||
|
|
||||||
# Ours create
|
|
||||||
echo -n " Ours create: "
|
|
||||||
O_QC_START=$(ms_now)
|
|
||||||
for i in $(seq 1 $QUEUE_FLOOD_COUNT); do
|
|
||||||
osqs create-queue --queue-name "qflood-${TS}-${i}" >/dev/null 2>&1
|
|
||||||
echo -n "."
|
|
||||||
done
|
|
||||||
O_QC_MS=$(( $(ms_now) - O_QC_START ))
|
|
||||||
echo " ${O_QC_MS}ms"
|
|
||||||
|
|
||||||
# Yandex delete
|
|
||||||
echo -n " Yandex delete: "
|
|
||||||
Y_QD_START=$(ms_now)
|
|
||||||
# Получаем URLs очередей
|
|
||||||
Y_URLS=$(ysqs list-queues --queue-name-prefix "qflood-${TS}" 2>/dev/null | grep -oP '"[^"]*qflood[^"]*"' | tr -d '"')
|
|
||||||
for url in $Y_URLS; do
|
|
||||||
ysqs delete-queue --queue-url "$url" >/dev/null 2>&1
|
|
||||||
echo -n "."
|
|
||||||
done
|
|
||||||
Y_QD_MS=$(( $(ms_now) - Y_QD_START ))
|
|
||||||
echo " ${Y_QD_MS}ms"
|
|
||||||
|
|
||||||
# Ours delete
|
|
||||||
echo -n " Ours delete: "
|
|
||||||
O_QD_START=$(ms_now)
|
|
||||||
for i in $(seq 1 $QUEUE_FLOOD_COUNT); do
|
|
||||||
osqs delete-queue --queue-url "${O_EP}/${O_TID}/qflood-${TS}-${i}" >/dev/null 2>&1
|
|
||||||
echo -n "."
|
|
||||||
done
|
|
||||||
O_QD_MS=$(( $(ms_now) - O_QD_START ))
|
|
||||||
echo " ${O_QD_MS}ms"
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo " Create ${QUEUE_FLOOD_COUNT}q: Yandex=${Y_QC_MS}ms Ours=${O_QC_MS}ms"
|
|
||||||
echo " Delete ${QUEUE_FLOOD_COUNT}q: Yandex=${Y_QD_MS}ms Ours=${O_QD_MS}ms"
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════
|
|
||||||
# 8. ERROR HANDLING — скорость отказа
|
|
||||||
# ═══════════════════════════════════════
|
|
||||||
section "Error handling — скорость отказа на невалидных запросах"
|
|
||||||
|
|
||||||
echo " ┌──────────────────────────┬──────────────────┬──────────────────┐"
|
|
||||||
echo " │ Error scenario │ Yandex (ms) │ Ours (ms) │"
|
|
||||||
echo " ├──────────────────────────┼──────────────────┼──────────────────┤"
|
|
||||||
|
|
||||||
# Несуществующая очередь
|
|
||||||
s=$(ms_now); ysqs send-message --queue-url "${Y_EP}/fake/queue/nonexist" --message-body "x" >/dev/null 2>&1
|
|
||||||
Y_ERR1=$(( $(ms_now) - s ))
|
|
||||||
s=$(ms_now); osqs send-message --queue-url "${O_EP}/fake/nonexist" --message-body "x" >/dev/null 2>&1
|
|
||||||
O_ERR1=$(( $(ms_now) - s ))
|
|
||||||
printf " │ %-24s │ %8d │ %8d │\n" "Non-existent queue" "$Y_ERR1" "$O_ERR1"
|
|
||||||
|
|
||||||
# Пустое тело
|
|
||||||
s=$(ms_now); ysqs send-message --queue-url "$Y_QURL" --message-body "" >/dev/null 2>&1
|
|
||||||
Y_ERR2=$(( $(ms_now) - s ))
|
|
||||||
s=$(ms_now); osqs send-message --queue-url "$O_QURL" --message-body "" >/dev/null 2>&1
|
|
||||||
O_ERR2=$(( $(ms_now) - s ))
|
|
||||||
printf " │ %-24s │ %8d │ %8d │\n" "Empty message body" "$Y_ERR2" "$O_ERR2"
|
|
||||||
|
|
||||||
# Невалидный receipt handle
|
|
||||||
s=$(ms_now); ysqs delete-message --queue-url "$Y_QURL" --receipt-handle "invalid-handle-xxx" >/dev/null 2>&1
|
|
||||||
Y_ERR3=$(( $(ms_now) - s ))
|
|
||||||
s=$(ms_now); osqs delete-message --queue-url "$O_QURL" --receipt-handle "invalid-handle-xxx" >/dev/null 2>&1
|
|
||||||
O_ERR3=$(( $(ms_now) - s ))
|
|
||||||
printf " │ %-24s │ %8d │ %8d │\n" "Invalid receipt handle" "$Y_ERR3" "$O_ERR3"
|
|
||||||
|
|
||||||
echo " └──────────────────────────┴──────────────────┴──────────────────┘"
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════
|
|
||||||
# 9. SUSTAINED LOAD — непрерывная нагрузка 2 мин
|
|
||||||
# ═══════════════════════════════════════
|
|
||||||
section "Sustained load — ${SUSTAINED_DURATION}s непрерывно (send+receive+delete)"
|
|
||||||
|
|
||||||
run_sustained() {
|
|
||||||
local provider="$1" sqs_func="$2" qurl="$3" duration="$4" result_file="$5"
|
|
||||||
local end=$((SECONDS + duration))
|
|
||||||
local sent=0 recv=0 del=0 errors=0
|
|
||||||
local interval=30
|
|
||||||
local next_report=$((SECONDS + interval))
|
|
||||||
|
|
||||||
while [[ $SECONDS -lt $end ]]; do
|
|
||||||
# Send
|
|
||||||
R=$($sqs_func send-message --queue-url "$qurl" --message-body "sustained-$(date +%s%N)" 2>&1)
|
|
||||||
if echo "$R" | grep -q "MessageId"; then
|
|
||||||
sent=$((sent+1))
|
|
||||||
else
|
|
||||||
errors=$((errors+1))
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Receive + Delete
|
|
||||||
R=$($sqs_func receive-message --queue-url "$qurl" --max-number-of-messages 1 --wait-time-seconds 0 2>&1)
|
|
||||||
if echo "$R" | grep -q "MessageId"; then
|
|
||||||
recv=$((recv+1))
|
|
||||||
H=$(echo "$R" | grep -oP '"ReceiptHandle"\s*:\s*"\K[^"]+' | head -1)
|
|
||||||
if [[ -n "$H" ]]; then
|
|
||||||
$sqs_func delete-message --queue-url "$qurl" --receipt-handle "$H" >/dev/null 2>&1
|
|
||||||
del=$((del+1))
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Промежуточный отчёт
|
|
||||||
if [[ $SECONDS -ge $next_report ]]; then
|
|
||||||
local elapsed=$((SECONDS - (end - duration)))
|
|
||||||
echo " [${provider} ${elapsed}s] sent=$sent recv=$recv del=$del err=$errors"
|
|
||||||
next_report=$((SECONDS + interval))
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
echo "${sent}:${recv}:${del}:${errors}" > "$result_file"
|
|
||||||
}
|
|
||||||
|
|
||||||
echo " Запуск Yandex sustained..."
|
|
||||||
run_sustained "YMQ" "ysqs" "$Y_QURL" "$SUSTAINED_DURATION" "${TMPDIR_B}/sus_y" &
|
|
||||||
Y_SUS_PID=$!
|
|
||||||
|
|
||||||
echo " Запуск Ours sustained..."
|
|
||||||
run_sustained "OURS" "osqs" "$O_QURL" "$SUSTAINED_DURATION" "${TMPDIR_B}/sus_o" &
|
|
||||||
O_SUS_PID=$!
|
|
||||||
|
|
||||||
echo " ⏳ Ждём ${SUSTAINED_DURATION}s..."
|
|
||||||
wait $Y_SUS_PID $O_SUS_PID 2>/dev/null
|
|
||||||
|
|
||||||
IFS=':' read -r Y_SUS_SENT Y_SUS_RECV Y_SUS_DEL Y_SUS_ERR < "${TMPDIR_B}/sus_y"
|
|
||||||
IFS=':' read -r O_SUS_SENT O_SUS_RECV O_SUS_DEL O_SUS_ERR < "${TMPDIR_B}/sus_o"
|
|
||||||
|
|
||||||
Y_SUS_RPS=$(( (Y_SUS_SENT + Y_SUS_RECV + Y_SUS_DEL) / (SUSTAINED_DURATION + 1) ))
|
|
||||||
O_SUS_RPS=$(( (O_SUS_SENT + O_SUS_RECV + O_SUS_DEL) / (SUSTAINED_DURATION + 1) ))
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo " ┌──────────────┬─────────────────────────────────────┬─────────────────────────────────────┐"
|
|
||||||
echo " │ │ Yandex MQ │ shared-sqs │"
|
|
||||||
echo " ├──────────────┼─────────────────────────────────────┼─────────────────────────────────────┤"
|
|
||||||
printf " │ %-12s │ sent=%-4d recv=%-4d del=%-4d err=%-3d │ sent=%-4d recv=%-4d del=%-4d err=%-3d │\n" \
|
|
||||||
"Operations" "$Y_SUS_SENT" "$Y_SUS_RECV" "$Y_SUS_DEL" "$Y_SUS_ERR" \
|
|
||||||
"$O_SUS_SENT" "$O_SUS_RECV" "$O_SUS_DEL" "$O_SUS_ERR"
|
|
||||||
printf " │ %-12s │ ~%-3d ops/sec │ ~%-3d ops/sec │\n" \
|
|
||||||
"Throughput" "$Y_SUS_RPS" "$O_SUS_RPS"
|
|
||||||
echo " └──────────────┴─────────────────────────────────────┴─────────────────────────────────────┘"
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════
|
|
||||||
# 10. CLEANUP
|
|
||||||
# ═══════════════════════════════════════
|
|
||||||
section "Cleanup"
|
|
||||||
ysqs purge-queue --queue-url "$Y_QURL" >/dev/null 2>&1
|
|
||||||
ysqs delete-queue --queue-url "$Y_QURL" >/dev/null 2>&1
|
|
||||||
echo " Yandex: bench queue deleted"
|
|
||||||
osqs purge-queue --queue-url "$O_QURL" >/dev/null 2>&1
|
|
||||||
osqs delete-queue --queue-url "$O_QURL" >/dev/null 2>&1
|
|
||||||
echo " Ours: bench queue deleted"
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════
|
|
||||||
# ИТОГОВАЯ СВОДКА
|
|
||||||
# ═══════════════════════════════════════
|
|
||||||
echo ""
|
|
||||||
echo "╔══════════════════════════════════════════════════════════════════════╗"
|
|
||||||
echo "║ FULL BENCHMARK COMPLETE ║"
|
|
||||||
echo "║ $(date '+%Y-%m-%d %H:%M:%S') ║"
|
|
||||||
echo "║ ║"
|
|
||||||
echo "║ Latency: SendMessage avg Y=$(echo $Y_SEND_STATS | awk '{print $2}')ms O=$(echo $O_SEND_STATS | awk '{print $2}')ms"
|
|
||||||
echo "║ ReceiveMsg avg Y=$(echo $Y_RECV_STATS | awk '{print $2}')ms O=$(echo $O_RECV_STATS | awk '{print $2}')ms"
|
|
||||||
echo "║ Batch: savings Y=${Y_BATCH_GAIN}% O=${O_BATCH_GAIN}%"
|
|
||||||
echo "║ Sustained: ops/sec Y=${Y_SUS_RPS} O=${O_SUS_RPS} (${SUSTAINED_DURATION}s)"
|
|
||||||
echo "║ Errors: Y=${Y_SUS_ERR} O=${O_SUS_ERR}"
|
|
||||||
echo "║ QueueOps: create10 Y=${Y_QC_MS}ms O=${O_QC_MS}ms"
|
|
||||||
echo "║ delete10 Y=${Y_QD_MS}ms O=${O_QD_MS}ms"
|
|
||||||
echo "║ Tags: shared-sqs ✅ (3/3) Yandex ⚠️ not supported"
|
|
||||||
echo "╚══════════════════════════════════════════════════════════════════════╝"
|
|
||||||
@@ -1,272 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
# compare_sqs.sh — сравнительный бенчмарк Yandex MQ vs shared-sqs
|
|
||||||
# Запуск из ЛОКАЛИ для равных условий
|
|
||||||
# Created: 2026-04-11
|
|
||||||
|
|
||||||
set -uo pipefail
|
|
||||||
|
|
||||||
# ═══ CREDENTIALS ═══
|
|
||||||
# Yandex MQ
|
|
||||||
Y_AK="YCAJEQDz_Eg_i4C4M7TAen2fd"
|
|
||||||
Y_SK="YCMDfD8OKFK51knPyydwQOYts7Q81_3YBhv4sd_j"
|
|
||||||
Y_REGION="ru-central1"
|
|
||||||
Y_ENDPOINT="https://message-queue.api.cloud.yandex.net"
|
|
||||||
Y_QURL="https://message-queue.api.cloud.yandex.net/b1gatc4m3hv1ldldhljp/dj60000000k2jb2o02mt/foropus"
|
|
||||||
|
|
||||||
# Our SQS
|
|
||||||
O_AK="SSAK-ed0b0c64dcc135adad9e11be"
|
|
||||||
O_SK="f917c133e4ad74cf37c8e3ba29d74a39f42f117a202bd672436c92fdb6b6f3dc"
|
|
||||||
O_REGION="us-east-1"
|
|
||||||
O_ENDPOINT="https://qu.kube5s.ru"
|
|
||||||
O_QURL="https://qu.kube5s.ru/t-e0ce25e83be94c58/foropus-test"
|
|
||||||
|
|
||||||
# ═══ ПАРАМЕТРЫ ═══
|
|
||||||
SEQ_COUNT=20 # последовательных send/receive
|
|
||||||
PAR_WORKERS=10 # параллельных воркеров
|
|
||||||
PAR_MSGS=5 # сообщений на воркер (итого 50)
|
|
||||||
BURST=30 # burst одновременно
|
|
||||||
|
|
||||||
TMPDIR_CMP=$(mktemp -d /tmp/sqs_compare_XXXXX)
|
|
||||||
trap "rm -rf $TMPDIR_CMP" EXIT
|
|
||||||
|
|
||||||
# ═══ ХЕЛПЕРЫ ═══
|
|
||||||
ysqs() {
|
|
||||||
AWS_ACCESS_KEY_ID="$Y_AK" AWS_SECRET_ACCESS_KEY="$Y_SK" \
|
|
||||||
AWS_DEFAULT_REGION="$Y_REGION" \
|
|
||||||
aws --endpoint-url "$Y_ENDPOINT" --output json sqs "$@" 2>&1
|
|
||||||
}
|
|
||||||
|
|
||||||
osqs() {
|
|
||||||
AWS_ACCESS_KEY_ID="$O_AK" AWS_SECRET_ACCESS_KEY="$O_SK" \
|
|
||||||
AWS_DEFAULT_REGION="$O_REGION" \
|
|
||||||
aws --endpoint-url "$O_ENDPOINT" --output json sqs "$@" 2>&1
|
|
||||||
}
|
|
||||||
|
|
||||||
# ms_since — миллисекунды с момента $1
|
|
||||||
ms_now() { date +%s%3N; }
|
|
||||||
|
|
||||||
echo "╔═══════════════════════════════════════════════════════════════════╗"
|
|
||||||
echo "║ BENCHMARK: Yandex MQ vs shared-sqs — $(date '+%Y-%m-%d %H:%M:%S') ║"
|
|
||||||
echo "║ Из локали, равные условия ║"
|
|
||||||
echo "╚═══════════════════════════════════════════════════════════════════╝"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════
|
|
||||||
# 1. SEQUENTIAL SEND — latency per message
|
|
||||||
# ═══════════════════════════════════════
|
|
||||||
echo "═══ 1. Sequential SendMessage (${SEQ_COUNT} msgs) ═══"
|
|
||||||
|
|
||||||
# Yandex
|
|
||||||
echo -n " Yandex: "
|
|
||||||
Y_START=$(ms_now)
|
|
||||||
Y_SEND_OK=0
|
|
||||||
for i in $(seq 1 $SEQ_COUNT); do
|
|
||||||
R=$(ysqs send-message --queue-url "$Y_QURL" --message-body "bench-seq-${i}")
|
|
||||||
if echo "$R" | grep -q "MessageId"; then Y_SEND_OK=$((Y_SEND_OK+1)); fi
|
|
||||||
echo -n "."
|
|
||||||
done
|
|
||||||
Y_SEND_MS=$(( $(ms_now) - Y_START ))
|
|
||||||
Y_SEND_AVG=$(( Y_SEND_MS / SEQ_COUNT ))
|
|
||||||
echo " ${Y_SEND_OK}/${SEQ_COUNT} ok, ${Y_SEND_MS}ms total, avg=${Y_SEND_AVG}ms"
|
|
||||||
|
|
||||||
# Ours
|
|
||||||
echo -n " Ours: "
|
|
||||||
O_START=$(ms_now)
|
|
||||||
O_SEND_OK=0
|
|
||||||
for i in $(seq 1 $SEQ_COUNT); do
|
|
||||||
R=$(osqs send-message --queue-url "$O_QURL" --message-body "bench-seq-${i}")
|
|
||||||
if echo "$R" | grep -q "MessageId"; then O_SEND_OK=$((O_SEND_OK+1)); fi
|
|
||||||
echo -n "."
|
|
||||||
done
|
|
||||||
O_SEND_MS=$(( $(ms_now) - O_START ))
|
|
||||||
O_SEND_AVG=$(( O_SEND_MS / SEQ_COUNT ))
|
|
||||||
echo " ${O_SEND_OK}/${SEQ_COUNT} ok, ${O_SEND_MS}ms total, avg=${O_SEND_AVG}ms"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════
|
|
||||||
# 2. SEQUENTIAL RECEIVE — latency per message
|
|
||||||
# ═══════════════════════════════════════
|
|
||||||
echo "═══ 2. Sequential ReceiveMessage (${SEQ_COUNT} msgs) ═══"
|
|
||||||
|
|
||||||
# Yandex
|
|
||||||
echo -n " Yandex: "
|
|
||||||
Y_START=$(ms_now)
|
|
||||||
Y_RECV_OK=0
|
|
||||||
for i in $(seq 1 $SEQ_COUNT); do
|
|
||||||
R=$(ysqs receive-message --queue-url "$Y_QURL" --max-number-of-messages 1 --wait-time-seconds 2)
|
|
||||||
if echo "$R" | grep -q "MessageId"; then
|
|
||||||
Y_RECV_OK=$((Y_RECV_OK+1))
|
|
||||||
H=$(echo "$R" | grep -oP '"ReceiptHandle"\s*:\s*"\K[^"]+')
|
|
||||||
ysqs delete-message --queue-url "$Y_QURL" --receipt-handle "$H" >/dev/null 2>&1
|
|
||||||
fi
|
|
||||||
echo -n "."
|
|
||||||
done
|
|
||||||
Y_RECV_MS=$(( $(ms_now) - Y_START ))
|
|
||||||
Y_RECV_AVG=$(( Y_RECV_MS / SEQ_COUNT ))
|
|
||||||
echo " ${Y_RECV_OK}/${SEQ_COUNT} ok, ${Y_RECV_MS}ms total, avg=${Y_RECV_AVG}ms"
|
|
||||||
|
|
||||||
# Ours
|
|
||||||
echo -n " Ours: "
|
|
||||||
O_START=$(ms_now)
|
|
||||||
O_RECV_OK=0
|
|
||||||
for i in $(seq 1 $SEQ_COUNT); do
|
|
||||||
R=$(osqs receive-message --queue-url "$O_QURL" --max-number-of-messages 1 --wait-time-seconds 2)
|
|
||||||
if echo "$R" | grep -q "MessageId"; then
|
|
||||||
O_RECV_OK=$((O_RECV_OK+1))
|
|
||||||
H=$(echo "$R" | grep -oP '"ReceiptHandle"\s*:\s*"\K[^"]+')
|
|
||||||
osqs delete-message --queue-url "$O_QURL" --receipt-handle "$H" >/dev/null 2>&1
|
|
||||||
fi
|
|
||||||
echo -n "."
|
|
||||||
done
|
|
||||||
O_RECV_MS=$(( $(ms_now) - O_START ))
|
|
||||||
O_RECV_AVG=$(( O_RECV_MS / SEQ_COUNT ))
|
|
||||||
echo " ${O_RECV_OK}/${SEQ_COUNT} ok, ${O_RECV_MS}ms total, avg=${O_RECV_AVG}ms"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════
|
|
||||||
# 3. PARALLEL SEND — throughput
|
|
||||||
# ═══════════════════════════════════════
|
|
||||||
TOTAL_PAR=$((PAR_WORKERS * PAR_MSGS))
|
|
||||||
echo "═══ 3. Parallel SendMessage (${PAR_WORKERS} workers × ${PAR_MSGS} = ${TOTAL_PAR} msgs) ═══"
|
|
||||||
|
|
||||||
# Yandex
|
|
||||||
echo -n " Yandex: "
|
|
||||||
Y_START=$(ms_now)
|
|
||||||
for w in $(seq 1 $PAR_WORKERS); do
|
|
||||||
(
|
|
||||||
ok=0
|
|
||||||
for m in $(seq 1 $PAR_MSGS); do
|
|
||||||
R=$(ysqs send-message --queue-url "$Y_QURL" --message-body "par-${w}-${m}")
|
|
||||||
if echo "$R" | grep -q "MessageId"; then ok=$((ok+1)); fi
|
|
||||||
done
|
|
||||||
echo "$ok" > "${TMPDIR_CMP}/y_par_${w}"
|
|
||||||
) &
|
|
||||||
done
|
|
||||||
wait
|
|
||||||
Y_PAR_MS=$(( $(ms_now) - Y_START ))
|
|
||||||
Y_PAR_OK=0
|
|
||||||
for w in $(seq 1 $PAR_WORKERS); do
|
|
||||||
[[ -f "${TMPDIR_CMP}/y_par_${w}" ]] && Y_PAR_OK=$((Y_PAR_OK + $(cat "${TMPDIR_CMP}/y_par_${w}")))
|
|
||||||
done
|
|
||||||
Y_PAR_RPS=$(( Y_PAR_OK * 1000 / Y_PAR_MS ))
|
|
||||||
echo "${Y_PAR_OK}/${TOTAL_PAR} ok, ${Y_PAR_MS}ms, ~${Y_PAR_RPS} msg/sec"
|
|
||||||
|
|
||||||
# Ours
|
|
||||||
echo -n " Ours: "
|
|
||||||
O_START=$(ms_now)
|
|
||||||
for w in $(seq 1 $PAR_WORKERS); do
|
|
||||||
(
|
|
||||||
ok=0
|
|
||||||
for m in $(seq 1 $PAR_MSGS); do
|
|
||||||
R=$(osqs send-message --queue-url "$O_QURL" --message-body "par-${w}-${m}")
|
|
||||||
if echo "$R" | grep -q "MessageId"; then ok=$((ok+1)); fi
|
|
||||||
done
|
|
||||||
echo "$ok" > "${TMPDIR_CMP}/o_par_${w}"
|
|
||||||
) &
|
|
||||||
done
|
|
||||||
wait
|
|
||||||
O_PAR_MS=$(( $(ms_now) - O_START ))
|
|
||||||
O_PAR_OK=0
|
|
||||||
for w in $(seq 1 $PAR_WORKERS); do
|
|
||||||
[[ -f "${TMPDIR_CMP}/o_par_${w}" ]] && O_PAR_OK=$((O_PAR_OK + $(cat "${TMPDIR_CMP}/o_par_${w}")))
|
|
||||||
done
|
|
||||||
O_PAR_RPS=$(( O_PAR_OK * 1000 / O_PAR_MS ))
|
|
||||||
echo "${O_PAR_OK}/${TOTAL_PAR} ok, ${O_PAR_MS}ms, ~${O_PAR_RPS} msg/sec"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════
|
|
||||||
# 4. BURST — все сразу
|
|
||||||
# ═══════════════════════════════════════
|
|
||||||
echo "═══ 4. Burst SendMessage (${BURST} одновременно) ═══"
|
|
||||||
|
|
||||||
# Yandex
|
|
||||||
echo -n " Yandex: "
|
|
||||||
Y_START=$(ms_now)
|
|
||||||
for b in $(seq 1 $BURST); do
|
|
||||||
( R=$(ysqs send-message --queue-url "$Y_QURL" --message-body "burst-${b}")
|
|
||||||
echo "$R" | grep -q "MessageId" && echo 1 || echo 0
|
|
||||||
) > "${TMPDIR_CMP}/y_burst_${b}" &
|
|
||||||
done
|
|
||||||
wait
|
|
||||||
Y_BURST_MS=$(( $(ms_now) - Y_START ))
|
|
||||||
Y_BURST_OK=0
|
|
||||||
for b in $(seq 1 $BURST); do
|
|
||||||
[[ -f "${TMPDIR_CMP}/y_burst_${b}" ]] && Y_BURST_OK=$((Y_BURST_OK + $(cat "${TMPDIR_CMP}/y_burst_${b}")))
|
|
||||||
done
|
|
||||||
echo "${Y_BURST_OK}/${BURST} ok, ${Y_BURST_MS}ms"
|
|
||||||
|
|
||||||
# Ours
|
|
||||||
echo -n " Ours: "
|
|
||||||
O_START=$(ms_now)
|
|
||||||
for b in $(seq 1 $BURST); do
|
|
||||||
( R=$(osqs send-message --queue-url "$O_QURL" --message-body "burst-${b}")
|
|
||||||
echo "$R" | grep -q "MessageId" && echo 1 || echo 0
|
|
||||||
) > "${TMPDIR_CMP}/o_burst_${b}" &
|
|
||||||
done
|
|
||||||
wait
|
|
||||||
O_BURST_MS=$(( $(ms_now) - O_START ))
|
|
||||||
O_BURST_OK=0
|
|
||||||
for b in $(seq 1 $BURST); do
|
|
||||||
[[ -f "${TMPDIR_CMP}/o_burst_${b}" ]] && O_BURST_OK=$((O_BURST_OK + $(cat "${TMPDIR_CMP}/o_burst_${b}")))
|
|
||||||
done
|
|
||||||
echo "${O_BURST_OK}/${BURST} ok, ${O_BURST_MS}ms"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════
|
|
||||||
# 5. GetQueueAttributes — latency
|
|
||||||
# ═══════════════════════════════════════
|
|
||||||
echo "═══ 5. GetQueueAttributes (5 раз) ═══"
|
|
||||||
|
|
||||||
echo -n " Yandex: "
|
|
||||||
Y_START=$(ms_now)
|
|
||||||
for i in $(seq 1 5); do
|
|
||||||
ysqs get-queue-attributes --queue-url "$Y_QURL" --attribute-names ApproximateNumberOfMessages >/dev/null 2>&1
|
|
||||||
echo -n "."
|
|
||||||
done
|
|
||||||
Y_ATTR_MS=$(( $(ms_now) - Y_START ))
|
|
||||||
Y_ATTR_AVG=$(( Y_ATTR_MS / 5 ))
|
|
||||||
echo " ${Y_ATTR_MS}ms total, avg=${Y_ATTR_AVG}ms"
|
|
||||||
|
|
||||||
echo -n " Ours: "
|
|
||||||
O_START=$(ms_now)
|
|
||||||
for i in $(seq 1 5); do
|
|
||||||
osqs get-queue-attributes --queue-url "$O_QURL" --attribute-names ApproximateNumberOfMessages >/dev/null 2>&1
|
|
||||||
echo -n "."
|
|
||||||
done
|
|
||||||
O_ATTR_MS=$(( $(ms_now) - O_START ))
|
|
||||||
O_ATTR_AVG=$(( O_ATTR_MS / 5 ))
|
|
||||||
echo " ${O_ATTR_MS}ms total, avg=${O_ATTR_AVG}ms"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════
|
|
||||||
# 6. PURGE — очистка после тестов
|
|
||||||
# ═══════════════════════════════════════
|
|
||||||
echo "═══ 6. Purge (очистка) ═══"
|
|
||||||
ysqs purge-queue --queue-url "$Y_QURL" >/dev/null 2>&1 && echo " Yandex: purged" || echo " Yandex: purge failed (cooldown?)"
|
|
||||||
osqs purge-queue --queue-url "$O_QURL" >/dev/null 2>&1 && echo " Ours: purged" || echo " Ours: purge failed"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════
|
|
||||||
# ИТОГОВАЯ ТАБЛИЦА
|
|
||||||
# ═══════════════════════════════════════
|
|
||||||
echo "╔══════════════════════════════════════════════════════════════════════════╗"
|
|
||||||
echo "║ РЕЗУЛЬТАТЫ СРАВНЕНИЯ ║"
|
|
||||||
echo "╠══════════════════════════╦══════════════════╦══════════════════╦════════╣"
|
|
||||||
echo "║ Тест ║ Yandex MQ ║ shared-sqs ║ Winner ║"
|
|
||||||
echo "╠══════════════════════════╬══════════════════╬══════════════════╬════════╣"
|
|
||||||
printf "║ %-24s ║ %4dms avg %2d/%2d ║ %4dms avg %2d/%2d ║ %-6s ║\n" \
|
|
||||||
"Seq Send (${SEQ_COUNT}msg)" "$Y_SEND_AVG" "$Y_SEND_OK" "$SEQ_COUNT" "$O_SEND_AVG" "$O_SEND_OK" "$SEQ_COUNT" \
|
|
||||||
"$([ $Y_SEND_AVG -le $O_SEND_AVG ] && echo 'YMQ' || echo 'OURS')"
|
|
||||||
printf "║ %-24s ║ %4dms avg %2d/%2d ║ %4dms avg %2d/%2d ║ %-6s ║\n" \
|
|
||||||
"Seq Recv+Del (${SEQ_COUNT}msg)" "$Y_RECV_AVG" "$Y_RECV_OK" "$SEQ_COUNT" "$O_RECV_AVG" "$O_RECV_OK" "$SEQ_COUNT" \
|
|
||||||
"$([ $Y_RECV_AVG -le $O_RECV_AVG ] && echo 'YMQ' || echo 'OURS')"
|
|
||||||
printf "║ %-24s ║ %4dms %3d msg/s ║ %4dms %3d msg/s ║ %-6s ║\n" \
|
|
||||||
"Parallel Send (${TOTAL_PAR}msg)" "$Y_PAR_MS" "$Y_PAR_RPS" "$O_PAR_MS" "$O_PAR_RPS" \
|
|
||||||
"$([ $Y_PAR_RPS -ge $O_PAR_RPS ] && echo 'YMQ' || echo 'OURS')"
|
|
||||||
printf "║ %-24s ║ %4dms %2d/%2d ║ %4dms %2d/%2d ║ %-6s ║\n" \
|
|
||||||
"Burst (${BURST} simultaneous)" "$Y_BURST_MS" "$Y_BURST_OK" "$BURST" "$O_BURST_MS" "$O_BURST_OK" "$BURST" \
|
|
||||||
"$([ $Y_BURST_MS -le $O_BURST_MS ] && echo 'YMQ' || echo 'OURS')"
|
|
||||||
printf "║ %-24s ║ %4dms avg ║ %4dms avg ║ %-6s ║\n" \
|
|
||||||
"GetQueueAttributes (5x)" "$Y_ATTR_AVG" "$O_ATTR_AVG" \
|
|
||||||
"$([ $Y_ATTR_AVG -le $O_ATTR_AVG ] && echo 'YMQ' || echo 'OURS')"
|
|
||||||
echo "╚══════════════════════════╩══════════════════╩══════════════════╩════════╝"
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,104 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
# Updated: 2026-04-12 05:40 UTC
|
|
||||||
# payload_latency_probe.sh — узкий замер SendMessage latency для payload 10KB и 32KB.
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
# ysqs выполняет aws sqs для Yandex MQ.
|
|
||||||
ysqs() {
|
|
||||||
AWS_ACCESS_KEY_ID="$Y_AK" AWS_SECRET_ACCESS_KEY="$Y_SK" \
|
|
||||||
AWS_DEFAULT_REGION="$Y_REGION" \
|
|
||||||
aws --endpoint-url "$Y_EP" --output json sqs "$@" >/dev/null 2>&1
|
|
||||||
}
|
|
||||||
|
|
||||||
# osqs выполняет aws sqs для shared-sqs.
|
|
||||||
osqs() {
|
|
||||||
AWS_ACCESS_KEY_ID="$O_AK" AWS_SECRET_ACCESS_KEY="$O_SK" \
|
|
||||||
AWS_DEFAULT_REGION="$O_REGION" \
|
|
||||||
aws --endpoint-url "$O_EP" --output json sqs "$@" >/dev/null 2>&1
|
|
||||||
}
|
|
||||||
|
|
||||||
# ms_now возвращает текущее время в миллисекундах.
|
|
||||||
ms_now() {
|
|
||||||
date +%s%3N
|
|
||||||
}
|
|
||||||
|
|
||||||
# calc_stats считает min/avg/max/p95 по файлу latency.
|
|
||||||
calc_stats() {
|
|
||||||
local file_path="$1"
|
|
||||||
sort -n "$file_path" | awk '
|
|
||||||
{ values[NR] = $1; total += $1 }
|
|
||||||
END {
|
|
||||||
p95_index = int(NR * 0.95)
|
|
||||||
if (p95_index < 1) p95_index = 1
|
|
||||||
printf "%d %d %d %d\n", values[1], int(total / NR), values[NR], values[p95_index]
|
|
||||||
}'
|
|
||||||
}
|
|
||||||
|
|
||||||
# cleanup удаляет временные объекты после замера.
|
|
||||||
cleanup() {
|
|
||||||
set +e
|
|
||||||
if [[ -n "${Y_QURL:-}" ]]; then
|
|
||||||
AWS_ACCESS_KEY_ID="$Y_AK" AWS_SECRET_ACCESS_KEY="$Y_SK" AWS_DEFAULT_REGION="$Y_REGION" \
|
|
||||||
aws --endpoint-url "$Y_EP" --output json sqs delete-queue --queue-url "$Y_QURL" >/dev/null 2>&1 || true
|
|
||||||
fi
|
|
||||||
if [[ -n "${O_QURL:-}" ]]; then
|
|
||||||
AWS_ACCESS_KEY_ID="$O_AK" AWS_SECRET_ACCESS_KEY="$O_SK" AWS_DEFAULT_REGION="$O_REGION" \
|
|
||||||
aws --endpoint-url "$O_EP" --output json sqs delete-queue --queue-url "$O_QURL" >/dev/null 2>&1 || true
|
|
||||||
fi
|
|
||||||
rm -rf "$TMPDIR"
|
|
||||||
}
|
|
||||||
|
|
||||||
trap cleanup EXIT
|
|
||||||
|
|
||||||
Y_AK="YCAJEQDz_Eg_i4C4M7TAen2fd"
|
|
||||||
Y_SK="YCMDfD8OKFK51knPyydwQOYts7Q81_3YBhv4sd_j"
|
|
||||||
Y_REGION="ru-central1"
|
|
||||||
Y_EP="https://message-queue.api.cloud.yandex.net"
|
|
||||||
|
|
||||||
O_AK="SSAK-ed0b0c64dcc135adad9e11be"
|
|
||||||
O_SK="f917c133e4ad74cf37c8e3ba29d74a39f42f117a202bd672436c92fdb6b6f3dc"
|
|
||||||
O_REGION="us-east-1"
|
|
||||||
O_EP="https://qu.kube5s.ru"
|
|
||||||
O_TID="t-e0ce25e83be94c58"
|
|
||||||
|
|
||||||
ITERATIONS=7
|
|
||||||
TMPDIR=$(mktemp -d /tmp/payload_latency_probe_XXXXX)
|
|
||||||
TS=$(date +%s)
|
|
||||||
|
|
||||||
head -c 10240 /dev/urandom | base64 | head -c 10240 > "$TMPDIR/payload_10k.txt"
|
|
||||||
head -c 32768 /dev/urandom | base64 | head -c 32768 > "$TMPDIR/payload_32k.txt"
|
|
||||||
|
|
||||||
Y_QNAME="payload-probe-y-${TS}"
|
|
||||||
O_QNAME="payload-probe-o-${TS}"
|
|
||||||
|
|
||||||
Y_CREATE_RAW=$(AWS_ACCESS_KEY_ID="$Y_AK" AWS_SECRET_ACCESS_KEY="$Y_SK" AWS_DEFAULT_REGION="$Y_REGION" aws --endpoint-url "$Y_EP" --output json sqs create-queue --queue-name "$Y_QNAME")
|
|
||||||
Y_QURL=$(echo "$Y_CREATE_RAW" | python3 -c 'import json,sys; print(json.load(sys.stdin)["QueueUrl"])')
|
|
||||||
osqs create-queue --queue-name "$O_QNAME"
|
|
||||||
O_QURL="${O_EP}/${O_TID}/${O_QNAME}"
|
|
||||||
|
|
||||||
run_probe() {
|
|
||||||
local provider_name="$1"
|
|
||||||
local queue_url="$2"
|
|
||||||
local payload_file="$3"
|
|
||||||
local result_file="$4"
|
|
||||||
local runner="$5"
|
|
||||||
|
|
||||||
: > "$result_file"
|
|
||||||
for _ in $(seq 1 "$ITERATIONS"); do
|
|
||||||
local started_at
|
|
||||||
started_at=$(ms_now)
|
|
||||||
"$runner" send-message --queue-url "$queue_url" --message-body "file://$payload_file"
|
|
||||||
echo $(( $(ms_now) - started_at )) >> "$result_file"
|
|
||||||
done
|
|
||||||
|
|
||||||
local stats_line
|
|
||||||
stats_line=$(calc_stats "$result_file")
|
|
||||||
echo "${provider_name}|$(echo "$stats_line" | awk '{print $1"|"$2"|"$3"|"$4}')"
|
|
||||||
}
|
|
||||||
|
|
||||||
echo "payload_kb|provider|min|avg|max|p95"
|
|
||||||
run_probe "yandex" "$Y_QURL" "$TMPDIR/payload_10k.txt" "$TMPDIR/y_10k.txt" ysqs | awk -F'|' '{print "10|"$0}'
|
|
||||||
run_probe "shared-sqs" "$O_QURL" "$TMPDIR/payload_10k.txt" "$TMPDIR/o_10k.txt" osqs | awk -F'|' '{print "10|"$0}'
|
|
||||||
run_probe "yandex" "$Y_QURL" "$TMPDIR/payload_32k.txt" "$TMPDIR/y_32k.txt" ysqs | awk -F'|' '{print "32|"$0}'
|
|
||||||
run_probe "shared-sqs" "$O_QURL" "$TMPDIR/payload_32k.txt" "$TMPDIR/o_32k.txt" osqs | awk -F'|' '{print "32|"$0}'
|
|
||||||
@@ -1,267 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
# tests/quick_test.sh — Быстрая проверка shared-sqs (smoke test)
|
|
||||||
# Created: 2026-04-09
|
|
||||||
# Updated: 2026-04-11 — полная поддержка JWT (nubes api_token), все 17 SQS команд,
|
|
||||||
# TagQueue/UntagQueue/ListQueueTags/ChangeMessageVisibilityBatch,
|
|
||||||
# UI API с авторизацией через Bearer JWT.
|
|
||||||
# Покрывает: health, admin CRUD, все SQS команды, UI API с JWT.
|
|
||||||
# Требования: curl, aws CLI, python3
|
|
||||||
# Запуск:
|
|
||||||
# bash tests/quick_test.sh
|
|
||||||
# BASE_URL=https://qu.kube5s.ru ADMIN_TOKEN=... NUBES_JWT=... bash tests/quick_test.sh
|
|
||||||
|
|
||||||
set -uo pipefail
|
|
||||||
|
|
||||||
BASE_URL="${BASE_URL:-https://qu.kube5s.ru}"
|
|
||||||
ADMIN_TOKEN="${ADMIN_TOKEN:-sqs-admin-7a7d8bd0c060a75c198d48680f34077a}"
|
|
||||||
NUBES_JWT="${NUBES_JWT:-eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhdXRoLWFwaSIsInN1YiI6IjAxOTllMzI1LTFjZGYtN2NkYS05MzE5LWU1MzAyYTg1ZTI5MSIsImV4cCI6MTc4NjkzMjI2MCwiaWF0IjoxNzcxMzgwMjYwLCJqdGkiOiIzOTQ3ZTgyMy0yNjljLTQ0MTAtYmU0My1iNGVkNTc1Njg0ZTQiLCJhdXRoX3RpbWUiOjAsInR5cCI6IiIsImF6cCI6IiIsInNlc3Npb25fc3RhdGUiOiIiLCJhY3IiOiIiLCJhbGxvd2VkLW9yaWdpbnMiOm51bGwsInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6bnVsbH0sInJlc291cmNlX2FjY2VzcyI6eyJhY2NvdW50Ijp7InJvbGVzIjpudWxsfX0sInNjb3BlIjoiIiwic2lkIjoiIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJuYW1lIjoiIiwiQ2xpZW50SUQiOiIiLCJncm91cHMiOm51bGwsInByZWZlcnJlZF91c2VybmFtZSI6IiIsImdpdmVuX25hbWUiOiIiLCJmYW1pbHlfbmFtZSI6IiIsImVtYWlsIjoidGF6ZXRAbmFyb2QucnUifQ.hzpIIqNWkKIoYUXDaLY7DLyGKH70rz0ZTqanv19qxF10i3N1t1g_KknA4Qsw1MduTyLzIz7y5SRSr4PSQ1gzR0vB_C0GudSFUhyBNNKkS4ClhRDWW9eN_IIEljbiJMLQi2L07XJ7Y5DQ0sIHRAPLkreCDFMKQ0yTCrKoScCJIDuUqzaTcOaX-hfjaxW8iV0SZMDxl0C5O3tke0btxkaLBaAcWH0V-1yu2r2m29fyU33FqikF0xAcDXiuZphfsrShKQYArZjKAphYCP_Vpmr-1sdjinkn8sPSk1qZny0rka8G6WVZUGaZSOnW8SYNLVUwdqtuQmK-Y18o7U0Suzrsjg}"
|
|
||||||
REGION="us-east-1"
|
|
||||||
TS=$(date +%s)
|
|
||||||
|
|
||||||
PASS=0
|
|
||||||
FAIL=0
|
|
||||||
|
|
||||||
ok() { echo " ✅ $1"; PASS=$((PASS+1)); }
|
|
||||||
fail() { echo " ❌ $1"; FAIL=$((FAIL+1)); }
|
|
||||||
|
|
||||||
check() {
|
|
||||||
local label="$1" body="$2" pattern="$3"
|
|
||||||
if echo "$body" | grep -qE "$pattern"; then ok "$label"; else fail "$label: $(echo "$body" | head -3)"; fi
|
|
||||||
}
|
|
||||||
|
|
||||||
check_not() {
|
|
||||||
local label="$1" body="$2" pattern="$3"
|
|
||||||
if echo "$body" | grep -qE "$pattern"; then fail "$label"; else ok "$label"; fi
|
|
||||||
}
|
|
||||||
|
|
||||||
check_http() {
|
|
||||||
local label="$1" want="$2" got="$3"
|
|
||||||
if [[ "$got" == "$want" ]]; then ok "$label (HTTP $got)"; else fail "$label — ожидалось $want, получено $got"; fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# sqs — AWS CLI враппер
|
|
||||||
sqs() {
|
|
||||||
local ak="$1" sk="$2"; shift 2
|
|
||||||
AWS_ACCESS_KEY_ID="$ak" AWS_SECRET_ACCESS_KEY="$sk" AWS_DEFAULT_REGION="$REGION" \
|
|
||||||
aws --endpoint-url "$BASE_URL" --output json sqs "$@" 2>&1
|
|
||||||
}
|
|
||||||
|
|
||||||
# admin — Admin API запрос с Bearer admin token
|
|
||||||
admin() {
|
|
||||||
local method="$1" path="$2" body="${3:-}"
|
|
||||||
if [[ -n "$body" ]]; then
|
|
||||||
curl -sf --max-time 15 -X "$method" \
|
|
||||||
-H "Authorization: Bearer $ADMIN_TOKEN" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d "$body" "${BASE_URL}${path}" 2>&1
|
|
||||||
else
|
|
||||||
curl -sf --max-time 15 -X "$method" \
|
|
||||||
-H "Authorization: Bearer $ADMIN_TOKEN" \
|
|
||||||
"${BASE_URL}${path}" 2>&1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# ui — UI API запрос с Bearer JWT (nubes токен)
|
|
||||||
ui() {
|
|
||||||
local method="$1" path="$2" body="${3:-}"
|
|
||||||
if [[ -n "$body" ]]; then
|
|
||||||
curl -sf --max-time 15 -X "$method" \
|
|
||||||
-H "Authorization: Bearer $NUBES_JWT" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d "$body" "${BASE_URL}/ui/api${path}" 2>&1
|
|
||||||
else
|
|
||||||
curl -sf --max-time 15 -X "$method" \
|
|
||||||
-H "Authorization: Bearer $NUBES_JWT" \
|
|
||||||
"${BASE_URL}/ui/api${path}" 2>&1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
echo "════════════════════════════════════════"
|
|
||||||
echo " shared-sqs Quick Test"
|
|
||||||
echo " Endpoint: $BASE_URL"
|
|
||||||
echo "════════════════════════════════════════"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# ── 1. Health ──
|
|
||||||
echo "── 1. Health ──"
|
|
||||||
R=$(curl -sf --max-time 10 "${BASE_URL}/health" 2>&1)
|
|
||||||
check "GET /health → ok" "$R" "[Oo][Kk]|status"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# ── 2. UI API авторизация (JWT auth) ──
|
|
||||||
echo "── 2. UI API авторизация ──"
|
|
||||||
R=$(curl -sf --max-time 15 -X POST \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d "{\"token\":\"$NUBES_JWT\"}" \
|
|
||||||
"${BASE_URL}/ui/api/auth" 2>&1)
|
|
||||||
check "POST /ui/api/auth → tenant_id" "$R" "tenant_id"
|
|
||||||
UI_TID=$(echo "$R" | python3 -c "import sys,json; print(json.load(sys.stdin).get('tenant_id',''))" 2>/dev/null || true)
|
|
||||||
echo " UI Tenant ID: $UI_TID"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# ── 3. Создание тенанта через Admin API ──
|
|
||||||
echo "── 3. Admin API — создание тенанта ──"
|
|
||||||
RESP=$(admin POST /admin/tenants '{"name":"quick-'"$TS"'","max_queues":10}')
|
|
||||||
check "POST /admin/tenants → access_key" "$RESP" "access_key"
|
|
||||||
AK=$(echo "$RESP" | python3 -c "import sys,json; print(json.load(sys.stdin)['access_key'])")
|
|
||||||
SK=$(echo "$RESP" | python3 -c "import sys,json; print(json.load(sys.stdin)['secret_key'])")
|
|
||||||
TID=$(echo "$RESP" | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])")
|
|
||||||
echo " Tenant ID: $TID"
|
|
||||||
echo " Access Key: $AK"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
QNAME="quick-q-$TS"
|
|
||||||
QURL="${BASE_URL}/${TID}/${QNAME}"
|
|
||||||
|
|
||||||
# ── 4. SQS Queue CRUD ──
|
|
||||||
echo "── 4. SQS Queue CRUD ──"
|
|
||||||
R=$(sqs "$AK" "$SK" create-queue --queue-name "$QNAME")
|
|
||||||
check "CreateQueue → QueueUrl" "$R" "QueueUrl"
|
|
||||||
|
|
||||||
R=$(sqs "$AK" "$SK" list-queues)
|
|
||||||
check "ListQueues → очередь в списке" "$R" "$QNAME"
|
|
||||||
|
|
||||||
R=$(sqs "$AK" "$SK" get-queue-url --queue-name "$QNAME")
|
|
||||||
check "GetQueueUrl → QueueUrl" "$R" "QueueUrl"
|
|
||||||
|
|
||||||
R=$(sqs "$AK" "$SK" get-queue-attributes --queue-url "$QURL" --attribute-names All)
|
|
||||||
check "GetQueueAttributes → VisibilityTimeout" "$R" "VisibilityTimeout"
|
|
||||||
|
|
||||||
R=$(sqs "$AK" "$SK" set-queue-attributes --queue-url "$QURL" --attributes '{"VisibilityTimeout":"60"}' 2>&1 && echo "OK" || echo "FAIL")
|
|
||||||
check "SetQueueAttributes → OK" "$R" "OK"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# ── 5. TagQueue / UntagQueue / ListQueueTags ──
|
|
||||||
echo "── 5. Tags API ──"
|
|
||||||
R=$(sqs "$AK" "$SK" tag-queue --queue-url "$QURL" --tags env=test,owner=qa 2>&1 && echo "OK" || echo "FAIL")
|
|
||||||
check "TagQueue → OK" "$R" "OK"
|
|
||||||
|
|
||||||
R=$(sqs "$AK" "$SK" list-queue-tags --queue-url "$QURL")
|
|
||||||
check "ListQueueTags → env=test" "$R" "env|test"
|
|
||||||
|
|
||||||
R=$(sqs "$AK" "$SK" untag-queue --queue-url "$QURL" --tag-keys env 2>&1 && echo "OK" || echo "FAIL")
|
|
||||||
check "UntagQueue → OK" "$R" "OK"
|
|
||||||
|
|
||||||
R=$(sqs "$AK" "$SK" list-queue-tags --queue-url "$QURL")
|
|
||||||
check_not "ListQueueTags → env удалён" "$R" '"env"'
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# ── 6. Сообщения: Send / Receive / Delete ──
|
|
||||||
echo "── 6. Send / Receive / Delete ──"
|
|
||||||
R=$(sqs "$AK" "$SK" send-message --queue-url "$QURL" --message-body "hello-$TS")
|
|
||||||
check "SendMessage → MessageId" "$R" "MessageId"
|
|
||||||
check "SendMessage → MD5" "$R" "MD5OfMessageBody"
|
|
||||||
|
|
||||||
R=$(sqs "$AK" "$SK" receive-message --queue-url "$QURL" --visibility-timeout 30)
|
|
||||||
check "ReceiveMessage → тело" "$R" "hello-$TS"
|
|
||||||
RECEIPT=$(echo "$R" | python3 -c "import sys,json; msgs=json.load(sys.stdin).get('Messages',[]); print(msgs[0]['ReceiptHandle'] if msgs else '')" 2>/dev/null || true)
|
|
||||||
|
|
||||||
if [[ -n "$RECEIPT" ]]; then
|
|
||||||
R=$(sqs "$AK" "$SK" change-message-visibility --queue-url "$QURL" --receipt-handle "$RECEIPT" --visibility-timeout 60 2>&1 && echo "OK" || echo "FAIL")
|
|
||||||
check "ChangeMessageVisibility → OK" "$R" "OK"
|
|
||||||
|
|
||||||
sqs "$AK" "$SK" delete-message --queue-url "$QURL" --receipt-handle "$RECEIPT" >/dev/null 2>&1
|
|
||||||
ok "DeleteMessage → без ошибок"
|
|
||||||
else
|
|
||||||
fail "ReceiptHandle не получен"
|
|
||||||
fi
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# ── 7. Batch операции ──
|
|
||||||
echo "── 7. Batch ──"
|
|
||||||
R=$(sqs "$AK" "$SK" send-message-batch --queue-url "$QURL" \
|
|
||||||
--entries '[{"Id":"b1","MessageBody":"batch-1"},{"Id":"b2","MessageBody":"batch-2"},{"Id":"b3","MessageBody":"batch-3"}]')
|
|
||||||
check "SendMessageBatch → Successful" "$R" "Successful"
|
|
||||||
|
|
||||||
R=$(sqs "$AK" "$SK" receive-message --queue-url "$QURL" --max-number-of-messages 3)
|
|
||||||
RECEIPTS=$(echo "$R" | python3 -c "import sys,json; msgs=json.load(sys.stdin).get('Messages',[]); print('\n'.join(m['ReceiptHandle'] for m in msgs))" 2>/dev/null || true)
|
|
||||||
|
|
||||||
# ChangeMessageVisibilityBatch
|
|
||||||
if [[ -n "$RECEIPTS" ]]; then
|
|
||||||
CMV_ENTRIES=$(echo "$RECEIPTS" | python3 -c "
|
|
||||||
import sys
|
|
||||||
entries = []
|
|
||||||
for i, rh in enumerate(sys.stdin.read().strip().split('\n'), 1):
|
|
||||||
if rh:
|
|
||||||
entries.append('{\"Id\":\"cmv%d\",\"ReceiptHandle\":\"%s\",\"VisibilityTimeout\":60}' % (i, rh))
|
|
||||||
print('[' + ','.join(entries) + ']')
|
|
||||||
" 2>/dev/null || echo "[]")
|
|
||||||
if [[ "$CMV_ENTRIES" != "[]" ]]; then
|
|
||||||
R=$(sqs "$AK" "$SK" change-message-visibility-batch --queue-url "$QURL" --entries "$CMV_ENTRIES")
|
|
||||||
check "ChangeMessageVisibilityBatch → Successful" "$R" "Successful"
|
|
||||||
fi
|
|
||||||
|
|
||||||
DEL_ENTRIES=$(echo "$RECEIPTS" | python3 -c "
|
|
||||||
import sys
|
|
||||||
entries = []
|
|
||||||
for i, rh in enumerate(sys.stdin.read().strip().split('\n'), 1):
|
|
||||||
if rh:
|
|
||||||
entries.append('{\"Id\":\"d%d\",\"ReceiptHandle\":\"%s\"}' % (i, rh))
|
|
||||||
print('[' + ','.join(entries) + ']')
|
|
||||||
" 2>/dev/null || echo "[]")
|
|
||||||
if [[ "$DEL_ENTRIES" != "[]" ]]; then
|
|
||||||
R=$(sqs "$AK" "$SK" delete-message-batch --queue-url "$QURL" --entries "$DEL_ENTRIES")
|
|
||||||
check "DeleteMessageBatch → Successful" "$R" "Successful"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# ── 8. PurgeQueue ──
|
|
||||||
echo "── 8. PurgeQueue ──"
|
|
||||||
sqs "$AK" "$SK" send-message --queue-url "$QURL" --message-body "purge-me" >/dev/null 2>&1 || true
|
|
||||||
R=$(sqs "$AK" "$SK" purge-queue --queue-url "$QURL" 2>&1 && echo "OK" || echo "FAIL")
|
|
||||||
check "PurgeQueue → OK" "$R" "OK"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# ── 9. UI API с JWT ──
|
|
||||||
echo "── 9. UI API с JWT ──"
|
|
||||||
R=$(ui GET /health)
|
|
||||||
check "GET /ui/api/health → ok" "$R" "ok|status"
|
|
||||||
|
|
||||||
if [[ -n "$UI_TID" ]]; then
|
|
||||||
R=$(ui GET "/tenants/${UI_TID}/queues")
|
|
||||||
check "UI GET /tenants/{id}/queues → ответ" "$R" "\[|\]|name|queues"
|
|
||||||
|
|
||||||
R=$(ui POST "/tenants/${UI_TID}/queues" '{"name":"ui-q-'"$TS"'"}')
|
|
||||||
check "UI POST /queues → создана" "$R" "ui-q-$TS|name"
|
|
||||||
|
|
||||||
R=$(ui POST "/tenants/${UI_TID}/queues/ui-q-${TS}/messages" '{"body":"ui-msg-'"$TS"'"}')
|
|
||||||
check "UI POST /messages → id" "$R" '"id"'
|
|
||||||
|
|
||||||
R=$(ui GET "/tenants/${UI_TID}/queues/ui-q-${TS}/messages")
|
|
||||||
check "UI GET /messages → тело" "$R" "ui-msg-$TS"
|
|
||||||
check_not "UI GET /messages → нет receipt_handle" "$R" "receipt_handle"
|
|
||||||
|
|
||||||
R=$(ui DELETE "/tenants/${UI_TID}/queues/ui-q-${TS}/messages" "")
|
|
||||||
check_not "UI DELETE /messages (purge) → сообщение исчезло" \
|
|
||||||
"$(ui GET "/tenants/${UI_TID}/queues/ui-q-${TS}/messages")" "ui-msg-$TS"
|
|
||||||
|
|
||||||
HTTP=$(curl -sf --max-time 15 -o /dev/null -w "%{http_code}" -X DELETE \
|
|
||||||
-H "Authorization: Bearer $NUBES_JWT" \
|
|
||||||
"${BASE_URL}/ui/api/tenants/${UI_TID}/queues/ui-q-${TS}")
|
|
||||||
if [[ "$HTTP" == "204" || "$HTTP" == "200" ]]; then
|
|
||||||
ok "UI DELETE /queues/ui-q → HTTP $HTTP"
|
|
||||||
else
|
|
||||||
fail "UI DELETE /queues/ui-q → HTTP $HTTP (ожидалось 200/204)"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
fail "UI API: JWT auth не дал tenant_id — пропускаем UI тесты"
|
|
||||||
fi
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# ── 10. DeleteQueue ──
|
|
||||||
echo "── 10. DeleteQueue ──"
|
|
||||||
R=$(sqs "$AK" "$SK" delete-queue --queue-url "$QURL" 2>&1 && echo "OK" || echo "FAIL")
|
|
||||||
check "DeleteQueue → OK" "$R" "OK"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# ── Cleanup ──
|
|
||||||
echo "── Cleanup ──"
|
|
||||||
admin DELETE "/admin/tenants/$TID" >/dev/null 2>&1 && ok "DELETE тенанта" || fail "DELETE тенанта"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
echo "════════════════════════════════════════"
|
|
||||||
printf " Результат: ✅ %d ❌ %d\n" "$PASS" "$FAIL"
|
|
||||||
echo "════════════════════════════════════════"
|
|
||||||
|
|
||||||
[[ $FAIL -eq 0 ]] && exit 0 || exit 1
|
|
||||||
@@ -1,211 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# tests/shared_sqs_test.sh
|
|
||||||
# Интеграционные тесты shared-sqs: Admin API, изоляция тенантов, CRUD, лимиты
|
|
||||||
# Created: 2026-04-09
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
BASE_URL="${BASE_URL:-http://localhost:4100}"
|
|
||||||
ADMIN_TOKEN="${ADMIN_TOKEN:-dev-token-123}"
|
|
||||||
PASS=0
|
|
||||||
FAIL=0
|
|
||||||
|
|
||||||
pass() { echo " [PASS] $1"; PASS=$((PASS+1)); }
|
|
||||||
fail() { echo " [FAIL] $1"; FAIL=$((FAIL+1)); }
|
|
||||||
|
|
||||||
check_status() {
|
|
||||||
local label="$1" expected="$2" actual="$3"
|
|
||||||
if [[ "$actual" == "$expected" ]]; then
|
|
||||||
pass "$label (HTTP $actual)"
|
|
||||||
else
|
|
||||||
fail "$label -- expected HTTP $expected, got $actual"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
check_contains() {
|
|
||||||
local label="$1" pattern="$2" body="$3"
|
|
||||||
if echo "$body" | grep -q "$pattern"; then
|
|
||||||
pass "$label"
|
|
||||||
else
|
|
||||||
fail "$label -- pattern '$pattern' not found in: $body"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
check_not_contains() {
|
|
||||||
local label="$1" pattern="$2" body="$3"
|
|
||||||
if ! echo "$body" | grep -q "$pattern"; then
|
|
||||||
pass "$label"
|
|
||||||
else
|
|
||||||
fail "$label -- pattern '$pattern' FOUND but should be absent"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
sqs_req() {
|
|
||||||
local access_key="$1" secret_key="$2" action="$3"
|
|
||||||
shift 3
|
|
||||||
AWS_ACCESS_KEY_ID="$access_key" \
|
|
||||||
AWS_SECRET_ACCESS_KEY="$secret_key" \
|
|
||||||
AWS_DEFAULT_REGION="us-east-1" \
|
|
||||||
aws --endpoint-url "$BASE_URL" --output json sqs "$action" "$@" 2>&1
|
|
||||||
}
|
|
||||||
|
|
||||||
sqs_req_status() {
|
|
||||||
local access_key="$1" secret_key="$2" action="$3"
|
|
||||||
shift 3
|
|
||||||
AWS_ACCESS_KEY_ID="$access_key" \
|
|
||||||
AWS_SECRET_ACCESS_KEY="$secret_key" \
|
|
||||||
AWS_DEFAULT_REGION="us-east-1" \
|
|
||||||
aws --endpoint-url "$BASE_URL" --output json sqs "$action" "$@" > /dev/null 2>&1
|
|
||||||
echo $?
|
|
||||||
}
|
|
||||||
|
|
||||||
echo "========================================"
|
|
||||||
echo " shared-sqs integration tests"
|
|
||||||
echo " BASE_URL=$BASE_URL"
|
|
||||||
echo "========================================"
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "--- 1. Health check ---"
|
|
||||||
STATUS=$(curl -s -o /dev/null -w "%{http_code}" "$BASE_URL/health")
|
|
||||||
check_status "GET /health" "200" "$STATUS"
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "--- 2. Admin API -- без токена ---"
|
|
||||||
STATUS=$(curl -s -o /dev/null -w "%{http_code}" -X GET "$BASE_URL/admin/tenants")
|
|
||||||
check_status "GET /admin/tenants без токена -> 401" "401" "$STATUS"
|
|
||||||
|
|
||||||
STATUS=$(curl -s -o /dev/null -w "%{http_code}" -X POST "$BASE_URL/admin/tenants" \
|
|
||||||
-H "Content-Type: application/json" -d '{"name":"test"}')
|
|
||||||
check_status "POST /admin/tenants без токена -> 401" "401" "$STATUS"
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "--- 3. Admin API -- создание тенантов ---"
|
|
||||||
RESP_A=$(curl -s -X POST "$BASE_URL/admin/tenants" \
|
|
||||||
-H "Authorization: Bearer $ADMIN_TOKEN" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{"name":"tenant-a","max_queues":10}')
|
|
||||||
check_contains "Создать tenant-a -- access_key" "access_key" "$RESP_A"
|
|
||||||
check_contains "Создать tenant-a -- secret_key" "secret_key" "$RESP_A"
|
|
||||||
check_contains "Создать tenant-a -- id" "\"id\"" "$RESP_A"
|
|
||||||
|
|
||||||
ACCESS_A=$(echo "$RESP_A" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['access_key'])")
|
|
||||||
SECRET_A=$(echo "$RESP_A" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['secret_key'])")
|
|
||||||
ID_A=$(echo "$RESP_A" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['id'])")
|
|
||||||
|
|
||||||
RESP_B=$(curl -s -X POST "$BASE_URL/admin/tenants" \
|
|
||||||
-H "Authorization: Bearer $ADMIN_TOKEN" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{"name":"tenant-b","max_queues":10}')
|
|
||||||
check_contains "Создать tenant-b -- access_key" "access_key" "$RESP_B"
|
|
||||||
|
|
||||||
ACCESS_B=$(echo "$RESP_B" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['access_key'])")
|
|
||||||
SECRET_B=$(echo "$RESP_B" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['secret_key'])")
|
|
||||||
ID_B=$(echo "$RESP_B" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['id'])")
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "--- 4. Admin API -- list/get ---"
|
|
||||||
LIST=$(curl -s "$BASE_URL/admin/tenants" -H "Authorization: Bearer $ADMIN_TOKEN")
|
|
||||||
check_contains "GET /admin/tenants -- tenant-a" "tenant-a" "$LIST"
|
|
||||||
check_contains "GET /admin/tenants -- tenant-b" "tenant-b" "$LIST"
|
|
||||||
check_not_contains "GET /admin/tenants -- НЕТ secret_key" "secret_key" "$LIST"
|
|
||||||
|
|
||||||
GET_A=$(curl -s "$BASE_URL/admin/tenants/$ID_A" -H "Authorization: Bearer $ADMIN_TOKEN")
|
|
||||||
check_contains "GET /admin/tenants/{id} -- tenant-a" "tenant-a" "$GET_A"
|
|
||||||
check_not_contains "GET /admin/tenants/{id} -- нет secret_key" "secret_key" "$GET_A"
|
|
||||||
|
|
||||||
STATUS404=$(curl -s -o /dev/null -w "%{http_code}" "$BASE_URL/admin/tenants/no-such-id" \
|
|
||||||
-H "Authorization: Bearer $ADMIN_TOKEN")
|
|
||||||
check_status "GET несуществующего тенанта -> 404" "404" "$STATUS404"
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "--- 5. CRUD flow (tenant-a) ---"
|
|
||||||
QUEUE_URL_A=$(sqs_req "$ACCESS_A" "$SECRET_A" create-queue --queue-name my-queue \
|
|
||||||
| python3 -c "import sys,json; print(json.load(sys.stdin)['QueueUrl'])")
|
|
||||||
check_contains "CreateQueue -> QueueUrl содержит my-queue" "my-queue" "$QUEUE_URL_A"
|
|
||||||
check_contains "CreateQueue -> QueueUrl содержит ID тенанта" "$ID_A" "$QUEUE_URL_A"
|
|
||||||
|
|
||||||
MSG_ID=$(sqs_req "$ACCESS_A" "$SECRET_A" send-message \
|
|
||||||
--queue-url "$QUEUE_URL_A" --message-body "hello-world" \
|
|
||||||
| python3 -c "import sys,json; print(json.load(sys.stdin)['MessageId'])")
|
|
||||||
check_contains "SendMessage -> MessageId" "-" "$MSG_ID"
|
|
||||||
|
|
||||||
RECV=$(sqs_req "$ACCESS_A" "$SECRET_A" receive-message --queue-url "$QUEUE_URL_A")
|
|
||||||
check_contains "ReceiveMessage -> тело hello-world" "hello-world" "$RECV"
|
|
||||||
|
|
||||||
RECEIPT=$(echo "$RECV" | python3 -c "import sys,json; msgs=json.load(sys.stdin).get('Messages',[]); print(msgs[0]['ReceiptHandle'] if msgs else '')")
|
|
||||||
if [[ -n "$RECEIPT" ]]; then
|
|
||||||
sqs_req "$ACCESS_A" "$SECRET_A" delete-message \
|
|
||||||
--queue-url "$QUEUE_URL_A" --receipt-handle "$RECEIPT" > /dev/null 2>&1
|
|
||||||
pass "DeleteMessage -- без ошибок"
|
|
||||||
else
|
|
||||||
fail "DeleteMessage -- нет ReceiptHandle"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "--- 6. Изоляция тенантов ---"
|
|
||||||
QUEUE_URL_B=$(sqs_req "$ACCESS_B" "$SECRET_B" create-queue --queue-name my-queue \
|
|
||||||
| python3 -c "import sys,json; print(json.load(sys.stdin)['QueueUrl'])")
|
|
||||||
check_contains "tenant-B CreateQueue my-queue -> ID tenant-B в URL" "$ID_B" "$QUEUE_URL_B"
|
|
||||||
|
|
||||||
sqs_req "$ACCESS_A" "$SECRET_A" send-message \
|
|
||||||
--queue-url "$QUEUE_URL_A" --message-body "msg-for-A" > /dev/null 2>&1
|
|
||||||
|
|
||||||
RECV_B=$(sqs_req "$ACCESS_B" "$SECRET_B" receive-message --queue-url "$QUEUE_URL_B" 2>&1 || true)
|
|
||||||
check_not_contains "Изоляция: tenant-B НЕ получает msg-for-A" "msg-for-A" "$RECV_B"
|
|
||||||
|
|
||||||
LIST_B=$(sqs_req "$ACCESS_B" "$SECRET_B" list-queues 2>&1 || true)
|
|
||||||
check_not_contains "ListQueues tenant-B -- нет ID tenant-A" "$ID_A" "$LIST_B"
|
|
||||||
|
|
||||||
LIST_A=$(sqs_req "$ACCESS_A" "$SECRET_A" list-queues 2>&1 || true)
|
|
||||||
check_not_contains "ListQueues tenant-A -- нет ID tenant-B" "$ID_B" "$LIST_A"
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "--- 7. SQS с невалидным ключом -> ошибка ---"
|
|
||||||
EXIT_FAKE=$(sqs_req_status "FAKE-KEY-0000000000" "fakesecret0000" list-queues)
|
|
||||||
if [[ "$EXIT_FAKE" != "0" ]]; then
|
|
||||||
pass "Невалидный access_key -> ошибка auth"
|
|
||||||
else
|
|
||||||
fail "Невалидный access_key -> ожидалась ошибка, получен 200"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "--- 8. Лимит очередей (max_queues=2) ---"
|
|
||||||
RESP_LIM=$(curl -s -X POST "$BASE_URL/admin/tenants" \
|
|
||||||
-H "Authorization: Bearer $ADMIN_TOKEN" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{"name":"limited","max_queues":2}')
|
|
||||||
ACCESS_LIM=$(echo "$RESP_LIM" | python3 -c "import sys,json; print(json.load(sys.stdin)['access_key'])")
|
|
||||||
SECRET_LIM=$(echo "$RESP_LIM" | python3 -c "import sys,json; print(json.load(sys.stdin)['secret_key'])")
|
|
||||||
|
|
||||||
sqs_req "$ACCESS_LIM" "$SECRET_LIM" create-queue --queue-name q1 > /dev/null 2>&1
|
|
||||||
pass "Очередь 1 из 2 -- создана"
|
|
||||||
sqs_req "$ACCESS_LIM" "$SECRET_LIM" create-queue --queue-name q2 > /dev/null 2>&1
|
|
||||||
pass "Очередь 2 из 2 -- создана"
|
|
||||||
|
|
||||||
EXIT3=$(sqs_req_status "$ACCESS_LIM" "$SECRET_LIM" create-queue --queue-name q3)
|
|
||||||
if [[ "$EXIT3" != "0" ]]; then
|
|
||||||
pass "Очередь 3 при max_queues=2 -> лимит (ошибка)"
|
|
||||||
else
|
|
||||||
fail "Очередь 3 при max_queues=2 -> ожидалась ошибка, создалась!"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "--- 9. DELETE tenant -> очереди удалены ---"
|
|
||||||
STATUS_DEL=$(curl -s -o /dev/null -w "%{http_code}" -X DELETE \
|
|
||||||
"$BASE_URL/admin/tenants/$ID_B" \
|
|
||||||
-H "Authorization: Bearer $ADMIN_TOKEN")
|
|
||||||
check_status "DELETE tenant-b -> 204" "204" "$STATUS_DEL"
|
|
||||||
|
|
||||||
EXIT_AFTER=$(sqs_req_status "$ACCESS_B" "$SECRET_B" list-queues)
|
|
||||||
if [[ "$EXIT_AFTER" != "0" ]]; then
|
|
||||||
pass "После DELETE tenant -- его ключи -> ошибка auth"
|
|
||||||
else
|
|
||||||
fail "После DELETE tenant -- его ключи вернули 200"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "========================================"
|
|
||||||
echo " Результат: PASS=$PASS FAIL=$FAIL"
|
|
||||||
echo "========================================"
|
|
||||||
|
|
||||||
[[ $FAIL -eq 0 ]] && exit 0 || exit 1
|
|
||||||
@@ -1,966 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
# stress_test.sh — ЖЁСТКИЙ стресс-тест shared-sqs
|
|
||||||
# Created: 2026-04-11
|
|
||||||
# Updated: 2026-04-11 — v2: масштаб, long-poll, race, batch, 50 очередей, memory, multi-kill
|
|
||||||
# Покрывает:
|
|
||||||
# 1. Подготовка (тенанты, очереди)
|
|
||||||
# 2. Параллельная отправка — 50 воркеров × 20 msg = 1000 сообщений
|
|
||||||
# 3. Параллельное чтение + удаление (гонка)
|
|
||||||
# 4. Multi-tenant изоляция (5 тенантов одновременно)
|
|
||||||
# 5. Burst — 100 сообщений одновременно
|
|
||||||
# 6. Long-polling + параллельные producer'ы
|
|
||||||
# 7. Двойное удаление (race на receipt handle)
|
|
||||||
# 8. Batch-операции конкурентно (SendMessageBatch + DeleteMessageBatch)
|
|
||||||
# 9. Много очередей — 30 очередей на тенанта
|
|
||||||
# 10. Kill pod + восстановление из Redis
|
|
||||||
# 11. Redis disconnect / reconnect
|
|
||||||
# 12. Смешанная нагрузка (send+recv+delete+getattr, 20s)
|
|
||||||
# 13. Memory leak check (RSS до/после нагрузки)
|
|
||||||
# 14. Multi-kill: kill → восстановление → kill → восстановление
|
|
||||||
# 15. Cleanup
|
|
||||||
# Запуск: BASE_URL=https://qu.kube5s.ru ADMIN_TOKEN=... bash tests/stress_test.sh
|
|
||||||
|
|
||||||
set -uo pipefail
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════════
|
|
||||||
# КОНФИГУРАЦИЯ
|
|
||||||
# ═══════════════════════════════════════════
|
|
||||||
BASE_URL="${BASE_URL:-https://qu.kube5s.ru}"
|
|
||||||
ADMIN_TOKEN="${ADMIN_TOKEN:-sqs-admin-7a7d8bd0c060a75c198d48680f34077a}"
|
|
||||||
TS=$(date +%s)
|
|
||||||
NAMESPACE="shared-sqs"
|
|
||||||
POD_LABEL="app.kubernetes.io/name=shared-sqs"
|
|
||||||
|
|
||||||
# Параметры нагрузки — ЖЁСТКИЕ
|
|
||||||
CONCURRENT_WORKERS=20 # параллельных воркеров
|
|
||||||
MESSAGES_PER_WORKER=10 # сообщений на воркер (итого 1000)
|
|
||||||
BURST_SIZE=80 # размер burst-пакета
|
|
||||||
TENANT_COUNT=5 # тенантов для multi-tenant теста
|
|
||||||
QUEUE_FLOOD_COUNT=25 # очередей на тенанта
|
|
||||||
MIXED_DURATION=15 # длительность смешанной нагрузки (сек)
|
|
||||||
BATCH_WORKERS=8 # воркеров для batch-операций
|
|
||||||
|
|
||||||
PASS=0
|
|
||||||
FAIL=0
|
|
||||||
TOTAL=0
|
|
||||||
TMPDIR_STRESS=$(mktemp -d /tmp/sqs_stress_XXXXX)
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════════
|
|
||||||
# ХЕЛПЕРЫ
|
|
||||||
# ═══════════════════════════════════════════
|
|
||||||
|
|
||||||
json_field() {
|
|
||||||
python3 -c "import sys,json; print(json.load(sys.stdin).get('$1',''))" 2>/dev/null
|
|
||||||
}
|
|
||||||
|
|
||||||
# progress_dot — печатает точку без переноса строки (прогресс)
|
|
||||||
progress_dot() {
|
|
||||||
printf "." >&2
|
|
||||||
}
|
|
||||||
|
|
||||||
# progress_done — завершает строку прогресса
|
|
||||||
progress_done() {
|
|
||||||
echo "" >&2
|
|
||||||
}
|
|
||||||
|
|
||||||
check() {
|
|
||||||
local name="$1" output="$2" pattern="$3"
|
|
||||||
TOTAL=$((TOTAL + 1))
|
|
||||||
if echo "$output" | grep -qE "$pattern"; then
|
|
||||||
PASS=$((PASS + 1))
|
|
||||||
echo " ✅ $name"
|
|
||||||
else
|
|
||||||
FAIL=$((FAIL + 1))
|
|
||||||
echo " ❌ $name"
|
|
||||||
echo " ожидалось: $pattern"
|
|
||||||
echo " получено: $(echo "$output" | head -5)"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
check_eq() {
|
|
||||||
local name="$1" got="$2" want="$3"
|
|
||||||
TOTAL=$((TOTAL + 1))
|
|
||||||
if [[ "$got" == "$want" ]]; then
|
|
||||||
PASS=$((PASS + 1))
|
|
||||||
echo " ✅ $name ($got)"
|
|
||||||
else
|
|
||||||
FAIL=$((FAIL + 1))
|
|
||||||
echo " ❌ $name: ожидалось=$want, получено=$got"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
check_ge() {
|
|
||||||
local name="$1" got="$2" want="$3"
|
|
||||||
TOTAL=$((TOTAL + 1))
|
|
||||||
if [[ "$got" -ge "$want" ]]; then
|
|
||||||
PASS=$((PASS + 1))
|
|
||||||
echo " ✅ $name ($got >= $want)"
|
|
||||||
else
|
|
||||||
FAIL=$((FAIL + 1))
|
|
||||||
echo " ❌ $name: ожидалось >=$want, получено=$got"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
check_le() {
|
|
||||||
local name="$1" got="$2" want="$3"
|
|
||||||
TOTAL=$((TOTAL + 1))
|
|
||||||
if [[ "$got" -le "$want" ]]; then
|
|
||||||
PASS=$((PASS + 1))
|
|
||||||
echo " ✅ $name ($got <= $want)"
|
|
||||||
else
|
|
||||||
FAIL=$((FAIL + 1))
|
|
||||||
echo " ❌ $name: ожидалось <=$want, получено=$got"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
sqs() {
|
|
||||||
local ak="$1" sk="$2"
|
|
||||||
shift 2
|
|
||||||
AWS_ACCESS_KEY_ID="$ak" \
|
|
||||||
AWS_SECRET_ACCESS_KEY="$sk" \
|
|
||||||
AWS_DEFAULT_REGION="us-east-1" \
|
|
||||||
aws --endpoint-url "$BASE_URL" --output json sqs "$@" 2>&1
|
|
||||||
}
|
|
||||||
|
|
||||||
admin_api() {
|
|
||||||
local method="$1" path="$2" body="${3:-}"
|
|
||||||
if [[ -n "$body" ]]; then
|
|
||||||
curl -s --max-time 20 -X "$method" \
|
|
||||||
-H "Authorization: Bearer $ADMIN_TOKEN" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d "$body" "${BASE_URL}${path}" 2>&1
|
|
||||||
else
|
|
||||||
curl -s --max-time 20 -X "$method" \
|
|
||||||
-H "Authorization: Bearer $ADMIN_TOKEN" "${BASE_URL}${path}" 2>&1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
qurl() { echo "${BASE_URL}/${1}/${2}"; }
|
|
||||||
|
|
||||||
wait_for_pod_ready() {
|
|
||||||
local max_wait=120 waited=0
|
|
||||||
echo " ⏳ Ожидание пода (max ${max_wait}s)..."
|
|
||||||
while [[ $waited -lt $max_wait ]]; do
|
|
||||||
local ready
|
|
||||||
ready=$(kubectl get pods -n "$NAMESPACE" -l "$POD_LABEL" \
|
|
||||||
-o jsonpath='{.items[0].status.conditions[?(@.type=="Ready")].status}' 2>/dev/null)
|
|
||||||
if [[ "$ready" == "True" ]]; then
|
|
||||||
echo " ✅ Под готов (${waited}s)"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
sleep 2
|
|
||||||
waited=$((waited + 2))
|
|
||||||
printf "." >&2
|
|
||||||
done
|
|
||||||
echo ""
|
|
||||||
echo " ⚠️ Под не готов после ${max_wait}s"
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
wait_for_healthy() {
|
|
||||||
local max_wait=120 waited=0
|
|
||||||
echo " ⏳ Ожидание /health (max ${max_wait}s)..."
|
|
||||||
while [[ $waited -lt $max_wait ]]; do
|
|
||||||
local status
|
|
||||||
status=$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 "${BASE_URL}/health" 2>/dev/null)
|
|
||||||
if [[ "$status" == "200" ]]; then
|
|
||||||
echo " ✅ Сервис отвечает (${waited}s)"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
sleep 2
|
|
||||||
waited=$((waited + 2))
|
|
||||||
printf "." >&2
|
|
||||||
done
|
|
||||||
echo ""
|
|
||||||
echo " ⚠️ Сервис не отвечает после ${max_wait}s"
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# get_pod_rss_mb — RSS памяти текущего пода в MB
|
|
||||||
get_pod_rss_mb() {
|
|
||||||
local pod
|
|
||||||
pod=$(kubectl get pods -n "$NAMESPACE" -l "$POD_LABEL" \
|
|
||||||
-o jsonpath='{.items[0].metadata.name}' 2>/dev/null)
|
|
||||||
# kubectl top может не работать — fallback на /proc
|
|
||||||
local rss_kb
|
|
||||||
rss_kb=$(kubectl exec -n "$NAMESPACE" "$pod" -- cat /proc/1/status 2>/dev/null \
|
|
||||||
| grep VmRSS | awk '{print $2}')
|
|
||||||
if [[ -n "$rss_kb" ]]; then
|
|
||||||
echo $((rss_kb / 1024))
|
|
||||||
else
|
|
||||||
echo "0"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
kill_pod() {
|
|
||||||
local pod
|
|
||||||
pod=$(kubectl get pods -n "$NAMESPACE" -l "$POD_LABEL" \
|
|
||||||
-o jsonpath='{.items[0].metadata.name}' 2>/dev/null)
|
|
||||||
echo " 🔪 Kill: $pod"
|
|
||||||
kubectl delete pod "$pod" -n "$NAMESPACE" --grace-period=0 --force 2>&1 | head -1
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup() { rm -rf "$TMPDIR_STRESS" 2>/dev/null; }
|
|
||||||
trap cleanup EXIT
|
|
||||||
|
|
||||||
STARTED_AT=$(date +%s)
|
|
||||||
|
|
||||||
echo "╔═══════════════════════════════════════════════════════════════════╗"
|
|
||||||
echo "║ shared-sqs STRESS TEST v2 — $(date '+%Y-%m-%d %H:%M:%S') ║"
|
|
||||||
echo "║ Endpoint: ${BASE_URL} ║"
|
|
||||||
echo "║ Workers: ${CONCURRENT_WORKERS} Msgs/worker: ${MESSAGES_PER_WORKER} Burst: ${BURST_SIZE} Tenants: ${TENANT_COUNT} ║"
|
|
||||||
echo "║ Queue-flood: ${QUEUE_FLOOD_COUNT} Batch-workers: ${BATCH_WORKERS} Mixed: ${MIXED_DURATION}s ║"
|
|
||||||
echo "╚═══════════════════════════════════════════════════════════════════╝"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════
|
|
||||||
# 1. ПОДГОТОВКА — тенанты и очереди
|
|
||||||
# ═══════════════════════════════════════════════════════════
|
|
||||||
echo "═══ 1. Подготовка — создаём $TENANT_COUNT тенантов ═══"
|
|
||||||
|
|
||||||
declare -a TENANT_AK TENANT_SK TENANT_ID
|
|
||||||
for i in $(seq 1 $TENANT_COUNT); do
|
|
||||||
R=$(admin_api POST "/admin/tenants" "{\"name\":\"stress-t${i}-${TS}\",\"max_queues\":100}")
|
|
||||||
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]:0:20}..."
|
|
||||||
if [[ -z "${TENANT_AK[$i]}" ]]; then
|
|
||||||
echo " ⛔ ОШИБКА создания тенанта ${i}: $R"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# Сохраняем в файлы — для subshell
|
|
||||||
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
|
|
||||||
|
|
||||||
T1_AK="${TENANT_AK[1]}"; T1_SK="${TENANT_SK[1]}"; T1_ID="${TENANT_ID[1]}"
|
|
||||||
|
|
||||||
# Очереди
|
|
||||||
STRESS_Q="stress-conc-${TS}"
|
|
||||||
for i in $(seq 1 $TENANT_COUNT); do
|
|
||||||
R=$(sqs "${TENANT_AK[$i]}" "${TENANT_SK[$i]}" create-queue --queue-name "$STRESS_Q")
|
|
||||||
done
|
|
||||||
STRESS_QURL=$(qurl "$T1_ID" "$STRESS_Q")
|
|
||||||
|
|
||||||
BURST_Q="stress-burst-${TS}"
|
|
||||||
R=$(sqs "$T1_AK" "$T1_SK" create-queue --queue-name "$BURST_Q")
|
|
||||||
BURST_QURL=$(qurl "$T1_ID" "$BURST_Q")
|
|
||||||
|
|
||||||
echo " Очередь conc: $STRESS_Q (для $TENANT_COUNT тенантов)"
|
|
||||||
echo " Очередь burst: $BURST_Q"
|
|
||||||
echo " Подготовка завершена за $(($(date +%s) - STARTED_AT))s"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════
|
|
||||||
# 2. КОНКУРЕНТНАЯ ОТПРАВКА — 50 × 20 = 1000 сообщений
|
|
||||||
# ═══════════════════════════════════════════════════════════
|
|
||||||
TOTAL_MSGS=$((CONCURRENT_WORKERS * MESSAGES_PER_WORKER))
|
|
||||||
echo "═══ 2. Конкурентная отправка: ${CONCURRENT_WORKERS} воркеров × ${MESSAGES_PER_WORKER} = ${TOTAL_MSGS} сообщений ═══"
|
|
||||||
SEC2_START=$(date +%s)
|
|
||||||
|
|
||||||
for w in $(seq 1 $CONCURRENT_WORKERS); do
|
|
||||||
(
|
|
||||||
ok=0; fail=0
|
|
||||||
for m in $(seq 1 $MESSAGES_PER_WORKER); do
|
|
||||||
R=$(sqs "$T1_AK" "$T1_SK" send-message \
|
|
||||||
--queue-url "$STRESS_QURL" \
|
|
||||||
--message-body "w${w}-m${m}-${TS}" 2>&1)
|
|
||||||
if echo "$R" | grep -q "MessageId"; then ok=$((ok + 1)); else fail=$((fail + 1)); fi
|
|
||||||
done
|
|
||||||
echo "${ok}:${fail}" > "${TMPDIR_STRESS}/send_w${w}.result"
|
|
||||||
# Прогресс: каждый 10-й воркер печатает
|
|
||||||
if (( w % 10 == 0 )); then printf "[W${w}:${ok}ok] " >&2; fi
|
|
||||||
) &
|
|
||||||
done
|
|
||||||
echo " ⏳ Отправка идёт... (прогресс каждые 10 воркеров)"
|
|
||||||
wait
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
SEND_OK=0; SEND_FAIL=0
|
|
||||||
for w in $(seq 1 $CONCURRENT_WORKERS); do
|
|
||||||
if [[ -f "${TMPDIR_STRESS}/send_w${w}.result" ]]; then
|
|
||||||
IFS=':' read -r ok fail < "${TMPDIR_STRESS}/send_w${w}.result"
|
|
||||||
SEND_OK=$((SEND_OK + ok)); SEND_FAIL=$((SEND_FAIL + fail))
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
SEC2_DUR=$(($(date +%s) - SEC2_START))
|
|
||||||
echo " Результат: ${SEND_OK} ok, ${SEND_FAIL} fail за ${SEC2_DUR}s (~$((SEND_OK / (SEC2_DUR + 1))) msg/s)"
|
|
||||||
check_eq "Все ${TOTAL_MSGS} сообщений отправлены" "$SEND_OK" "$TOTAL_MSGS"
|
|
||||||
|
|
||||||
sleep 2
|
|
||||||
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 " GetQueueAttributes: ~${APPROX} в очереди"
|
|
||||||
check_eq "Количество в очереди = $TOTAL_MSGS" "${APPROX:-0}" "$TOTAL_MSGS"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════
|
|
||||||
# 3. КОНКУРЕНТНОЕ ЧТЕНИЕ — гонка за 1000 сообщений
|
|
||||||
# ═══════════════════════════════════════════════════════════
|
|
||||||
echo "═══ 3. Конкурентное чтение: ${CONCURRENT_WORKERS} воркеров забирают ${TOTAL_MSGS} сообщений ═══"
|
|
||||||
SEC3_START=$(date +%s)
|
|
||||||
|
|
||||||
for w in $(seq 1 $CONCURRENT_WORKERS); do
|
|
||||||
(
|
|
||||||
received=0; deleted=0; empty_rounds=0
|
|
||||||
while [[ $empty_rounds -lt 8 ]]; do
|
|
||||||
R=$(sqs "$T1_AK" "$T1_SK" receive-message \
|
|
||||||
--queue-url "$STRESS_QURL" \
|
|
||||||
--max-number-of-messages 10 --wait-time-seconds 1 2>&1)
|
|
||||||
MSG_COUNT=$(echo "$R" | grep -c '"MessageId"' || true)
|
|
||||||
if [[ "$MSG_COUNT" -eq 0 ]]; then empty_rounds=$((empty_rounds + 1)); continue; fi
|
|
||||||
empty_rounds=0
|
|
||||||
received=$((received + MSG_COUNT))
|
|
||||||
HANDLES=$(echo "$R" | grep -oP '"ReceiptHandle"\s*:\s*"\K[^"]+')
|
|
||||||
while IFS= read -r handle; do
|
|
||||||
sqs "$T1_AK" "$T1_SK" delete-message \
|
|
||||||
--queue-url "$STRESS_QURL" --receipt-handle "$handle" >/dev/null 2>&1
|
|
||||||
deleted=$((deleted + 1))
|
|
||||||
done <<< "$HANDLES"
|
|
||||||
done
|
|
||||||
echo "${received}:${deleted}" > "${TMPDIR_STRESS}/recv_w${w}.result"
|
|
||||||
if (( w % 10 == 0 )); then printf "[R${w}:${received}r/${deleted}d] " >&2; fi
|
|
||||||
) &
|
|
||||||
done
|
|
||||||
echo " ⏳ Чтение идёт... (прогресс каждые 10 воркеров)"
|
|
||||||
wait
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
RECV_TOTAL=0; DEL_TOTAL=0
|
|
||||||
for w in $(seq 1 $CONCURRENT_WORKERS); do
|
|
||||||
if [[ -f "${TMPDIR_STRESS}/recv_w${w}.result" ]]; then
|
|
||||||
IFS=':' read -r recv del < "${TMPDIR_STRESS}/recv_w${w}.result"
|
|
||||||
RECV_TOTAL=$((RECV_TOTAL + recv)); DEL_TOTAL=$((DEL_TOTAL + del))
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
SEC3_DUR=$(($(date +%s) - SEC3_START))
|
|
||||||
echo " Результат: прочитано=${RECV_TOTAL}, удалено=${DEL_TOTAL} за ${SEC3_DUR}s"
|
|
||||||
check_ge "Прочитано >= $TOTAL_MSGS" "$RECV_TOTAL" "$TOTAL_MSGS"
|
|
||||||
|
|
||||||
sleep 2
|
|
||||||
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"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════
|
|
||||||
# 4. MULTI-TENANT ИЗОЛЯЦИЯ — 5 тенантов × 50 msg одновременно
|
|
||||||
# ═══════════════════════════════════════════════════════════
|
|
||||||
MSGS_PER_TENANT=30
|
|
||||||
echo "═══ 4. Multi-tenant изоляция: ${TENANT_COUNT} тенантов × ${MSGS_PER_TENANT} msg ═══"
|
|
||||||
SEC4_START=$(date +%s)
|
|
||||||
|
|
||||||
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 "$tak" "$tsk" send-message --queue-url "$my_qurl" \
|
|
||||||
--message-body "tenant-${i}-iso-${m}" 2>&1)
|
|
||||||
if echo "$R" | grep -q "MessageId"; then ok=$((ok + 1)); fi
|
|
||||||
done
|
|
||||||
echo "$ok" > "${TMPDIR_STRESS}/tenant_send_${i}.result"
|
|
||||||
printf "[T${i}:${ok}sent] " >&2
|
|
||||||
) &
|
|
||||||
done
|
|
||||||
echo " ⏳ Тенанты отправляют..."
|
|
||||||
wait
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Чтение и проверка изоляции
|
|
||||||
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 "$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)
|
|
||||||
if [[ "$MSG_COUNT" -eq 0 ]]; then empty_rounds=$((empty_rounds + 1)); continue; fi
|
|
||||||
empty_rounds=0
|
|
||||||
while IFS= read -r body; do
|
|
||||||
if echo "$body" | grep -q "tenant-${i}-"; then own=$((own + 1)); else foreign=$((foreign + 1)); fi
|
|
||||||
done < <(echo "$R" | grep -oP '"Body"\s*:\s*"\K[^"]+')
|
|
||||||
HANDLES=$(echo "$R" | grep -oP '"ReceiptHandle"\s*:\s*"\K[^"]+')
|
|
||||||
while IFS= read -r handle; do
|
|
||||||
sqs "$tak" "$tsk" delete-message --queue-url "$my_qurl" --receipt-handle "$handle" >/dev/null 2>&1
|
|
||||||
done <<< "$HANDLES"
|
|
||||||
done
|
|
||||||
echo "${own}:${foreign}" > "${TMPDIR_STRESS}/tenant_recv_${i}.result"
|
|
||||||
printf "[T${i}:own=${own},foreign=${foreign}] " >&2
|
|
||||||
) &
|
|
||||||
done
|
|
||||||
echo " ⏳ Тенанты читают..."
|
|
||||||
wait
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
ALL_FOREIGN=0; ALL_OWN=0
|
|
||||||
for i in $(seq 1 $TENANT_COUNT); do
|
|
||||||
if [[ -f "${TMPDIR_STRESS}/tenant_recv_${i}.result" ]]; then
|
|
||||||
IFS=':' read -r own foreign < "${TMPDIR_STRESS}/tenant_recv_${i}.result"
|
|
||||||
echo " Тенант ${i}: своих=${own}, чужих=${foreign}"
|
|
||||||
ALL_OWN=$((ALL_OWN + own)); ALL_FOREIGN=$((ALL_FOREIGN + foreign))
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
SEC4_DUR=$(($(date +%s) - SEC4_START))
|
|
||||||
echo " Итого: своих=$ALL_OWN, чужих=$ALL_FOREIGN за ${SEC4_DUR}s"
|
|
||||||
check_eq "Чужих сообщений: 0" "$ALL_FOREIGN" "0"
|
|
||||||
check_ge "Свои сообщения получены" "$ALL_OWN" "$((TENANT_COUNT * MSGS_PER_TENANT))"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════
|
|
||||||
# 5. BURST — 100 сообщений одновременно
|
|
||||||
# ═══════════════════════════════════════════════════════════
|
|
||||||
echo "═══ 5. Burst: ${BURST_SIZE} сообщений одновременно ═══"
|
|
||||||
SEC5_START=$(date +%s)
|
|
||||||
|
|
||||||
for b in $(seq 1 $BURST_SIZE); do
|
|
||||||
(
|
|
||||||
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"
|
|
||||||
) &
|
|
||||||
# Прогресс пачками по 25
|
|
||||||
if (( b % 25 == 0 )); then printf "[${b}/${BURST_SIZE}] " >&2; fi
|
|
||||||
done
|
|
||||||
echo " ⏳ Burst отправка..."
|
|
||||||
wait
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
BURST_OK=0
|
|
||||||
for b in $(seq 1 $BURST_SIZE); do
|
|
||||||
[[ -f "${TMPDIR_STRESS}/burst_${b}.result" ]] && BURST_OK=$((BURST_OK + $(cat "${TMPDIR_STRESS}/burst_${b}.result")))
|
|
||||||
done
|
|
||||||
SEC5_DUR=$(($(date +%s) - SEC5_START))
|
|
||||||
echo " Результат: ${BURST_OK}/${BURST_SIZE} за ${SEC5_DUR}s"
|
|
||||||
check_ge "Burst >= 90%" "$BURST_OK" "$((BURST_SIZE * 90 / 100))"
|
|
||||||
|
|
||||||
sleep 3
|
|
||||||
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}"
|
|
||||||
check_eq "Burst все в очереди" "${BURST_APPROX:-0}" "$BURST_OK"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════
|
|
||||||
# 6. LONG-POLLING + параллельные producer'ы
|
|
||||||
# ═══════════════════════════════════════════════════════════
|
|
||||||
echo "═══ 6. Long-polling: consumer WaitTime=10s + 5 producers ═══"
|
|
||||||
SEC6_START=$(date +%s)
|
|
||||||
|
|
||||||
LONGPOLL_Q="stress-longpoll-${TS}"
|
|
||||||
R=$(sqs "$T1_AK" "$T1_SK" create-queue --queue-name "$LONGPOLL_Q" \
|
|
||||||
--attributes '{"ReceiveMessageWaitTimeSeconds":"10"}')
|
|
||||||
LONGPOLL_QURL=$(qurl "$T1_ID" "$LONGPOLL_Q")
|
|
||||||
|
|
||||||
# Consumer: читает с long-poll, считает сколько получит за 30s
|
|
||||||
(
|
|
||||||
end=$((SECONDS + 20))
|
|
||||||
total_recv=0
|
|
||||||
while [[ $SECONDS -lt $end ]]; do
|
|
||||||
R=$(sqs "$T1_AK" "$T1_SK" receive-message --queue-url "$LONGPOLL_QURL" \
|
|
||||||
--max-number-of-messages 10 --wait-time-seconds 10 2>&1)
|
|
||||||
cnt=$(echo "$R" | grep -c '"MessageId"' || true)
|
|
||||||
total_recv=$((total_recv + cnt))
|
|
||||||
if (( total_recv % 10 == 0 && total_recv > 0 )); then printf "[LP-recv:${total_recv}] " >&2; fi
|
|
||||||
done
|
|
||||||
echo "$total_recv" > "${TMPDIR_STRESS}/longpoll_recv.result"
|
|
||||||
) &
|
|
||||||
|
|
||||||
# Producers: начинают через 3s, каждый отправляет 20 msg с паузами
|
|
||||||
sleep 3
|
|
||||||
for p in $(seq 1 5); do
|
|
||||||
(
|
|
||||||
ok=0
|
|
||||||
for m in $(seq 1 20); do
|
|
||||||
R=$(sqs "$T1_AK" "$T1_SK" send-message --queue-url "$LONGPOLL_QURL" \
|
|
||||||
--message-body "longpoll-p${p}-m${m}" 2>&1)
|
|
||||||
if echo "$R" | grep -q "MessageId"; then ok=$((ok + 1)); fi
|
|
||||||
sleep 0.2
|
|
||||||
done
|
|
||||||
echo "$ok" > "${TMPDIR_STRESS}/longpoll_prod_${p}.result"
|
|
||||||
printf "[LP-prod${p}:${ok}] " >&2
|
|
||||||
) &
|
|
||||||
done
|
|
||||||
echo " ⏳ Long-poll: consumer ждёт, producers отправляют (20s)..."
|
|
||||||
wait
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
LP_SENT=0
|
|
||||||
for p in $(seq 1 5); do
|
|
||||||
[[ -f "${TMPDIR_STRESS}/longpoll_prod_${p}.result" ]] && LP_SENT=$((LP_SENT + $(cat "${TMPDIR_STRESS}/longpoll_prod_${p}.result")))
|
|
||||||
done
|
|
||||||
LP_RECV=$(cat "${TMPDIR_STRESS}/longpoll_recv.result" 2>/dev/null || echo 0)
|
|
||||||
SEC6_DUR=$(($(date +%s) - SEC6_START))
|
|
||||||
echo " Producers отправили: $LP_SENT, consumer получил: $LP_RECV за ${SEC6_DUR}s"
|
|
||||||
check_ge "Long-poll: получено >= 50% отправленных" "$LP_RECV" "$((LP_SENT / 2))"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════
|
|
||||||
# 7. ДВОЙНОЕ УДАЛЕНИЕ — race на receipt handle
|
|
||||||
# ═══════════════════════════════════════════════════════════
|
|
||||||
echo "═══ 7. Двойное удаление: 2 воркера гонятся за одно сообщение ═══"
|
|
||||||
|
|
||||||
RACE_Q="stress-race-${TS}"
|
|
||||||
R=$(sqs "$T1_AK" "$T1_SK" create-queue --queue-name "$RACE_Q")
|
|
||||||
RACE_QURL=$(qurl "$T1_ID" "$RACE_Q")
|
|
||||||
|
|
||||||
# Отправляем 20 сообщений
|
|
||||||
for m in $(seq 1 20); do
|
|
||||||
sqs "$T1_AK" "$T1_SK" send-message --queue-url "$RACE_QURL" \
|
|
||||||
--message-body "race-${m}" >/dev/null 2>&1
|
|
||||||
done
|
|
||||||
echo " Отправлено 20 сообщений"
|
|
||||||
|
|
||||||
# VisibilityTimeout=0 — чтобы оба воркера получали одни сообщения
|
|
||||||
RACE_DOUBLE_DEL=0
|
|
||||||
RACE_OK_DEL=0
|
|
||||||
for round in $(seq 1 10); do
|
|
||||||
# Два воркера одновременно получают
|
|
||||||
R1=$(sqs "$T1_AK" "$T1_SK" receive-message --queue-url "$RACE_QURL" \
|
|
||||||
--max-number-of-messages 1 --visibility-timeout 30 2>&1)
|
|
||||||
HANDLE=$(echo "$R1" | grep -oP '"ReceiptHandle"\s*:\s*"\K[^"]+' | head -1)
|
|
||||||
if [[ -z "$HANDLE" ]]; then continue; fi
|
|
||||||
|
|
||||||
# Два параллельных delete
|
|
||||||
D1=$(sqs "$T1_AK" "$T1_SK" delete-message --queue-url "$RACE_QURL" --receipt-handle "$HANDLE" 2>&1) &
|
|
||||||
PID1=$!
|
|
||||||
D2=$(sqs "$T1_AK" "$T1_SK" delete-message --queue-url "$RACE_QURL" --receipt-handle "$HANDLE" 2>&1) &
|
|
||||||
PID2=$!
|
|
||||||
wait $PID1 $PID2
|
|
||||||
RACE_OK_DEL=$((RACE_OK_DEL + 1))
|
|
||||||
printf "[race-${round}] " >&2
|
|
||||||
done
|
|
||||||
echo ""
|
|
||||||
echo " Раундов race: ${RACE_OK_DEL}/10"
|
|
||||||
# Главное: сервер НЕ упал
|
|
||||||
R=$(sqs "$T1_AK" "$T1_SK" get-queue-attributes --queue-url "$RACE_QURL" \
|
|
||||||
--attribute-names ApproximateNumberOfMessages 2>&1)
|
|
||||||
check "Сервер жив после double-delete" "$R" "ApproximateNumberOfMessages"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════
|
|
||||||
# 8. BATCH-ОПЕРАЦИИ конкурентно
|
|
||||||
# ═══════════════════════════════════════════════════════════
|
|
||||||
echo "═══ 8. Batch-операции: ${BATCH_WORKERS} воркеров SendMessageBatch + DeleteMessageBatch ═══"
|
|
||||||
SEC8_START=$(date +%s)
|
|
||||||
|
|
||||||
BATCH_Q="stress-batch-${TS}"
|
|
||||||
R=$(sqs "$T1_AK" "$T1_SK" create-queue --queue-name "$BATCH_Q")
|
|
||||||
BATCH_QURL=$(qurl "$T1_ID" "$BATCH_Q")
|
|
||||||
|
|
||||||
# Параллельные SendMessageBatch (10 msg per batch × 10 workers = 100 msg)
|
|
||||||
for w in $(seq 1 $BATCH_WORKERS); do
|
|
||||||
(
|
|
||||||
# Формируем batch из 10 записей
|
|
||||||
ENTRIES=""
|
|
||||||
for e in $(seq 1 10); do
|
|
||||||
[[ -n "$ENTRIES" ]] && ENTRIES="${ENTRIES},"
|
|
||||||
ENTRIES="${ENTRIES}{\"Id\":\"${w}-${e}\",\"MessageBody\":\"batch-w${w}-e${e}\"}"
|
|
||||||
done
|
|
||||||
R=$(sqs "$T1_AK" "$T1_SK" send-message-batch --queue-url "$BATCH_QURL" \
|
|
||||||
--entries "[${ENTRIES}]" 2>&1)
|
|
||||||
ok=$(echo "$R" | grep -c '"MessageId"' || true)
|
|
||||||
echo "$ok" > "${TMPDIR_STRESS}/batch_send_${w}.result"
|
|
||||||
printf "[B-send${w}:${ok}] " >&2
|
|
||||||
) &
|
|
||||||
done
|
|
||||||
echo " ⏳ SendMessageBatch..."
|
|
||||||
wait
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
BATCH_SENT=0
|
|
||||||
for w in $(seq 1 $BATCH_WORKERS); do
|
|
||||||
[[ -f "${TMPDIR_STRESS}/batch_send_${w}.result" ]] && BATCH_SENT=$((BATCH_SENT + $(cat "${TMPDIR_STRESS}/batch_send_${w}.result")))
|
|
||||||
done
|
|
||||||
echo " Batch отправлено: ${BATCH_SENT}"
|
|
||||||
check_ge "Batch: отправлено >= 80" "$BATCH_SENT" "80"
|
|
||||||
|
|
||||||
# Параллельные ReceiveMessage + DeleteMessageBatch
|
|
||||||
BATCH_DELETED=0
|
|
||||||
for w in $(seq 1 5); do
|
|
||||||
(
|
|
||||||
del=0; empty=0
|
|
||||||
while [[ $empty -lt 3 ]]; do
|
|
||||||
R=$(sqs "$T1_AK" "$T1_SK" receive-message --queue-url "$BATCH_QURL" \
|
|
||||||
--max-number-of-messages 10 --wait-time-seconds 1 2>&1)
|
|
||||||
HANDLES=$(echo "$R" | grep -oP '"ReceiptHandle"\s*:\s*"\K[^"]+')
|
|
||||||
if [[ -z "$HANDLES" ]]; then empty=$((empty + 1)); continue; fi
|
|
||||||
empty=0
|
|
||||||
# Формируем batch delete
|
|
||||||
ENTRIES=""
|
|
||||||
idx=0
|
|
||||||
while IFS= read -r handle; do
|
|
||||||
[[ -z "$handle" ]] && continue
|
|
||||||
[[ -n "$ENTRIES" ]] && ENTRIES="${ENTRIES},"
|
|
||||||
ENTRIES="${ENTRIES}{\"Id\":\"${idx}\",\"ReceiptHandle\":\"${handle}\"}"
|
|
||||||
idx=$((idx + 1))
|
|
||||||
done <<< "$HANDLES"
|
|
||||||
if [[ -n "$ENTRIES" ]]; then
|
|
||||||
DR=$(sqs "$T1_AK" "$T1_SK" delete-message-batch --queue-url "$BATCH_QURL" \
|
|
||||||
--entries "[${ENTRIES}]" 2>&1)
|
|
||||||
ok_del=$(echo "$DR" | grep -c '"Id"' || true)
|
|
||||||
del=$((del + ok_del))
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
echo "$del" > "${TMPDIR_STRESS}/batch_del_${w}.result"
|
|
||||||
printf "[B-del${w}:${del}] " >&2
|
|
||||||
) &
|
|
||||||
done
|
|
||||||
echo " ⏳ DeleteMessageBatch..."
|
|
||||||
wait
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
BATCH_DEL_TOTAL=0
|
|
||||||
for w in $(seq 1 5); do
|
|
||||||
[[ -f "${TMPDIR_STRESS}/batch_del_${w}.result" ]] && BATCH_DEL_TOTAL=$((BATCH_DEL_TOTAL + $(cat "${TMPDIR_STRESS}/batch_del_${w}.result")))
|
|
||||||
done
|
|
||||||
SEC8_DUR=$(($(date +%s) - SEC8_START))
|
|
||||||
echo " Batch удалено: $BATCH_DEL_TOTAL за ${SEC8_DUR}s"
|
|
||||||
check_ge "Batch: удалено >= 80%" "$BATCH_DEL_TOTAL" "$((BATCH_SENT * 80 / 100))"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════
|
|
||||||
# 9. МНОГО ОЧЕРЕДЕЙ — 30 очередей на тенанта
|
|
||||||
# ═══════════════════════════════════════════════════════════
|
|
||||||
echo "═══ 9. Queue-flood: ${QUEUE_FLOOD_COUNT} очередей, send+receive в каждую ═══"
|
|
||||||
SEC9_START=$(date +%s)
|
|
||||||
|
|
||||||
# Создаём 30 очередей параллельно
|
|
||||||
for q in $(seq 1 $QUEUE_FLOOD_COUNT); do
|
|
||||||
(
|
|
||||||
qname="stress-flood-${q}-${TS}"
|
|
||||||
my_qurl=$(qurl "$T1_ID" "$qname")
|
|
||||||
# Создаём
|
|
||||||
R=$(sqs "$T1_AK" "$T1_SK" create-queue --queue-name "$qname" 2>&1)
|
|
||||||
if ! echo "$R" | grep -q "QueueUrl"; then echo "0" > "${TMPDIR_STRESS}/flood_${q}.result"; exit 0; fi
|
|
||||||
# Отправляем 5 msg
|
|
||||||
for m in $(seq 1 5); do
|
|
||||||
sqs "$T1_AK" "$T1_SK" send-message --queue-url "$my_qurl" \
|
|
||||||
--message-body "flood-q${q}-m${m}" >/dev/null 2>&1
|
|
||||||
done
|
|
||||||
# Читаем 1
|
|
||||||
R=$(sqs "$T1_AK" "$T1_SK" receive-message --queue-url "$my_qurl" \
|
|
||||||
--max-number-of-messages 1 --wait-time-seconds 1 2>&1)
|
|
||||||
if echo "$R" | grep -q "MessageId"; then echo "1" > "${TMPDIR_STRESS}/flood_${q}.result"
|
|
||||||
else echo "0" > "${TMPDIR_STRESS}/flood_${q}.result"; fi
|
|
||||||
if (( q % 10 == 0 )); then printf "[Q${q}] " >&2; fi
|
|
||||||
) &
|
|
||||||
done
|
|
||||||
echo " ⏳ Создание и тест ${QUEUE_FLOOD_COUNT} очередей..."
|
|
||||||
wait
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
FLOOD_OK=0
|
|
||||||
for q in $(seq 1 $QUEUE_FLOOD_COUNT); do
|
|
||||||
[[ -f "${TMPDIR_STRESS}/flood_${q}.result" ]] && FLOOD_OK=$((FLOOD_OK + $(cat "${TMPDIR_STRESS}/flood_${q}.result")))
|
|
||||||
done
|
|
||||||
SEC9_DUR=$(($(date +%s) - SEC9_START))
|
|
||||||
echo " Успешных: ${FLOOD_OK}/${QUEUE_FLOOD_COUNT} за ${SEC9_DUR}s"
|
|
||||||
check_ge "Queue-flood: >= 90% очередей работают" "$FLOOD_OK" "$((QUEUE_FLOOD_COUNT * 90 / 100))"
|
|
||||||
|
|
||||||
# Проверяем ListQueues — должно быть >= 30 flood-очередей
|
|
||||||
R=$(sqs "$T1_AK" "$T1_SK" list-queues --queue-name-prefix "stress-flood" 2>&1)
|
|
||||||
LISTED=$(echo "$R" | grep -c "stress-flood" || true)
|
|
||||||
echo " ListQueues видит: $LISTED очередей"
|
|
||||||
check_ge "ListQueues >= ${QUEUE_FLOOD_COUNT}" "$LISTED" "$QUEUE_FLOOD_COUNT"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════
|
|
||||||
# 10. MEMORY CHECK — RSS до нагрузки
|
|
||||||
# ═══════════════════════════════════════════════════════════
|
|
||||||
echo "═══ 10. Memory check — запоминаем RSS ═══"
|
|
||||||
RSS_BEFORE=$(get_pod_rss_mb)
|
|
||||||
echo " RSS до тяжёлой фазы: ${RSS_BEFORE} MB"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════
|
|
||||||
# 11. KILL POD + восстановление из Redis
|
|
||||||
# ═══════════════════════════════════════════════════════════
|
|
||||||
echo "═══ 11. Kill pod → восстановление из Redis ═══"
|
|
||||||
|
|
||||||
# Перед kill — отправляем в burst-очередь ещё 20 msg
|
|
||||||
EXTRA=20
|
|
||||||
for m in $(seq 1 $EXTRA); do
|
|
||||||
sqs "$T1_AK" "$T1_SK" send-message --queue-url "$BURST_QURL" \
|
|
||||||
--message-body "pre-kill-${m}-${TS}" >/dev/null 2>&1
|
|
||||||
if (( m % 5 == 0 )); then printf "[pre-kill:${m}] " >&2; fi
|
|
||||||
done
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
R=$(sqs "$T1_AK" "$T1_SK" get-queue-attributes --queue-url "$BURST_QURL" \
|
|
||||||
--attribute-names ApproximateNumberOfMessages 2>&1)
|
|
||||||
BEFORE_KILL=$(echo "$R" | grep -oP '"ApproximateNumberOfMessages"\s*:\s*"\K[0-9]+')
|
|
||||||
echo " Сообщений в burst до kill: ${BEFORE_KILL:-?}"
|
|
||||||
|
|
||||||
kill_pod
|
|
||||||
echo " Ждём рестарт..."
|
|
||||||
wait_for_pod_ready
|
|
||||||
wait_for_healthy
|
|
||||||
|
|
||||||
sleep 3
|
|
||||||
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}"
|
|
||||||
check_eq "Данные восстановлены из Redis" "${AFTER_KILL:-0}" "${BEFORE_KILL}"
|
|
||||||
|
|
||||||
R=$(admin_api GET "/admin/tenants")
|
|
||||||
TC=$(echo "$R" | grep -c "stress-t1-${TS}" || true)
|
|
||||||
check_ge "Тенанты восстановлены" "$TC" "1"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════
|
|
||||||
# 12. REDIS DISCONNECT / RECONNECT
|
|
||||||
# ═══════════════════════════════════════════════════════════
|
|
||||||
echo "═══ 12. Redis disconnect simulation ═══"
|
|
||||||
|
|
||||||
cat <<'NETPOL' | kubectl apply -n "$NAMESPACE" -f - 2>&1 | head -1
|
|
||||||
apiVersion: networking.k8s.io/v1
|
|
||||||
kind: NetworkPolicy
|
|
||||||
metadata:
|
|
||||||
name: stress-test-block-redis
|
|
||||||
spec:
|
|
||||||
podSelector:
|
|
||||||
matchLabels:
|
|
||||||
app.kubernetes.io/name: shared-sqs
|
|
||||||
policyTypes:
|
|
||||||
- Egress
|
|
||||||
egress:
|
|
||||||
- to:
|
|
||||||
- ipBlock:
|
|
||||||
cidr: 0.0.0.0/0
|
|
||||||
except:
|
|
||||||
- 10.0.0.0/8
|
|
||||||
ports:
|
|
||||||
- protocol: TCP
|
|
||||||
port: 443
|
|
||||||
- protocol: TCP
|
|
||||||
port: 80
|
|
||||||
- to:
|
|
||||||
- namespaceSelector: {}
|
|
||||||
ports:
|
|
||||||
- protocol: TCP
|
|
||||||
port: 53
|
|
||||||
- protocol: UDP
|
|
||||||
port: 53
|
|
||||||
NETPOL
|
|
||||||
echo " NetworkPolicy applied — Redis заблокирован"
|
|
||||||
sleep 5
|
|
||||||
|
|
||||||
R_CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 \
|
|
||||||
-H "Authorization: Bearer $ADMIN_TOKEN" "${BASE_URL}/admin/tenants" 2>/dev/null)
|
|
||||||
echo " HTTP при потере Redis: $R_CODE"
|
|
||||||
TOTAL=$((TOTAL + 1))
|
|
||||||
if [[ "$R_CODE" =~ ^[2345][0-9][0-9]$ ]]; then
|
|
||||||
PASS=$((PASS + 1)); echo " ✅ Сервис отвечает (HTTP $R_CODE)"
|
|
||||||
else
|
|
||||||
FAIL=$((FAIL + 1)); echo " ❌ Сервис не отвечает (HTTP $R_CODE)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
kubectl delete networkpolicy stress-test-block-redis -n "$NAMESPACE" 2>&1 | head -1
|
|
||||||
echo " NetworkPolicy удалена — Redis доступен"
|
|
||||||
sleep 5
|
|
||||||
|
|
||||||
R=$(sqs "$T1_AK" "$T1_SK" send-message --queue-url "$BURST_QURL" \
|
|
||||||
--message-body "after-redis-recovery-${TS}" 2>&1)
|
|
||||||
check "Сервис работает после Redis reconnect" "$R" "MessageId"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════
|
|
||||||
# 13. СМЕШАННАЯ НАГРУЗКА — send + receive + delete + getattr (20s)
|
|
||||||
# ═══════════════════════════════════════════════════════════
|
|
||||||
echo "═══ 13. Смешанная нагрузка: 10 senders + 10 receivers + 3 getattr × ${MIXED_DURATION}s ═══"
|
|
||||||
SEC13_START=$(date +%s)
|
|
||||||
|
|
||||||
MIXED_Q="stress-mixed-${TS}"
|
|
||||||
R=$(sqs "$T1_AK" "$T1_SK" create-queue --queue-name "$MIXED_Q")
|
|
||||||
MIXED_QURL=$(qurl "$T1_ID" "$MIXED_Q")
|
|
||||||
|
|
||||||
# Предзагрузка 100 msg
|
|
||||||
for m in $(seq 1 30); do
|
|
||||||
sqs "$T1_AK" "$T1_SK" send-message --queue-url "$MIXED_QURL" \
|
|
||||||
--message-body "mixed-init-${m}" >/dev/null 2>&1
|
|
||||||
if (( m % 20 == 0 )); then printf "[init:${m}] " >&2; fi
|
|
||||||
done
|
|
||||||
echo ""
|
|
||||||
echo " Начальная загрузка: 30 msg"
|
|
||||||
|
|
||||||
# 10 Отправителей
|
|
||||||
for w in $(seq 1 5); do
|
|
||||||
(
|
|
||||||
end=$((SECONDS + MIXED_DURATION)); ok=0
|
|
||||||
while [[ $SECONDS -lt $end ]]; do
|
|
||||||
R=$(sqs "$T1_AK" "$T1_SK" send-message --queue-url "$MIXED_QURL" \
|
|
||||||
--message-body "mixed-s${w}-$(date +%s%N)" 2>&1)
|
|
||||||
if echo "$R" | grep -q "MessageId"; then ok=$((ok + 1)); fi
|
|
||||||
done
|
|
||||||
echo "$ok" > "${TMPDIR_STRESS}/mixed_send_${w}.result"
|
|
||||||
) &
|
|
||||||
done
|
|
||||||
|
|
||||||
# 10 Читателей-удалителей
|
|
||||||
for w in $(seq 1 5); do
|
|
||||||
(
|
|
||||||
end=$((SECONDS + MIXED_DURATION)); recv=0
|
|
||||||
while [[ $SECONDS -lt $end ]]; do
|
|
||||||
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 "$T1_AK" "$T1_SK" delete-message --queue-url "$MIXED_QURL" \
|
|
||||||
--receipt-handle "$handle" >/dev/null 2>&1
|
|
||||||
recv=$((recv + 1))
|
|
||||||
done <<< "$HANDLES"
|
|
||||||
done
|
|
||||||
echo "$recv" > "${TMPDIR_STRESS}/mixed_recv_${w}.result"
|
|
||||||
) &
|
|
||||||
done
|
|
||||||
|
|
||||||
# 3 GetQueueAttributes
|
|
||||||
for w in $(seq 1 2); do
|
|
||||||
(
|
|
||||||
end=$((SECONDS + MIXED_DURATION)); ok=0; fail=0
|
|
||||||
while [[ $SECONDS -lt $end ]]; do
|
|
||||||
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)); else fail=$((fail + 1)); fi
|
|
||||||
sleep 0.5
|
|
||||||
done
|
|
||||||
echo "${ok}:${fail}" > "${TMPDIR_STRESS}/mixed_attr_${w}.result"
|
|
||||||
) &
|
|
||||||
done
|
|
||||||
|
|
||||||
# Прогресс каждые 5 сек
|
|
||||||
(
|
|
||||||
elapsed=0
|
|
||||||
while [[ $elapsed -lt $MIXED_DURATION ]]; do
|
|
||||||
sleep 5
|
|
||||||
elapsed=$((elapsed + 5))
|
|
||||||
printf "[mixed: ${elapsed}/${MIXED_DURATION}s] " >&2
|
|
||||||
done
|
|
||||||
) &
|
|
||||||
PROGRESS_PID=$!
|
|
||||||
|
|
||||||
echo " ⏳ Смешанная нагрузка ${MIXED_DURATION}s..."
|
|
||||||
wait
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
MIXED_SENT=0; MIXED_RECV=0; ATTR_OK=0; ATTR_FAIL=0
|
|
||||||
for w in $(seq 1 5); do
|
|
||||||
[[ -f "${TMPDIR_STRESS}/mixed_send_${w}.result" ]] && MIXED_SENT=$((MIXED_SENT + $(cat "${TMPDIR_STRESS}/mixed_send_${w}.result")))
|
|
||||||
[[ -f "${TMPDIR_STRESS}/mixed_recv_${w}.result" ]] && MIXED_RECV=$((MIXED_RECV + $(cat "${TMPDIR_STRESS}/mixed_recv_${w}.result")))
|
|
||||||
done
|
|
||||||
for w in $(seq 1 2); do
|
|
||||||
if [[ -f "${TMPDIR_STRESS}/mixed_attr_${w}.result" ]]; then
|
|
||||||
IFS=':' read -r ok fail < "${TMPDIR_STRESS}/mixed_attr_${w}.result"
|
|
||||||
ATTR_OK=$((ATTR_OK + ok)); ATTR_FAIL=$((ATTR_FAIL + fail))
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
SEC13_DUR=$(($(date +%s) - SEC13_START))
|
|
||||||
echo " Отправлено: $MIXED_SENT, прочитано+удалено: $MIXED_RECV | Attr: ok=$ATTR_OK fail=$ATTR_FAIL | ${SEC13_DUR}s"
|
|
||||||
check_ge "Mixed: отправлено > 0" "$MIXED_SENT" "1"
|
|
||||||
check_ge "Mixed: прочитано > 0" "$MIXED_RECV" "1"
|
|
||||||
check_eq "Mixed: GetQueueAttributes 0 сбоев" "$ATTR_FAIL" "0"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════
|
|
||||||
# 14. MEMORY CHECK — после нагрузки
|
|
||||||
# ═══════════════════════════════════════════════════════════
|
|
||||||
echo "═══ 14. Memory check — RSS после нагрузки ═══"
|
|
||||||
RSS_AFTER=$(get_pod_rss_mb)
|
|
||||||
echo " RSS до: ${RSS_BEFORE} MB, после: ${RSS_AFTER} MB"
|
|
||||||
if [[ "$RSS_BEFORE" -gt 0 && "$RSS_AFTER" -gt 0 ]]; then
|
|
||||||
RSS_GROWTH=$((RSS_AFTER - RSS_BEFORE))
|
|
||||||
echo " Рост: ${RSS_GROWTH} MB"
|
|
||||||
# Допускаем рост до 100 MB — выше уже подозрительно
|
|
||||||
check_le "Рост RSS <= 100MB" "$RSS_GROWTH" "100"
|
|
||||||
else
|
|
||||||
echo " ⚠️ Не удалось измерить RSS (возможно /proc недоступен)"
|
|
||||||
TOTAL=$((TOTAL + 1)); PASS=$((PASS + 1))
|
|
||||||
echo " ✅ Memory check пропущен (нет доступа к /proc)"
|
|
||||||
fi
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════
|
|
||||||
# 15. MULTI-KILL: kill → up → kill → up
|
|
||||||
# ═══════════════════════════════════════════════════════════
|
|
||||||
echo "═══ 15. Multi-kill: 3 рестарта подряд ═══"
|
|
||||||
SEC15_START=$(date +%s)
|
|
||||||
|
|
||||||
# Отправляем маркерное сообщение
|
|
||||||
MARKER_Q="stress-marker-${TS}"
|
|
||||||
R=$(sqs "$T1_AK" "$T1_SK" create-queue --queue-name "$MARKER_Q")
|
|
||||||
MARKER_QURL=$(qurl "$T1_ID" "$MARKER_Q")
|
|
||||||
sqs "$T1_AK" "$T1_SK" send-message --queue-url "$MARKER_QURL" \
|
|
||||||
--message-body "marker-survive-3-kills-${TS}" >/dev/null 2>&1
|
|
||||||
|
|
||||||
for k in 1 2 3; do
|
|
||||||
echo " --- Kill #${k} ---"
|
|
||||||
kill_pod
|
|
||||||
wait_for_pod_ready
|
|
||||||
wait_for_healthy
|
|
||||||
echo " Kill #${k}: под восстановлен ✓"
|
|
||||||
done
|
|
||||||
|
|
||||||
# Маркер выжил?
|
|
||||||
R=$(sqs "$T1_AK" "$T1_SK" receive-message --queue-url "$MARKER_QURL" \
|
|
||||||
--max-number-of-messages 1 --wait-time-seconds 2 2>&1)
|
|
||||||
SEC15_DUR=$(($(date +%s) - SEC15_START))
|
|
||||||
echo " 3 рестарта за ${SEC15_DUR}s"
|
|
||||||
check "Маркер выжил 3 рестарта" "$R" "marker-survive-3-kills"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════
|
|
||||||
# 16. CLEANUP
|
|
||||||
# ═══════════════════════════════════════════════════════════
|
|
||||||
echo "═══ 16. Cleanup ═══"
|
|
||||||
echo " Удаляю очереди..."
|
|
||||||
|
|
||||||
# Основные очереди
|
|
||||||
for i in $(seq 1 $TENANT_COUNT); do
|
|
||||||
tak=$(cat "${TMPDIR_STRESS}/tak_${i}" 2>/dev/null)
|
|
||||||
tsk=$(cat "${TMPDIR_STRESS}/tsk_${i}" 2>/dev/null)
|
|
||||||
tid=$(cat "${TMPDIR_STRESS}/tid_${i}" 2>/dev/null)
|
|
||||||
[[ -n "$tak" ]] && sqs "$tak" "$tsk" delete-queue --queue-url "${BASE_URL}/${tid}/${STRESS_Q}" >/dev/null 2>&1
|
|
||||||
done
|
|
||||||
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
|
|
||||||
sqs "$T1_AK" "$T1_SK" delete-queue --queue-url "$LONGPOLL_QURL" >/dev/null 2>&1
|
|
||||||
sqs "$T1_AK" "$T1_SK" delete-queue --queue-url "$RACE_QURL" >/dev/null 2>&1
|
|
||||||
sqs "$T1_AK" "$T1_SK" delete-queue --queue-url "$BATCH_QURL" >/dev/null 2>&1
|
|
||||||
sqs "$T1_AK" "$T1_SK" delete-queue --queue-url "$MARKER_QURL" >/dev/null 2>&1
|
|
||||||
|
|
||||||
# Flood-очереди
|
|
||||||
for q in $(seq 1 $QUEUE_FLOOD_COUNT); do
|
|
||||||
sqs "$T1_AK" "$T1_SK" delete-queue \
|
|
||||||
--queue-url "$(qurl "$T1_ID" "stress-flood-${q}-${TS}")" >/dev/null 2>&1 &
|
|
||||||
done
|
|
||||||
wait
|
|
||||||
|
|
||||||
echo " Очереди удалены"
|
|
||||||
|
|
||||||
TOTAL_DUR=$(($(date +%s) - STARTED_AT))
|
|
||||||
echo ""
|
|
||||||
echo "╔═══════════════════════════════════════════════════════════════════╗"
|
|
||||||
echo "║ ИТОГО: ${PASS}/${TOTAL} ✅ ${FAIL}/${TOTAL} ❌ ║"
|
|
||||||
echo "║ Время: ${TOTAL_DUR}s ║"
|
|
||||||
echo "╚═══════════════════════════════════════════════════════════════════╝"
|
|
||||||
|
|
||||||
if [[ $FAIL -gt 0 ]]; then exit 1; fi
|
|
||||||
@@ -1,966 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
# stress_test.sh — ЖЁСТКИЙ стресс-тест shared-sqs
|
|
||||||
# Created: 2026-04-11
|
|
||||||
# Updated: 2026-04-11 — v2: масштаб, long-poll, race, batch, 50 очередей, memory, multi-kill
|
|
||||||
# Покрывает:
|
|
||||||
# 1. Подготовка (тенанты, очереди)
|
|
||||||
# 2. Параллельная отправка — 50 воркеров × 20 msg = 1000 сообщений
|
|
||||||
# 3. Параллельное чтение + удаление (гонка)
|
|
||||||
# 4. Multi-tenant изоляция (5 тенантов одновременно)
|
|
||||||
# 5. Burst — 100 сообщений одновременно
|
|
||||||
# 6. Long-polling + параллельные producer'ы
|
|
||||||
# 7. Двойное удаление (race на receipt handle)
|
|
||||||
# 8. Batch-операции конкурентно (SendMessageBatch + DeleteMessageBatch)
|
|
||||||
# 9. Много очередей — 30 очередей на тенанта
|
|
||||||
# 10. Kill pod + восстановление из Redis
|
|
||||||
# 11. Redis disconnect / reconnect
|
|
||||||
# 12. Смешанная нагрузка (send+recv+delete+getattr, 20s)
|
|
||||||
# 13. Memory leak check (RSS до/после нагрузки)
|
|
||||||
# 14. Multi-kill: kill → восстановление → kill → восстановление
|
|
||||||
# 15. Cleanup
|
|
||||||
# Запуск: BASE_URL=https://qu.kube5s.ru ADMIN_TOKEN=... bash tests/stress_test.sh
|
|
||||||
|
|
||||||
set -uo pipefail
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════════
|
|
||||||
# КОНФИГУРАЦИЯ
|
|
||||||
# ═══════════════════════════════════════════
|
|
||||||
BASE_URL="${BASE_URL:-https://qu.kube5s.ru}"
|
|
||||||
ADMIN_TOKEN="${ADMIN_TOKEN:-sqs-admin-7a7d8bd0c060a75c198d48680f34077a}"
|
|
||||||
TS=$(date +%s)
|
|
||||||
NAMESPACE="shared-sqs"
|
|
||||||
POD_LABEL="app.kubernetes.io/name=shared-sqs"
|
|
||||||
|
|
||||||
# Параметры нагрузки — ЖЁСТКИЕ
|
|
||||||
CONCURRENT_WORKERS=50 # параллельных воркеров
|
|
||||||
MESSAGES_PER_WORKER=20 # сообщений на воркер (итого 1000)
|
|
||||||
BURST_SIZE=100 # размер burst-пакета
|
|
||||||
TENANT_COUNT=5 # тенантов для multi-tenant теста
|
|
||||||
QUEUE_FLOOD_COUNT=30 # очередей на тенанта
|
|
||||||
MIXED_DURATION=20 # длительность смешанной нагрузки (сек)
|
|
||||||
BATCH_WORKERS=10 # воркеров для batch-операций
|
|
||||||
|
|
||||||
PASS=0
|
|
||||||
FAIL=0
|
|
||||||
TOTAL=0
|
|
||||||
TMPDIR_STRESS=$(mktemp -d /tmp/sqs_stress_XXXXX)
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════════
|
|
||||||
# ХЕЛПЕРЫ
|
|
||||||
# ═══════════════════════════════════════════
|
|
||||||
|
|
||||||
json_field() {
|
|
||||||
python3 -c "import sys,json; print(json.load(sys.stdin).get('$1',''))" 2>/dev/null
|
|
||||||
}
|
|
||||||
|
|
||||||
# progress_dot — печатает точку без переноса строки (прогресс)
|
|
||||||
progress_dot() {
|
|
||||||
printf "." >&2
|
|
||||||
}
|
|
||||||
|
|
||||||
# progress_done — завершает строку прогресса
|
|
||||||
progress_done() {
|
|
||||||
echo "" >&2
|
|
||||||
}
|
|
||||||
|
|
||||||
check() {
|
|
||||||
local name="$1" output="$2" pattern="$3"
|
|
||||||
TOTAL=$((TOTAL + 1))
|
|
||||||
if echo "$output" | grep -qE "$pattern"; then
|
|
||||||
PASS=$((PASS + 1))
|
|
||||||
echo " ✅ $name"
|
|
||||||
else
|
|
||||||
FAIL=$((FAIL + 1))
|
|
||||||
echo " ❌ $name"
|
|
||||||
echo " ожидалось: $pattern"
|
|
||||||
echo " получено: $(echo "$output" | head -5)"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
check_eq() {
|
|
||||||
local name="$1" got="$2" want="$3"
|
|
||||||
TOTAL=$((TOTAL + 1))
|
|
||||||
if [[ "$got" == "$want" ]]; then
|
|
||||||
PASS=$((PASS + 1))
|
|
||||||
echo " ✅ $name ($got)"
|
|
||||||
else
|
|
||||||
FAIL=$((FAIL + 1))
|
|
||||||
echo " ❌ $name: ожидалось=$want, получено=$got"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
check_ge() {
|
|
||||||
local name="$1" got="$2" want="$3"
|
|
||||||
TOTAL=$((TOTAL + 1))
|
|
||||||
if [[ "$got" -ge "$want" ]]; then
|
|
||||||
PASS=$((PASS + 1))
|
|
||||||
echo " ✅ $name ($got >= $want)"
|
|
||||||
else
|
|
||||||
FAIL=$((FAIL + 1))
|
|
||||||
echo " ❌ $name: ожидалось >=$want, получено=$got"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
check_le() {
|
|
||||||
local name="$1" got="$2" want="$3"
|
|
||||||
TOTAL=$((TOTAL + 1))
|
|
||||||
if [[ "$got" -le "$want" ]]; then
|
|
||||||
PASS=$((PASS + 1))
|
|
||||||
echo " ✅ $name ($got <= $want)"
|
|
||||||
else
|
|
||||||
FAIL=$((FAIL + 1))
|
|
||||||
echo " ❌ $name: ожидалось <=$want, получено=$got"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
sqs() {
|
|
||||||
local ak="$1" sk="$2"
|
|
||||||
shift 2
|
|
||||||
AWS_ACCESS_KEY_ID="$ak" \
|
|
||||||
AWS_SECRET_ACCESS_KEY="$sk" \
|
|
||||||
AWS_DEFAULT_REGION="us-east-1" \
|
|
||||||
aws --endpoint-url "$BASE_URL" --output json sqs "$@" 2>&1
|
|
||||||
}
|
|
||||||
|
|
||||||
admin_api() {
|
|
||||||
local method="$1" path="$2" body="${3:-}"
|
|
||||||
if [[ -n "$body" ]]; then
|
|
||||||
curl -s --max-time 20 -X "$method" \
|
|
||||||
-H "Authorization: Bearer $ADMIN_TOKEN" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d "$body" "${BASE_URL}${path}" 2>&1
|
|
||||||
else
|
|
||||||
curl -s --max-time 20 -X "$method" \
|
|
||||||
-H "Authorization: Bearer $ADMIN_TOKEN" "${BASE_URL}${path}" 2>&1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
qurl() { echo "${BASE_URL}/${1}/${2}"; }
|
|
||||||
|
|
||||||
wait_for_pod_ready() {
|
|
||||||
local max_wait=120 waited=0
|
|
||||||
echo " ⏳ Ожидание пода (max ${max_wait}s)..."
|
|
||||||
while [[ $waited -lt $max_wait ]]; do
|
|
||||||
local ready
|
|
||||||
ready=$(kubectl get pods -n "$NAMESPACE" -l "$POD_LABEL" \
|
|
||||||
-o jsonpath='{.items[0].status.conditions[?(@.type=="Ready")].status}' 2>/dev/null)
|
|
||||||
if [[ "$ready" == "True" ]]; then
|
|
||||||
echo " ✅ Под готов (${waited}s)"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
sleep 2
|
|
||||||
waited=$((waited + 2))
|
|
||||||
printf "." >&2
|
|
||||||
done
|
|
||||||
echo ""
|
|
||||||
echo " ⚠️ Под не готов после ${max_wait}s"
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
wait_for_healthy() {
|
|
||||||
local max_wait=120 waited=0
|
|
||||||
echo " ⏳ Ожидание /health (max ${max_wait}s)..."
|
|
||||||
while [[ $waited -lt $max_wait ]]; do
|
|
||||||
local status
|
|
||||||
status=$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 "${BASE_URL}/health" 2>/dev/null)
|
|
||||||
if [[ "$status" == "200" ]]; then
|
|
||||||
echo " ✅ Сервис отвечает (${waited}s)"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
sleep 2
|
|
||||||
waited=$((waited + 2))
|
|
||||||
printf "." >&2
|
|
||||||
done
|
|
||||||
echo ""
|
|
||||||
echo " ⚠️ Сервис не отвечает после ${max_wait}s"
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# get_pod_rss_mb — RSS памяти текущего пода в MB
|
|
||||||
get_pod_rss_mb() {
|
|
||||||
local pod
|
|
||||||
pod=$(kubectl get pods -n "$NAMESPACE" -l "$POD_LABEL" \
|
|
||||||
-o jsonpath='{.items[0].metadata.name}' 2>/dev/null)
|
|
||||||
# kubectl top может не работать — fallback на /proc
|
|
||||||
local rss_kb
|
|
||||||
rss_kb=$(kubectl exec -n "$NAMESPACE" "$pod" -- cat /proc/1/status 2>/dev/null \
|
|
||||||
| grep VmRSS | awk '{print $2}')
|
|
||||||
if [[ -n "$rss_kb" ]]; then
|
|
||||||
echo $((rss_kb / 1024))
|
|
||||||
else
|
|
||||||
echo "0"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
kill_pod() {
|
|
||||||
local pod
|
|
||||||
pod=$(kubectl get pods -n "$NAMESPACE" -l "$POD_LABEL" \
|
|
||||||
-o jsonpath='{.items[0].metadata.name}' 2>/dev/null)
|
|
||||||
echo " 🔪 Kill: $pod"
|
|
||||||
kubectl delete pod "$pod" -n "$NAMESPACE" --grace-period=0 --force 2>&1 | head -1
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup() { rm -rf "$TMPDIR_STRESS" 2>/dev/null; }
|
|
||||||
trap cleanup EXIT
|
|
||||||
|
|
||||||
STARTED_AT=$(date +%s)
|
|
||||||
|
|
||||||
echo "╔═══════════════════════════════════════════════════════════════════╗"
|
|
||||||
echo "║ shared-sqs STRESS TEST v2 — $(date '+%Y-%m-%d %H:%M:%S') ║"
|
|
||||||
echo "║ Endpoint: ${BASE_URL} ║"
|
|
||||||
echo "║ Workers: ${CONCURRENT_WORKERS} Msgs/worker: ${MESSAGES_PER_WORKER} Burst: ${BURST_SIZE} Tenants: ${TENANT_COUNT} ║"
|
|
||||||
echo "║ Queue-flood: ${QUEUE_FLOOD_COUNT} Batch-workers: ${BATCH_WORKERS} Mixed: ${MIXED_DURATION}s ║"
|
|
||||||
echo "╚═══════════════════════════════════════════════════════════════════╝"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════
|
|
||||||
# 1. ПОДГОТОВКА — тенанты и очереди
|
|
||||||
# ═══════════════════════════════════════════════════════════
|
|
||||||
echo "═══ 1. Подготовка — создаём $TENANT_COUNT тенантов ═══"
|
|
||||||
|
|
||||||
declare -a TENANT_AK TENANT_SK TENANT_ID
|
|
||||||
for i in $(seq 1 $TENANT_COUNT); do
|
|
||||||
R=$(admin_api POST "/admin/tenants" "{\"name\":\"stress-t${i}-${TS}\",\"max_queues\":100}")
|
|
||||||
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]:0:20}..."
|
|
||||||
if [[ -z "${TENANT_AK[$i]}" ]]; then
|
|
||||||
echo " ⛔ ОШИБКА создания тенанта ${i}: $R"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# Сохраняем в файлы — для subshell
|
|
||||||
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
|
|
||||||
|
|
||||||
T1_AK="${TENANT_AK[1]}"; T1_SK="${TENANT_SK[1]}"; T1_ID="${TENANT_ID[1]}"
|
|
||||||
|
|
||||||
# Очереди
|
|
||||||
STRESS_Q="stress-conc-${TS}"
|
|
||||||
for i in $(seq 1 $TENANT_COUNT); do
|
|
||||||
R=$(sqs "${TENANT_AK[$i]}" "${TENANT_SK[$i]}" create-queue --queue-name "$STRESS_Q")
|
|
||||||
done
|
|
||||||
STRESS_QURL=$(qurl "$T1_ID" "$STRESS_Q")
|
|
||||||
|
|
||||||
BURST_Q="stress-burst-${TS}"
|
|
||||||
R=$(sqs "$T1_AK" "$T1_SK" create-queue --queue-name "$BURST_Q")
|
|
||||||
BURST_QURL=$(qurl "$T1_ID" "$BURST_Q")
|
|
||||||
|
|
||||||
echo " Очередь conc: $STRESS_Q (для $TENANT_COUNT тенантов)"
|
|
||||||
echo " Очередь burst: $BURST_Q"
|
|
||||||
echo " Подготовка завершена за $(($(date +%s) - STARTED_AT))s"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════
|
|
||||||
# 2. КОНКУРЕНТНАЯ ОТПРАВКА — 50 × 20 = 1000 сообщений
|
|
||||||
# ═══════════════════════════════════════════════════════════
|
|
||||||
TOTAL_MSGS=$((CONCURRENT_WORKERS * MESSAGES_PER_WORKER))
|
|
||||||
echo "═══ 2. Конкурентная отправка: ${CONCURRENT_WORKERS} воркеров × ${MESSAGES_PER_WORKER} = ${TOTAL_MSGS} сообщений ═══"
|
|
||||||
SEC2_START=$(date +%s)
|
|
||||||
|
|
||||||
for w in $(seq 1 $CONCURRENT_WORKERS); do
|
|
||||||
(
|
|
||||||
ok=0; fail=0
|
|
||||||
for m in $(seq 1 $MESSAGES_PER_WORKER); do
|
|
||||||
R=$(sqs "$T1_AK" "$T1_SK" send-message \
|
|
||||||
--queue-url "$STRESS_QURL" \
|
|
||||||
--message-body "w${w}-m${m}-${TS}" 2>&1)
|
|
||||||
if echo "$R" | grep -q "MessageId"; then ok=$((ok + 1)); else fail=$((fail + 1)); fi
|
|
||||||
done
|
|
||||||
echo "${ok}:${fail}" > "${TMPDIR_STRESS}/send_w${w}.result"
|
|
||||||
# Прогресс: каждый 10-й воркер печатает
|
|
||||||
if (( w % 10 == 0 )); then printf "[W${w}:${ok}ok] " >&2; fi
|
|
||||||
) &
|
|
||||||
done
|
|
||||||
echo " ⏳ Отправка идёт... (прогресс каждые 10 воркеров)"
|
|
||||||
wait
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
SEND_OK=0; SEND_FAIL=0
|
|
||||||
for w in $(seq 1 $CONCURRENT_WORKERS); do
|
|
||||||
if [[ -f "${TMPDIR_STRESS}/send_w${w}.result" ]]; then
|
|
||||||
IFS=':' read -r ok fail < "${TMPDIR_STRESS}/send_w${w}.result"
|
|
||||||
SEND_OK=$((SEND_OK + ok)); SEND_FAIL=$((SEND_FAIL + fail))
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
SEC2_DUR=$(($(date +%s) - SEC2_START))
|
|
||||||
echo " Результат: ${SEND_OK} ok, ${SEND_FAIL} fail за ${SEC2_DUR}s (~$((SEND_OK / (SEC2_DUR + 1))) msg/s)"
|
|
||||||
check_eq "Все ${TOTAL_MSGS} сообщений отправлены" "$SEND_OK" "$TOTAL_MSGS"
|
|
||||||
|
|
||||||
sleep 2
|
|
||||||
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 " GetQueueAttributes: ~${APPROX} в очереди"
|
|
||||||
check_eq "Количество в очереди = $TOTAL_MSGS" "${APPROX:-0}" "$TOTAL_MSGS"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════
|
|
||||||
# 3. КОНКУРЕНТНОЕ ЧТЕНИЕ — гонка за 1000 сообщений
|
|
||||||
# ═══════════════════════════════════════════════════════════
|
|
||||||
echo "═══ 3. Конкурентное чтение: ${CONCURRENT_WORKERS} воркеров забирают ${TOTAL_MSGS} сообщений ═══"
|
|
||||||
SEC3_START=$(date +%s)
|
|
||||||
|
|
||||||
for w in $(seq 1 $CONCURRENT_WORKERS); do
|
|
||||||
(
|
|
||||||
received=0; deleted=0; empty_rounds=0
|
|
||||||
while [[ $empty_rounds -lt 8 ]]; do
|
|
||||||
R=$(sqs "$T1_AK" "$T1_SK" receive-message \
|
|
||||||
--queue-url "$STRESS_QURL" \
|
|
||||||
--max-number-of-messages 10 --wait-time-seconds 1 2>&1)
|
|
||||||
MSG_COUNT=$(echo "$R" | grep -c '"MessageId"' || true)
|
|
||||||
if [[ "$MSG_COUNT" -eq 0 ]]; then empty_rounds=$((empty_rounds + 1)); continue; fi
|
|
||||||
empty_rounds=0
|
|
||||||
received=$((received + MSG_COUNT))
|
|
||||||
HANDLES=$(echo "$R" | grep -oP '"ReceiptHandle"\s*:\s*"\K[^"]+')
|
|
||||||
while IFS= read -r handle; do
|
|
||||||
sqs "$T1_AK" "$T1_SK" delete-message \
|
|
||||||
--queue-url "$STRESS_QURL" --receipt-handle "$handle" >/dev/null 2>&1
|
|
||||||
deleted=$((deleted + 1))
|
|
||||||
done <<< "$HANDLES"
|
|
||||||
done
|
|
||||||
echo "${received}:${deleted}" > "${TMPDIR_STRESS}/recv_w${w}.result"
|
|
||||||
if (( w % 10 == 0 )); then printf "[R${w}:${received}r/${deleted}d] " >&2; fi
|
|
||||||
) &
|
|
||||||
done
|
|
||||||
echo " ⏳ Чтение идёт... (прогресс каждые 10 воркеров)"
|
|
||||||
wait
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
RECV_TOTAL=0; DEL_TOTAL=0
|
|
||||||
for w in $(seq 1 $CONCURRENT_WORKERS); do
|
|
||||||
if [[ -f "${TMPDIR_STRESS}/recv_w${w}.result" ]]; then
|
|
||||||
IFS=':' read -r recv del < "${TMPDIR_STRESS}/recv_w${w}.result"
|
|
||||||
RECV_TOTAL=$((RECV_TOTAL + recv)); DEL_TOTAL=$((DEL_TOTAL + del))
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
SEC3_DUR=$(($(date +%s) - SEC3_START))
|
|
||||||
echo " Результат: прочитано=${RECV_TOTAL}, удалено=${DEL_TOTAL} за ${SEC3_DUR}s"
|
|
||||||
check_ge "Прочитано >= $TOTAL_MSGS" "$RECV_TOTAL" "$TOTAL_MSGS"
|
|
||||||
|
|
||||||
sleep 2
|
|
||||||
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"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════
|
|
||||||
# 4. MULTI-TENANT ИЗОЛЯЦИЯ — 5 тенантов × 50 msg одновременно
|
|
||||||
# ═══════════════════════════════════════════════════════════
|
|
||||||
MSGS_PER_TENANT=50
|
|
||||||
echo "═══ 4. Multi-tenant изоляция: ${TENANT_COUNT} тенантов × ${MSGS_PER_TENANT} msg ═══"
|
|
||||||
SEC4_START=$(date +%s)
|
|
||||||
|
|
||||||
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 "$tak" "$tsk" send-message --queue-url "$my_qurl" \
|
|
||||||
--message-body "tenant-${i}-iso-${m}" 2>&1)
|
|
||||||
if echo "$R" | grep -q "MessageId"; then ok=$((ok + 1)); fi
|
|
||||||
done
|
|
||||||
echo "$ok" > "${TMPDIR_STRESS}/tenant_send_${i}.result"
|
|
||||||
printf "[T${i}:${ok}sent] " >&2
|
|
||||||
) &
|
|
||||||
done
|
|
||||||
echo " ⏳ Тенанты отправляют..."
|
|
||||||
wait
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Чтение и проверка изоляции
|
|
||||||
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 "$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)
|
|
||||||
if [[ "$MSG_COUNT" -eq 0 ]]; then empty_rounds=$((empty_rounds + 1)); continue; fi
|
|
||||||
empty_rounds=0
|
|
||||||
while IFS= read -r body; do
|
|
||||||
if echo "$body" | grep -q "tenant-${i}-"; then own=$((own + 1)); else foreign=$((foreign + 1)); fi
|
|
||||||
done < <(echo "$R" | grep -oP '"Body"\s*:\s*"\K[^"]+')
|
|
||||||
HANDLES=$(echo "$R" | grep -oP '"ReceiptHandle"\s*:\s*"\K[^"]+')
|
|
||||||
while IFS= read -r handle; do
|
|
||||||
sqs "$tak" "$tsk" delete-message --queue-url "$my_qurl" --receipt-handle "$handle" >/dev/null 2>&1
|
|
||||||
done <<< "$HANDLES"
|
|
||||||
done
|
|
||||||
echo "${own}:${foreign}" > "${TMPDIR_STRESS}/tenant_recv_${i}.result"
|
|
||||||
printf "[T${i}:own=${own},foreign=${foreign}] " >&2
|
|
||||||
) &
|
|
||||||
done
|
|
||||||
echo " ⏳ Тенанты читают..."
|
|
||||||
wait
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
ALL_FOREIGN=0; ALL_OWN=0
|
|
||||||
for i in $(seq 1 $TENANT_COUNT); do
|
|
||||||
if [[ -f "${TMPDIR_STRESS}/tenant_recv_${i}.result" ]]; then
|
|
||||||
IFS=':' read -r own foreign < "${TMPDIR_STRESS}/tenant_recv_${i}.result"
|
|
||||||
echo " Тенант ${i}: своих=${own}, чужих=${foreign}"
|
|
||||||
ALL_OWN=$((ALL_OWN + own)); ALL_FOREIGN=$((ALL_FOREIGN + foreign))
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
SEC4_DUR=$(($(date +%s) - SEC4_START))
|
|
||||||
echo " Итого: своих=$ALL_OWN, чужих=$ALL_FOREIGN за ${SEC4_DUR}s"
|
|
||||||
check_eq "Чужих сообщений: 0" "$ALL_FOREIGN" "0"
|
|
||||||
check_ge "Свои сообщения получены" "$ALL_OWN" "$((TENANT_COUNT * MSGS_PER_TENANT))"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════
|
|
||||||
# 5. BURST — 100 сообщений одновременно
|
|
||||||
# ═══════════════════════════════════════════════════════════
|
|
||||||
echo "═══ 5. Burst: ${BURST_SIZE} сообщений одновременно ═══"
|
|
||||||
SEC5_START=$(date +%s)
|
|
||||||
|
|
||||||
for b in $(seq 1 $BURST_SIZE); do
|
|
||||||
(
|
|
||||||
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"
|
|
||||||
) &
|
|
||||||
# Прогресс пачками по 25
|
|
||||||
if (( b % 25 == 0 )); then printf "[${b}/${BURST_SIZE}] " >&2; fi
|
|
||||||
done
|
|
||||||
echo " ⏳ Burst отправка..."
|
|
||||||
wait
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
BURST_OK=0
|
|
||||||
for b in $(seq 1 $BURST_SIZE); do
|
|
||||||
[[ -f "${TMPDIR_STRESS}/burst_${b}.result" ]] && BURST_OK=$((BURST_OK + $(cat "${TMPDIR_STRESS}/burst_${b}.result")))
|
|
||||||
done
|
|
||||||
SEC5_DUR=$(($(date +%s) - SEC5_START))
|
|
||||||
echo " Результат: ${BURST_OK}/${BURST_SIZE} за ${SEC5_DUR}s"
|
|
||||||
check_ge "Burst >= 90%" "$BURST_OK" "$((BURST_SIZE * 90 / 100))"
|
|
||||||
|
|
||||||
sleep 3
|
|
||||||
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}"
|
|
||||||
check_eq "Burst все в очереди" "${BURST_APPROX:-0}" "$BURST_OK"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════
|
|
||||||
# 6. LONG-POLLING + параллельные producer'ы
|
|
||||||
# ═══════════════════════════════════════════════════════════
|
|
||||||
echo "═══ 6. Long-polling: consumer WaitTime=10s + 5 producers ═══"
|
|
||||||
SEC6_START=$(date +%s)
|
|
||||||
|
|
||||||
LONGPOLL_Q="stress-longpoll-${TS}"
|
|
||||||
R=$(sqs "$T1_AK" "$T1_SK" create-queue --queue-name "$LONGPOLL_Q" \
|
|
||||||
--attributes '{"ReceiveMessageWaitTimeSeconds":"10"}')
|
|
||||||
LONGPOLL_QURL=$(qurl "$T1_ID" "$LONGPOLL_Q")
|
|
||||||
|
|
||||||
# Consumer: читает с long-poll, считает сколько получит за 30s
|
|
||||||
(
|
|
||||||
end=$((SECONDS + 30))
|
|
||||||
total_recv=0
|
|
||||||
while [[ $SECONDS -lt $end ]]; do
|
|
||||||
R=$(sqs "$T1_AK" "$T1_SK" receive-message --queue-url "$LONGPOLL_QURL" \
|
|
||||||
--max-number-of-messages 10 --wait-time-seconds 10 2>&1)
|
|
||||||
cnt=$(echo "$R" | grep -c '"MessageId"' || true)
|
|
||||||
total_recv=$((total_recv + cnt))
|
|
||||||
if (( total_recv % 10 == 0 && total_recv > 0 )); then printf "[LP-recv:${total_recv}] " >&2; fi
|
|
||||||
done
|
|
||||||
echo "$total_recv" > "${TMPDIR_STRESS}/longpoll_recv.result"
|
|
||||||
) &
|
|
||||||
|
|
||||||
# Producers: начинают через 3s, каждый отправляет 20 msg с паузами
|
|
||||||
sleep 3
|
|
||||||
for p in $(seq 1 5); do
|
|
||||||
(
|
|
||||||
ok=0
|
|
||||||
for m in $(seq 1 20); do
|
|
||||||
R=$(sqs "$T1_AK" "$T1_SK" send-message --queue-url "$LONGPOLL_QURL" \
|
|
||||||
--message-body "longpoll-p${p}-m${m}" 2>&1)
|
|
||||||
if echo "$R" | grep -q "MessageId"; then ok=$((ok + 1)); fi
|
|
||||||
sleep 0.2
|
|
||||||
done
|
|
||||||
echo "$ok" > "${TMPDIR_STRESS}/longpoll_prod_${p}.result"
|
|
||||||
printf "[LP-prod${p}:${ok}] " >&2
|
|
||||||
) &
|
|
||||||
done
|
|
||||||
echo " ⏳ Long-poll: consumer ждёт, producers отправляют (30s)..."
|
|
||||||
wait
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
LP_SENT=0
|
|
||||||
for p in $(seq 1 5); do
|
|
||||||
[[ -f "${TMPDIR_STRESS}/longpoll_prod_${p}.result" ]] && LP_SENT=$((LP_SENT + $(cat "${TMPDIR_STRESS}/longpoll_prod_${p}.result")))
|
|
||||||
done
|
|
||||||
LP_RECV=$(cat "${TMPDIR_STRESS}/longpoll_recv.result" 2>/dev/null || echo 0)
|
|
||||||
SEC6_DUR=$(($(date +%s) - SEC6_START))
|
|
||||||
echo " Producers отправили: $LP_SENT, consumer получил: $LP_RECV за ${SEC6_DUR}s"
|
|
||||||
check_ge "Long-poll: получено >= 50% отправленных" "$LP_RECV" "$((LP_SENT / 2))"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════
|
|
||||||
# 7. ДВОЙНОЕ УДАЛЕНИЕ — race на receipt handle
|
|
||||||
# ═══════════════════════════════════════════════════════════
|
|
||||||
echo "═══ 7. Двойное удаление: 2 воркера гонятся за одно сообщение ═══"
|
|
||||||
|
|
||||||
RACE_Q="stress-race-${TS}"
|
|
||||||
R=$(sqs "$T1_AK" "$T1_SK" create-queue --queue-name "$RACE_Q")
|
|
||||||
RACE_QURL=$(qurl "$T1_ID" "$RACE_Q")
|
|
||||||
|
|
||||||
# Отправляем 20 сообщений
|
|
||||||
for m in $(seq 1 20); do
|
|
||||||
sqs "$T1_AK" "$T1_SK" send-message --queue-url "$RACE_QURL" \
|
|
||||||
--message-body "race-${m}" >/dev/null 2>&1
|
|
||||||
done
|
|
||||||
echo " Отправлено 20 сообщений"
|
|
||||||
|
|
||||||
# VisibilityTimeout=0 — чтобы оба воркера получали одни сообщения
|
|
||||||
RACE_DOUBLE_DEL=0
|
|
||||||
RACE_OK_DEL=0
|
|
||||||
for round in $(seq 1 10); do
|
|
||||||
# Два воркера одновременно получают
|
|
||||||
R1=$(sqs "$T1_AK" "$T1_SK" receive-message --queue-url "$RACE_QURL" \
|
|
||||||
--max-number-of-messages 1 --visibility-timeout 30 2>&1)
|
|
||||||
HANDLE=$(echo "$R1" | grep -oP '"ReceiptHandle"\s*:\s*"\K[^"]+' | head -1)
|
|
||||||
if [[ -z "$HANDLE" ]]; then continue; fi
|
|
||||||
|
|
||||||
# Два параллельных delete
|
|
||||||
D1=$(sqs "$T1_AK" "$T1_SK" delete-message --queue-url "$RACE_QURL" --receipt-handle "$HANDLE" 2>&1) &
|
|
||||||
PID1=$!
|
|
||||||
D2=$(sqs "$T1_AK" "$T1_SK" delete-message --queue-url "$RACE_QURL" --receipt-handle "$HANDLE" 2>&1) &
|
|
||||||
PID2=$!
|
|
||||||
wait $PID1 $PID2
|
|
||||||
RACE_OK_DEL=$((RACE_OK_DEL + 1))
|
|
||||||
printf "[race-${round}] " >&2
|
|
||||||
done
|
|
||||||
echo ""
|
|
||||||
echo " Раундов race: ${RACE_OK_DEL}/10"
|
|
||||||
# Главное: сервер НЕ упал
|
|
||||||
R=$(sqs "$T1_AK" "$T1_SK" get-queue-attributes --queue-url "$RACE_QURL" \
|
|
||||||
--attribute-names ApproximateNumberOfMessages 2>&1)
|
|
||||||
check "Сервер жив после double-delete" "$R" "ApproximateNumberOfMessages"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════
|
|
||||||
# 8. BATCH-ОПЕРАЦИИ конкурентно
|
|
||||||
# ═══════════════════════════════════════════════════════════
|
|
||||||
echo "═══ 8. Batch-операции: ${BATCH_WORKERS} воркеров SendMessageBatch + DeleteMessageBatch ═══"
|
|
||||||
SEC8_START=$(date +%s)
|
|
||||||
|
|
||||||
BATCH_Q="stress-batch-${TS}"
|
|
||||||
R=$(sqs "$T1_AK" "$T1_SK" create-queue --queue-name "$BATCH_Q")
|
|
||||||
BATCH_QURL=$(qurl "$T1_ID" "$BATCH_Q")
|
|
||||||
|
|
||||||
# Параллельные SendMessageBatch (10 msg per batch × 10 workers = 100 msg)
|
|
||||||
for w in $(seq 1 $BATCH_WORKERS); do
|
|
||||||
(
|
|
||||||
# Формируем batch из 10 записей
|
|
||||||
ENTRIES=""
|
|
||||||
for e in $(seq 1 10); do
|
|
||||||
[[ -n "$ENTRIES" ]] && ENTRIES="${ENTRIES},"
|
|
||||||
ENTRIES="${ENTRIES}{\"Id\":\"${w}-${e}\",\"MessageBody\":\"batch-w${w}-e${e}\"}"
|
|
||||||
done
|
|
||||||
R=$(sqs "$T1_AK" "$T1_SK" send-message-batch --queue-url "$BATCH_QURL" \
|
|
||||||
--entries "[${ENTRIES}]" 2>&1)
|
|
||||||
ok=$(echo "$R" | grep -c '"MessageId"' || true)
|
|
||||||
echo "$ok" > "${TMPDIR_STRESS}/batch_send_${w}.result"
|
|
||||||
printf "[B-send${w}:${ok}] " >&2
|
|
||||||
) &
|
|
||||||
done
|
|
||||||
echo " ⏳ SendMessageBatch..."
|
|
||||||
wait
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
BATCH_SENT=0
|
|
||||||
for w in $(seq 1 $BATCH_WORKERS); do
|
|
||||||
[[ -f "${TMPDIR_STRESS}/batch_send_${w}.result" ]] && BATCH_SENT=$((BATCH_SENT + $(cat "${TMPDIR_STRESS}/batch_send_${w}.result")))
|
|
||||||
done
|
|
||||||
echo " Batch отправлено: ${BATCH_SENT}"
|
|
||||||
check_ge "Batch: отправлено >= 80" "$BATCH_SENT" "80"
|
|
||||||
|
|
||||||
# Параллельные ReceiveMessage + DeleteMessageBatch
|
|
||||||
BATCH_DELETED=0
|
|
||||||
for w in $(seq 1 5); do
|
|
||||||
(
|
|
||||||
del=0; empty=0
|
|
||||||
while [[ $empty -lt 3 ]]; do
|
|
||||||
R=$(sqs "$T1_AK" "$T1_SK" receive-message --queue-url "$BATCH_QURL" \
|
|
||||||
--max-number-of-messages 10 --wait-time-seconds 1 2>&1)
|
|
||||||
HANDLES=$(echo "$R" | grep -oP '"ReceiptHandle"\s*:\s*"\K[^"]+')
|
|
||||||
if [[ -z "$HANDLES" ]]; then empty=$((empty + 1)); continue; fi
|
|
||||||
empty=0
|
|
||||||
# Формируем batch delete
|
|
||||||
ENTRIES=""
|
|
||||||
idx=0
|
|
||||||
while IFS= read -r handle; do
|
|
||||||
[[ -z "$handle" ]] && continue
|
|
||||||
[[ -n "$ENTRIES" ]] && ENTRIES="${ENTRIES},"
|
|
||||||
ENTRIES="${ENTRIES}{\"Id\":\"${idx}\",\"ReceiptHandle\":\"${handle}\"}"
|
|
||||||
idx=$((idx + 1))
|
|
||||||
done <<< "$HANDLES"
|
|
||||||
if [[ -n "$ENTRIES" ]]; then
|
|
||||||
DR=$(sqs "$T1_AK" "$T1_SK" delete-message-batch --queue-url "$BATCH_QURL" \
|
|
||||||
--entries "[${ENTRIES}]" 2>&1)
|
|
||||||
ok_del=$(echo "$DR" | grep -c '"Id"' || true)
|
|
||||||
del=$((del + ok_del))
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
echo "$del" > "${TMPDIR_STRESS}/batch_del_${w}.result"
|
|
||||||
printf "[B-del${w}:${del}] " >&2
|
|
||||||
) &
|
|
||||||
done
|
|
||||||
echo " ⏳ DeleteMessageBatch..."
|
|
||||||
wait
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
BATCH_DEL_TOTAL=0
|
|
||||||
for w in $(seq 1 5); do
|
|
||||||
[[ -f "${TMPDIR_STRESS}/batch_del_${w}.result" ]] && BATCH_DEL_TOTAL=$((BATCH_DEL_TOTAL + $(cat "${TMPDIR_STRESS}/batch_del_${w}.result")))
|
|
||||||
done
|
|
||||||
SEC8_DUR=$(($(date +%s) - SEC8_START))
|
|
||||||
echo " Batch удалено: $BATCH_DEL_TOTAL за ${SEC8_DUR}s"
|
|
||||||
check_ge "Batch: удалено >= 80%" "$BATCH_DEL_TOTAL" "$((BATCH_SENT * 80 / 100))"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════
|
|
||||||
# 9. МНОГО ОЧЕРЕДЕЙ — 30 очередей на тенанта
|
|
||||||
# ═══════════════════════════════════════════════════════════
|
|
||||||
echo "═══ 9. Queue-flood: ${QUEUE_FLOOD_COUNT} очередей, send+receive в каждую ═══"
|
|
||||||
SEC9_START=$(date +%s)
|
|
||||||
|
|
||||||
# Создаём 30 очередей параллельно
|
|
||||||
for q in $(seq 1 $QUEUE_FLOOD_COUNT); do
|
|
||||||
(
|
|
||||||
qname="stress-flood-${q}-${TS}"
|
|
||||||
my_qurl=$(qurl "$T1_ID" "$qname")
|
|
||||||
# Создаём
|
|
||||||
R=$(sqs "$T1_AK" "$T1_SK" create-queue --queue-name "$qname" 2>&1)
|
|
||||||
if ! echo "$R" | grep -q "QueueUrl"; then echo "0" > "${TMPDIR_STRESS}/flood_${q}.result"; exit 0; fi
|
|
||||||
# Отправляем 5 msg
|
|
||||||
for m in $(seq 1 5); do
|
|
||||||
sqs "$T1_AK" "$T1_SK" send-message --queue-url "$my_qurl" \
|
|
||||||
--message-body "flood-q${q}-m${m}" >/dev/null 2>&1
|
|
||||||
done
|
|
||||||
# Читаем 1
|
|
||||||
R=$(sqs "$T1_AK" "$T1_SK" receive-message --queue-url "$my_qurl" \
|
|
||||||
--max-number-of-messages 1 --wait-time-seconds 1 2>&1)
|
|
||||||
if echo "$R" | grep -q "MessageId"; then echo "1" > "${TMPDIR_STRESS}/flood_${q}.result"
|
|
||||||
else echo "0" > "${TMPDIR_STRESS}/flood_${q}.result"; fi
|
|
||||||
if (( q % 10 == 0 )); then printf "[Q${q}] " >&2; fi
|
|
||||||
) &
|
|
||||||
done
|
|
||||||
echo " ⏳ Создание и тест ${QUEUE_FLOOD_COUNT} очередей..."
|
|
||||||
wait
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
FLOOD_OK=0
|
|
||||||
for q in $(seq 1 $QUEUE_FLOOD_COUNT); do
|
|
||||||
[[ -f "${TMPDIR_STRESS}/flood_${q}.result" ]] && FLOOD_OK=$((FLOOD_OK + $(cat "${TMPDIR_STRESS}/flood_${q}.result")))
|
|
||||||
done
|
|
||||||
SEC9_DUR=$(($(date +%s) - SEC9_START))
|
|
||||||
echo " Успешных: ${FLOOD_OK}/${QUEUE_FLOOD_COUNT} за ${SEC9_DUR}s"
|
|
||||||
check_ge "Queue-flood: >= 90% очередей работают" "$FLOOD_OK" "$((QUEUE_FLOOD_COUNT * 90 / 100))"
|
|
||||||
|
|
||||||
# Проверяем ListQueues — должно быть >= 30 flood-очередей
|
|
||||||
R=$(sqs "$T1_AK" "$T1_SK" list-queues --queue-name-prefix "stress-flood" 2>&1)
|
|
||||||
LISTED=$(echo "$R" | grep -c "stress-flood" || true)
|
|
||||||
echo " ListQueues видит: $LISTED очередей"
|
|
||||||
check_ge "ListQueues >= ${QUEUE_FLOOD_COUNT}" "$LISTED" "$QUEUE_FLOOD_COUNT"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════
|
|
||||||
# 10. MEMORY CHECK — RSS до нагрузки
|
|
||||||
# ═══════════════════════════════════════════════════════════
|
|
||||||
echo "═══ 10. Memory check — запоминаем RSS ═══"
|
|
||||||
RSS_BEFORE=$(get_pod_rss_mb)
|
|
||||||
echo " RSS до тяжёлой фазы: ${RSS_BEFORE} MB"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════
|
|
||||||
# 11. KILL POD + восстановление из Redis
|
|
||||||
# ═══════════════════════════════════════════════════════════
|
|
||||||
echo "═══ 11. Kill pod → восстановление из Redis ═══"
|
|
||||||
|
|
||||||
# Перед kill — отправляем в burst-очередь ещё 20 msg
|
|
||||||
EXTRA=20
|
|
||||||
for m in $(seq 1 $EXTRA); do
|
|
||||||
sqs "$T1_AK" "$T1_SK" send-message --queue-url "$BURST_QURL" \
|
|
||||||
--message-body "pre-kill-${m}-${TS}" >/dev/null 2>&1
|
|
||||||
if (( m % 5 == 0 )); then printf "[pre-kill:${m}] " >&2; fi
|
|
||||||
done
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
R=$(sqs "$T1_AK" "$T1_SK" get-queue-attributes --queue-url "$BURST_QURL" \
|
|
||||||
--attribute-names ApproximateNumberOfMessages 2>&1)
|
|
||||||
BEFORE_KILL=$(echo "$R" | grep -oP '"ApproximateNumberOfMessages"\s*:\s*"\K[0-9]+')
|
|
||||||
echo " Сообщений в burst до kill: ${BEFORE_KILL:-?}"
|
|
||||||
|
|
||||||
kill_pod
|
|
||||||
echo " Ждём рестарт..."
|
|
||||||
wait_for_pod_ready
|
|
||||||
wait_for_healthy
|
|
||||||
|
|
||||||
sleep 3
|
|
||||||
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}"
|
|
||||||
check_eq "Данные восстановлены из Redis" "${AFTER_KILL:-0}" "${BEFORE_KILL}"
|
|
||||||
|
|
||||||
R=$(admin_api GET "/admin/tenants")
|
|
||||||
TC=$(echo "$R" | grep -c "stress-t1-${TS}" || true)
|
|
||||||
check_ge "Тенанты восстановлены" "$TC" "1"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════
|
|
||||||
# 12. REDIS DISCONNECT / RECONNECT
|
|
||||||
# ═══════════════════════════════════════════════════════════
|
|
||||||
echo "═══ 12. Redis disconnect simulation ═══"
|
|
||||||
|
|
||||||
cat <<'NETPOL' | kubectl apply -n "$NAMESPACE" -f - 2>&1 | head -1
|
|
||||||
apiVersion: networking.k8s.io/v1
|
|
||||||
kind: NetworkPolicy
|
|
||||||
metadata:
|
|
||||||
name: stress-test-block-redis
|
|
||||||
spec:
|
|
||||||
podSelector:
|
|
||||||
matchLabels:
|
|
||||||
app.kubernetes.io/name: shared-sqs
|
|
||||||
policyTypes:
|
|
||||||
- Egress
|
|
||||||
egress:
|
|
||||||
- to:
|
|
||||||
- ipBlock:
|
|
||||||
cidr: 0.0.0.0/0
|
|
||||||
except:
|
|
||||||
- 10.0.0.0/8
|
|
||||||
ports:
|
|
||||||
- protocol: TCP
|
|
||||||
port: 443
|
|
||||||
- protocol: TCP
|
|
||||||
port: 80
|
|
||||||
- to:
|
|
||||||
- namespaceSelector: {}
|
|
||||||
ports:
|
|
||||||
- protocol: TCP
|
|
||||||
port: 53
|
|
||||||
- protocol: UDP
|
|
||||||
port: 53
|
|
||||||
NETPOL
|
|
||||||
echo " NetworkPolicy applied — Redis заблокирован"
|
|
||||||
sleep 5
|
|
||||||
|
|
||||||
R_CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 \
|
|
||||||
-H "Authorization: Bearer $ADMIN_TOKEN" "${BASE_URL}/admin/tenants" 2>/dev/null)
|
|
||||||
echo " HTTP при потере Redis: $R_CODE"
|
|
||||||
TOTAL=$((TOTAL + 1))
|
|
||||||
if [[ "$R_CODE" =~ ^[2345][0-9][0-9]$ ]]; then
|
|
||||||
PASS=$((PASS + 1)); echo " ✅ Сервис отвечает (HTTP $R_CODE)"
|
|
||||||
else
|
|
||||||
FAIL=$((FAIL + 1)); echo " ❌ Сервис не отвечает (HTTP $R_CODE)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
kubectl delete networkpolicy stress-test-block-redis -n "$NAMESPACE" 2>&1 | head -1
|
|
||||||
echo " NetworkPolicy удалена — Redis доступен"
|
|
||||||
sleep 5
|
|
||||||
|
|
||||||
R=$(sqs "$T1_AK" "$T1_SK" send-message --queue-url "$BURST_QURL" \
|
|
||||||
--message-body "after-redis-recovery-${TS}" 2>&1)
|
|
||||||
check "Сервис работает после Redis reconnect" "$R" "MessageId"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════
|
|
||||||
# 13. СМЕШАННАЯ НАГРУЗКА — send + receive + delete + getattr (20s)
|
|
||||||
# ═══════════════════════════════════════════════════════════
|
|
||||||
echo "═══ 13. Смешанная нагрузка: 10 senders + 10 receivers + 3 getattr × ${MIXED_DURATION}s ═══"
|
|
||||||
SEC13_START=$(date +%s)
|
|
||||||
|
|
||||||
MIXED_Q="stress-mixed-${TS}"
|
|
||||||
R=$(sqs "$T1_AK" "$T1_SK" create-queue --queue-name "$MIXED_Q")
|
|
||||||
MIXED_QURL=$(qurl "$T1_ID" "$MIXED_Q")
|
|
||||||
|
|
||||||
# Предзагрузка 100 msg
|
|
||||||
for m in $(seq 1 100); do
|
|
||||||
sqs "$T1_AK" "$T1_SK" send-message --queue-url "$MIXED_QURL" \
|
|
||||||
--message-body "mixed-init-${m}" >/dev/null 2>&1
|
|
||||||
if (( m % 20 == 0 )); then printf "[init:${m}] " >&2; fi
|
|
||||||
done
|
|
||||||
echo ""
|
|
||||||
echo " Начальная загрузка: 100 msg"
|
|
||||||
|
|
||||||
# 10 Отправителей
|
|
||||||
for w in $(seq 1 10); do
|
|
||||||
(
|
|
||||||
end=$((SECONDS + MIXED_DURATION)); ok=0
|
|
||||||
while [[ $SECONDS -lt $end ]]; do
|
|
||||||
R=$(sqs "$T1_AK" "$T1_SK" send-message --queue-url "$MIXED_QURL" \
|
|
||||||
--message-body "mixed-s${w}-$(date +%s%N)" 2>&1)
|
|
||||||
if echo "$R" | grep -q "MessageId"; then ok=$((ok + 1)); fi
|
|
||||||
done
|
|
||||||
echo "$ok" > "${TMPDIR_STRESS}/mixed_send_${w}.result"
|
|
||||||
) &
|
|
||||||
done
|
|
||||||
|
|
||||||
# 10 Читателей-удалителей
|
|
||||||
for w in $(seq 1 10); do
|
|
||||||
(
|
|
||||||
end=$((SECONDS + MIXED_DURATION)); recv=0
|
|
||||||
while [[ $SECONDS -lt $end ]]; do
|
|
||||||
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 "$T1_AK" "$T1_SK" delete-message --queue-url "$MIXED_QURL" \
|
|
||||||
--receipt-handle "$handle" >/dev/null 2>&1
|
|
||||||
recv=$((recv + 1))
|
|
||||||
done <<< "$HANDLES"
|
|
||||||
done
|
|
||||||
echo "$recv" > "${TMPDIR_STRESS}/mixed_recv_${w}.result"
|
|
||||||
) &
|
|
||||||
done
|
|
||||||
|
|
||||||
# 3 GetQueueAttributes
|
|
||||||
for w in $(seq 1 3); do
|
|
||||||
(
|
|
||||||
end=$((SECONDS + MIXED_DURATION)); ok=0; fail=0
|
|
||||||
while [[ $SECONDS -lt $end ]]; do
|
|
||||||
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)); else fail=$((fail + 1)); fi
|
|
||||||
sleep 0.5
|
|
||||||
done
|
|
||||||
echo "${ok}:${fail}" > "${TMPDIR_STRESS}/mixed_attr_${w}.result"
|
|
||||||
) &
|
|
||||||
done
|
|
||||||
|
|
||||||
# Прогресс каждые 5 сек
|
|
||||||
(
|
|
||||||
elapsed=0
|
|
||||||
while [[ $elapsed -lt $MIXED_DURATION ]]; do
|
|
||||||
sleep 5
|
|
||||||
elapsed=$((elapsed + 5))
|
|
||||||
printf "[mixed: ${elapsed}/${MIXED_DURATION}s] " >&2
|
|
||||||
done
|
|
||||||
) &
|
|
||||||
PROGRESS_PID=$!
|
|
||||||
|
|
||||||
echo " ⏳ Смешанная нагрузка ${MIXED_DURATION}s..."
|
|
||||||
wait
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
MIXED_SENT=0; MIXED_RECV=0; ATTR_OK=0; ATTR_FAIL=0
|
|
||||||
for w in $(seq 1 10); do
|
|
||||||
[[ -f "${TMPDIR_STRESS}/mixed_send_${w}.result" ]] && MIXED_SENT=$((MIXED_SENT + $(cat "${TMPDIR_STRESS}/mixed_send_${w}.result")))
|
|
||||||
[[ -f "${TMPDIR_STRESS}/mixed_recv_${w}.result" ]] && MIXED_RECV=$((MIXED_RECV + $(cat "${TMPDIR_STRESS}/mixed_recv_${w}.result")))
|
|
||||||
done
|
|
||||||
for w in $(seq 1 3); do
|
|
||||||
if [[ -f "${TMPDIR_STRESS}/mixed_attr_${w}.result" ]]; then
|
|
||||||
IFS=':' read -r ok fail < "${TMPDIR_STRESS}/mixed_attr_${w}.result"
|
|
||||||
ATTR_OK=$((ATTR_OK + ok)); ATTR_FAIL=$((ATTR_FAIL + fail))
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
SEC13_DUR=$(($(date +%s) - SEC13_START))
|
|
||||||
echo " Отправлено: $MIXED_SENT, прочитано+удалено: $MIXED_RECV | Attr: ok=$ATTR_OK fail=$ATTR_FAIL | ${SEC13_DUR}s"
|
|
||||||
check_ge "Mixed: отправлено > 0" "$MIXED_SENT" "1"
|
|
||||||
check_ge "Mixed: прочитано > 0" "$MIXED_RECV" "1"
|
|
||||||
check_eq "Mixed: GetQueueAttributes 0 сбоев" "$ATTR_FAIL" "0"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════
|
|
||||||
# 14. MEMORY CHECK — после нагрузки
|
|
||||||
# ═══════════════════════════════════════════════════════════
|
|
||||||
echo "═══ 14. Memory check — RSS после нагрузки ═══"
|
|
||||||
RSS_AFTER=$(get_pod_rss_mb)
|
|
||||||
echo " RSS до: ${RSS_BEFORE} MB, после: ${RSS_AFTER} MB"
|
|
||||||
if [[ "$RSS_BEFORE" -gt 0 && "$RSS_AFTER" -gt 0 ]]; then
|
|
||||||
RSS_GROWTH=$((RSS_AFTER - RSS_BEFORE))
|
|
||||||
echo " Рост: ${RSS_GROWTH} MB"
|
|
||||||
# Допускаем рост до 100 MB — выше уже подозрительно
|
|
||||||
check_le "Рост RSS <= 100MB" "$RSS_GROWTH" "100"
|
|
||||||
else
|
|
||||||
echo " ⚠️ Не удалось измерить RSS (возможно /proc недоступен)"
|
|
||||||
TOTAL=$((TOTAL + 1)); PASS=$((PASS + 1))
|
|
||||||
echo " ✅ Memory check пропущен (нет доступа к /proc)"
|
|
||||||
fi
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════
|
|
||||||
# 15. MULTI-KILL: kill → up → kill → up
|
|
||||||
# ═══════════════════════════════════════════════════════════
|
|
||||||
echo "═══ 15. Multi-kill: 3 рестарта подряд ═══"
|
|
||||||
SEC15_START=$(date +%s)
|
|
||||||
|
|
||||||
# Отправляем маркерное сообщение
|
|
||||||
MARKER_Q="stress-marker-${TS}"
|
|
||||||
R=$(sqs "$T1_AK" "$T1_SK" create-queue --queue-name "$MARKER_Q")
|
|
||||||
MARKER_QURL=$(qurl "$T1_ID" "$MARKER_Q")
|
|
||||||
sqs "$T1_AK" "$T1_SK" send-message --queue-url "$MARKER_QURL" \
|
|
||||||
--message-body "marker-survive-3-kills-${TS}" >/dev/null 2>&1
|
|
||||||
|
|
||||||
for k in 1 2 3; do
|
|
||||||
echo " --- Kill #${k} ---"
|
|
||||||
kill_pod
|
|
||||||
wait_for_pod_ready
|
|
||||||
wait_for_healthy
|
|
||||||
echo " Kill #${k}: под восстановлен ✓"
|
|
||||||
done
|
|
||||||
|
|
||||||
# Маркер выжил?
|
|
||||||
R=$(sqs "$T1_AK" "$T1_SK" receive-message --queue-url "$MARKER_QURL" \
|
|
||||||
--max-number-of-messages 1 --wait-time-seconds 2 2>&1)
|
|
||||||
SEC15_DUR=$(($(date +%s) - SEC15_START))
|
|
||||||
echo " 3 рестарта за ${SEC15_DUR}s"
|
|
||||||
check "Маркер выжил 3 рестарта" "$R" "marker-survive-3-kills"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════
|
|
||||||
# 16. CLEANUP
|
|
||||||
# ═══════════════════════════════════════════════════════════
|
|
||||||
echo "═══ 16. Cleanup ═══"
|
|
||||||
echo " Удаляю очереди..."
|
|
||||||
|
|
||||||
# Основные очереди
|
|
||||||
for i in $(seq 1 $TENANT_COUNT); do
|
|
||||||
tak=$(cat "${TMPDIR_STRESS}/tak_${i}" 2>/dev/null)
|
|
||||||
tsk=$(cat "${TMPDIR_STRESS}/tsk_${i}" 2>/dev/null)
|
|
||||||
tid=$(cat "${TMPDIR_STRESS}/tid_${i}" 2>/dev/null)
|
|
||||||
[[ -n "$tak" ]] && sqs "$tak" "$tsk" delete-queue --queue-url "${BASE_URL}/${tid}/${STRESS_Q}" >/dev/null 2>&1
|
|
||||||
done
|
|
||||||
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
|
|
||||||
sqs "$T1_AK" "$T1_SK" delete-queue --queue-url "$LONGPOLL_QURL" >/dev/null 2>&1
|
|
||||||
sqs "$T1_AK" "$T1_SK" delete-queue --queue-url "$RACE_QURL" >/dev/null 2>&1
|
|
||||||
sqs "$T1_AK" "$T1_SK" delete-queue --queue-url "$BATCH_QURL" >/dev/null 2>&1
|
|
||||||
sqs "$T1_AK" "$T1_SK" delete-queue --queue-url "$MARKER_QURL" >/dev/null 2>&1
|
|
||||||
|
|
||||||
# Flood-очереди
|
|
||||||
for q in $(seq 1 $QUEUE_FLOOD_COUNT); do
|
|
||||||
sqs "$T1_AK" "$T1_SK" delete-queue \
|
|
||||||
--queue-url "$(qurl "$T1_ID" "stress-flood-${q}-${TS}")" >/dev/null 2>&1 &
|
|
||||||
done
|
|
||||||
wait
|
|
||||||
|
|
||||||
echo " Очереди удалены"
|
|
||||||
|
|
||||||
TOTAL_DUR=$(($(date +%s) - STARTED_AT))
|
|
||||||
echo ""
|
|
||||||
echo "╔═══════════════════════════════════════════════════════════════════╗"
|
|
||||||
echo "║ ИТОГО: ${PASS}/${TOTAL} ✅ ${FAIL}/${TOTAL} ❌ ║"
|
|
||||||
echo "║ Время: ${TOTAL_DUR}s ║"
|
|
||||||
echo "╚═══════════════════════════════════════════════════════════════════╝"
|
|
||||||
|
|
||||||
if [[ $FAIL -gt 0 ]]; then exit 1; fi
|
|
||||||
Reference in New Issue
Block a user