diff --git a/doc/api/benchmark-comparison-2026-04-12.md b/doc/api/benchmark-comparison-2026-04-12.md new file mode 100644 index 0000000..03a4470 --- /dev/null +++ b/doc/api/benchmark-comparison-2026-04-12.md @@ -0,0 +1,104 @@ +# Сравнительный benchmark shared-sqs vs Yandex MQ до 32KB + +**Дата:** 2026-04-12 05:45 UTC + +## Область сравнения + +Этот отчёт специально ограничен сообщениями до 32KB. + +- Сценарии `64KB+` исключены из сравнения по решению пользователя. +- Причина исключения: для `64KB+` уже подтверждено отдельное platform-level ограничение ingress path, а не дефект shared-sqs. +- Цель этого отчёта: показать, как shared-sqs ведёт себя на практически приемлемом диапазоне размеров в текущей инфраструктуре. + +## Методика + +- Запуск выполнялся с удалённой ВМ `5.172.178.213` в каталоге `~/terra/SQS-service`. +- Для сравнения использовался один и тот же AWS CLI клиент. +- Базовый прогон: [tests/benchmark_compare_32k.sh](/home/naeel/remote_dev/SQS-service/tests/benchmark_compare_32k.sh). +- Уточняющий прогон для `SendMessage 10KB` и `SendMessage 32KB`: [tests/payload_latency_probe.sh](/home/naeel/remote_dev/SQS-service/tests/payload_latency_probe.sh). +- Для большинства операций использовано `7` итераций. +- Для throughput использовалось `5` воркеров по `10` сообщений `1KB`. +- Для `PurgeQueue` зафиксирован одиночный контрольный замер, потому что повторный вызов упирается в стандартный cooldown `60s`. + +## Покрытие API + +Сравнение включало операции, которые есть у обоих сервисов: + +- `GetQueueUrl` +- `ListQueues` +- `GetQueueAttributes` +- `SetQueueAttributes` +- `SendMessage` +- `SendMessageBatch` +- `ReceiveMessage` +- `DeleteMessage` +- `DeleteMessageBatch` +- `ChangeMessageVisibility` +- `ChangeMessageVisibilityBatch` +- `PurgeQueue` + +Операции, которые есть в shared-sqs, но не участвуют в прямом сравнении с Yandex MQ: + +- `TagQueue` +- `UntagQueue` +- `ListQueueTags` + +## Итоги по latency + +Формат значений: `min / avg / max / p95`, миллисекунды. + +| Операция | Yandex MQ | shared-sqs | Вывод | +|---|---:|---:|---| +| GetQueueUrl | 766 / 1346 / 2266 / 2060 | 739 / 752 / 770 / 760 | shared-sqs заметно стабильнее | +| ListQueues | 765 / 786 / 817 / 811 | 740 / 756 / 775 / 762 | shared-sqs быстрее | +| GetQueueAttributes | 804 / 820 / 847 / 828 | 738 / 752 / 768 / 765 | shared-sqs быстрее | +| SetQueueAttributes | 805 / 816 / 835 / 835 | 750 / 765 / 803 / 774 | shared-sqs быстрее | +| SendMessage 1KB | 804 / 811 / 824 / 823 | 748 / 760 / 770 / 767 | shared-sqs быстрее | +| SendMessage 10KB | 916 / 967 / 996 / 987 | 880 / 913 / 965 / 951 | shared-sqs быстрее | +| SendMessage 32KB | 905 / 930 / 948 / 947 | 883 / 904 / 939 / 928 | shared-sqs быстрее | +| SendMessageBatch 10 | 784 / 803 / 827 / 820 | 718 / 746 / 782 / 759 | shared-sqs быстрее | +| ReceiveMessage | 782 / 826 / 869 / 864 | 738 / 748 / 777 / 751 | shared-sqs быстрее | +| DeleteMessage | 783 / 800 / 823 / 814 | 727 / 742 / 757 / 755 | shared-sqs быстрее | +| DeleteMessageBatch 10 | 818 / 845 / 872 / 864 | 742 / 783 / 821 / 810 | shared-sqs быстрее | +| ChangeMessageVisibility | 810 / 842 / 894 / 854 | 778 / 805 / 814 / 814 | shared-sqs быстрее | +| ChangeMessageVisibilityBatch 10 | 806 / 829 / 848 / 841 | 803 / 824 / 838 / 836 | почти паритет, но shared-sqs чуть быстрее | +| PurgeQueue | 867 | 801 | shared-sqs быстрее, но это одиночный контрольный замер | + +## Throughput + +Тест: `SendMessage 1KB`, `5` воркеров по `10` сообщений. + +| Провайдер | Успешно | Общее время | Пропускная способность | +|---|---:|---:|---:| +| Yandex MQ | 50 / 50 | 9116 ms | ~5 msg/s | +| shared-sqs | 50 / 50 | 8580 ms | ~5 msg/s | + +Вывод по throughput: + +- В этом сценарии наблюдается паритет по грубому `msg/s`. +- shared-sqs завершает тот же объём немного быстрее по wall-clock time. +- Ограничение здесь задаётся в первую очередь AWS CLI, а не серверной частью обоих сервисов. + +## Основные выводы + +1. В диапазоне до `32KB` shared-sqs не уступает Yandex MQ ни по одной из измеренных общих операций. +2. На `SendMessage` с payload `10KB` и `32KB` shared-sqs в текущем прогоне стабильно быстрее Yandex MQ. +3. На control-plane вызовах `GetQueueUrl`, `ListQueues`, `GetQueueAttributes`, `SetQueueAttributes` shared-sqs показывает более низкий средний latency. +4. На batch-операциях shared-sqs также быстрее, но разница уже не драматическая. +5. На текущем practical диапазоне `<=32KB` нет оснований вводить code-level лимит ниже `32KB`. + +## Важное примечание по качеству измерений + +- В первом длинном прогоне [tests/benchmark_compare_32k.sh](/home/naeel/remote_dev/SQS-service/tests/benchmark_compare_32k.sh) для `SendMessage 10KB` и `SendMessage 32KB` у shared-sqs были получены артефактные нули. +- Повторная точечная проверка показала, что это был дефект benchmark harness, а не отказ сервиса. +- Для этих двух строк в таблице используются результаты повторного узкого прогона из [tests/payload_latency_probe.sh](/home/naeel/remote_dev/SQS-service/tests/payload_latency_probe.sh). + +## Что сознательно не включено + +- `64KB+` payload. +- Кросс-кластерные transport-level расследования, уже вынесенные в отдельные документы по ingress. +- Прямое сравнение `TagQueue`, `UntagQueue`, `ListQueueTags`, потому что Yandex MQ в текущем сравнении их не даёт как симметричный baseline. + +## Финальный практический вывод + +Если смотреть только на рабочую область до `32KB`, shared-sqs уже выглядит конкурентоспособно относительно managed Yandex MQ: сервис стабильно проходит базовые и batch-операции, не проигрывает по latency и в большинстве измеренных точек оказывается быстрее. С инженерной точки зрения это достаточное подтверждение, что текущий bottleneck shared-sqs находится не в базовом data plane для малых и средних сообщений. \ No newline at end of file diff --git a/doc/progress.md b/doc/progress.md index 30432e9..a2b3b08 100644 --- a/doc/progress.md +++ b/doc/progress.md @@ -15,10 +15,12 @@ | Размер | shared-sqs | Yandex MQ | Сравнение | |--------|-----------|-----------|-----------| | 1KB | ~800ms | ~700ms | Паритет | -| 10KB | ~800ms | ~700ms | Паритет | -| 32KB | **805-917ms** | 869-3490ms | **Быстрее** (нет cold start) | +| 10KB | **880-965ms** | 916-996ms | **Быстрее** | +| 32KB | **883-939ms** | 905-948ms | **Быстрее** | | 64KB+ | ❌ 10-52s зависание | ~900ms | Проблема nginx+botocore | +- Детальный сравнительный отчёт по API операциям до 32KB: [doc/api/benchmark-comparison-2026-04-12.md](/home/naeel/remote_dev/SQS-service/doc/api/benchmark-comparison-2026-04-12.md) + ### Проблема 65KB+ payload (расследование 2026-04-11) **Root cause:** botocore (AWS SDK) + urllib3 2.0 + TLS record boundary. diff --git a/doc/thinking/2026-04-12.md b/doc/thinking/2026-04-12.md index 2b9938b..9745e71 100644 --- a/doc/thinking/2026-04-12.md +++ b/doc/thinking/2026-04-12.md @@ -285,4 +285,32 @@ 3. Что реально сгенерировано в `/etc/nginx/nginx.conf` у ingress controller. 4. Не изменились ли версия штурвала и версия ingress-nginx controller. 5. Не включили ли на platform уровне `allow-snippet-annotations`. -6. Не появился ли доступ к source-of-truth конфигурации платформенного ingress controller. \ No newline at end of file +6. Не появился ли доступ к source-of-truth конфигурации платформенного ingress controller. + +--- + +## Задача: собрать отдельный сравнительный benchmark-отчёт только до 32KB + +### Контекст +После завершения расследования по 64KB+ пользователь явно зафиксировал новую рамку: в сравнительном отчёте не трогать `64KB` и выше, а ограничиться practically useful диапазоном до `32KB`. + +### Что сделал +1. Выделил отдельный benchmark-сценарий `tests/benchmark_compare_32k.sh`, чтобы не смешивать его с прежними широкими сценариями. +2. Запустил прогон по общим операциям API и получил полноценную таблицу latency для control-plane и data-plane вызовов. +3. Нашёл, что секция `PurgeQueue` искусственно раздувает время всего прогона, потому что повторный purge требует cooldown `60s` по самому контракту API. Убрал многократные sleep и оставил одиночный контрольный замер. +4. Нашёл ещё один дефект уже в самом benchmark harness: в общем длинном прогоне для `SendMessage 10KB` и `SendMessage 32KB` у shared-sqs появились артефактные нули, хотя отдельная точечная проверка `5/5` показала, что обе операции реально проходят стабильно. +5. Чтобы не оставлять сомнительные данные, вынес для этих размеров отдельный узкий probe `tests/payload_latency_probe.sh` и снял повторные latency-цифры отдельно. +6. На основе общего прогона и узкого probe собрал отдельный документ `doc/api/benchmark-comparison-2026-04-12.md`. + +### Что подтвердилось +- До `32KB` shared-sqs не проиграл Yandex MQ ни по одной из общих измеренных операций. +- На `SendMessage 10KB` и `SendMessage 32KB` shared-sqs в повторном узком прогоне получился немного быстрее Yandex MQ. +- На control-plane вызовах `GetQueueUrl`, `ListQueues`, `GetQueueAttributes`, `SetQueueAttributes` shared-sqs выглядит стабильно сильнее в текущей конфигурации. +- Throughput в тесте через AWS CLI фактически ограничивается самим клиентом, поэтому там паритет по грубому `msg/s` и небольшой выигрыш shared-sqs по общему времени. + +### Почему это важно +Этот отчёт теперь отделяет две разные темы, которые раньше легко спутать: +- вопрос прикладной конкурентоспособности shared-sqs в practically useful диапазоне до `32KB`; +- отдельную transport/platform проблему `64KB+`, уже локализованную на ingress path. + +Именно такое разделение и нужно, чтобы дальше не смешивать хорошие рабочие метрики сервиса с чужим инфраструктурным ограничением. \ No newline at end of file diff --git a/tests/benchmark_compare_32k.sh b/tests/benchmark_compare_32k.sh new file mode 100755 index 0000000..0f3a234 --- /dev/null +++ b/tests/benchmark_compare_32k.sh @@ -0,0 +1,380 @@ +#!/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." \ No newline at end of file diff --git a/tests/payload_latency_probe.sh b/tests/payload_latency_probe.sh new file mode 100755 index 0000000..0a2638b --- /dev/null +++ b/tests/payload_latency_probe.sh @@ -0,0 +1,104 @@ +#!/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}' \ No newline at end of file