tests: сравнение shared-SQS vs YMQ — p50 6-8мс vs 60-62мс; у shared выброс send 12-25с на прогон (платформа?)
This commit is contained in:
@@ -910,3 +910,25 @@ P3.8 — PASS; P3.7 (RSS 2ч) — мониторинг в процессе.
|
|||||||
стабилизации GC). Плато 15648 kB держится последние сэмплы.
|
стабилизации GC). Плато 15648 kB держится последние сэмплы.
|
||||||
- Threads: стабильно **13** на всём интервале.
|
- Threads: стабильно **13** на всём интервале.
|
||||||
- Утечки памяти/потоков НЕТ. План Соннета выполнен полностью.
|
- Утечки памяти/потоков НЕТ. План Соннета выполнен полностью.
|
||||||
|
|
||||||
|
## 2026-08-15 (день) — сравнение shared-SQS vs Yandex YMQ (очередь newsqs)
|
||||||
|
|
||||||
|
**Скрипт**: `tests/compare_ymq_vs_shared.py` (новый, локально + ВМ). N=50,
|
||||||
|
тело 512 байт, последовательно: send x50 → receive+delete x50. retries=0.
|
||||||
|
|
||||||
|
**Прогон 1** (ВМ):
|
||||||
|
- shared-SQS: send p50=6мс p95=17мс **max=12648мс**; receive p50=7мс; delete p50=8мс; 50/50, errs=0, total=13.9с.
|
||||||
|
- YMQ: send p50=62мс p95=76мс max=84мс; receive p50=60мс; delete p50=62мс; 50/50, errs=0, total=9.4с.
|
||||||
|
|
||||||
|
**Прогон 2** (ВМ, повтор):
|
||||||
|
- shared-SQS: send p50=8мс p95=16мс **max=25446мс**; receive p50=6мс; delete p50=7мс; 50/50, errs=0, total=26.6с.
|
||||||
|
- YMQ: send p50=62мс p95=69мс max=72мс; receive p50=60мс; delete p50=62мс; 50/50, errs=0, total=9.5с.
|
||||||
|
|
||||||
|
**Выводы (факты)**:
|
||||||
|
1. По p50 наш сервис в ~8–10 раз быстрее YMQ (6–8мс против 60–62мс).
|
||||||
|
2. У shared-SQS в КАЖДОМ прогоне ровно один send зависает на 12.6с / 25.4с
|
||||||
|
(тело 512 байт — размер ни при чём; errs=0, ответ приходит). YMQ — без
|
||||||
|
выбросов, стабилен.
|
||||||
|
3. Гипотеза выброса: платформенные «паузы» сети к поду Nubes (ранее фиксировали
|
||||||
|
зависания ~51с на больших POST из-за MTU/MSS 1448) — но при 512 байтах
|
||||||
|
причина требует отдельного расследования (tcpdump/тайминги платформы).
|
||||||
|
|||||||
@@ -0,0 +1,153 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Сравнение shared-SQS vs Yandex YMQ (очередь newsqs).
|
||||||
|
|
||||||
|
Короткий замер: последовательные операции, N send → N receive+delete.
|
||||||
|
Метрики: p50/p95/max латентности send/receive/delete, число ошибок.
|
||||||
|
|
||||||
|
Требуемые переменные окружения:
|
||||||
|
SHARED_AK, SHARED_SK — креды shared-SQS
|
||||||
|
YMQ_AK, YMQ_SK — креды Yandex YMQ (SA newsqs)
|
||||||
|
YMQ_QUEUE_URL — URL очереди Яндекса (newsqs)
|
||||||
|
|
||||||
|
Запуск: python3 tests/compare_ymq_vs_shared.py [N]
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
import boto3
|
||||||
|
from botocore.config import Config
|
||||||
|
|
||||||
|
N = int(sys.argv[1]) if len(sys.argv) > 1 else 50
|
||||||
|
BODY = "x" * 512
|
||||||
|
|
||||||
|
SHARED_EP = "https://sqs.containerk8s.dev.nubes.ru"
|
||||||
|
YMQ_EP = "https://message-queue.api.cloud.yandex.net"
|
||||||
|
|
||||||
|
SHARED_AK = os.environ["SHARED_AK"]
|
||||||
|
SHARED_SK = os.environ["SHARED_SK"]
|
||||||
|
YMQ_AK = os.environ["YMQ_AK"]
|
||||||
|
YMQ_SK = os.environ["YMQ_SK"]
|
||||||
|
YMQ_QUEUE_URL = os.environ["YMQ_QUEUE_URL"]
|
||||||
|
|
||||||
|
CFG = Config(connect_timeout=15, read_timeout=30, retries={"max_attempts": 0})
|
||||||
|
|
||||||
|
|
||||||
|
def mk_client(endpoint, region, ak, sk):
|
||||||
|
return boto3.client(
|
||||||
|
"sqs",
|
||||||
|
endpoint_url=endpoint,
|
||||||
|
region_name=region,
|
||||||
|
aws_access_key_id=ak,
|
||||||
|
aws_secret_access_key=sk,
|
||||||
|
config=CFG,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def stats(name, values):
|
||||||
|
v = sorted(values)
|
||||||
|
if not v:
|
||||||
|
print("%s: n=0" % name)
|
||||||
|
return
|
||||||
|
n = len(v)
|
||||||
|
print("%s: n=%d p50=%.0fms p95=%.0fms max=%.0fms" %
|
||||||
|
(name, n, v[n // 2] * 1000, v[int(n * .95)] * 1000, v[-1] * 1000))
|
||||||
|
|
||||||
|
|
||||||
|
def bench(client, queue_url, label):
|
||||||
|
errs = 0
|
||||||
|
lat_send, lat_recv, lat_del = [], [], []
|
||||||
|
t_start = time.time()
|
||||||
|
|
||||||
|
for i in range(N):
|
||||||
|
t0 = time.time()
|
||||||
|
try:
|
||||||
|
client.send_message(QueueUrl=queue_url, MessageBody=BODY)
|
||||||
|
lat_send.append(time.time() - t0)
|
||||||
|
except Exception as e:
|
||||||
|
errs += 1
|
||||||
|
if errs <= 3:
|
||||||
|
print(" send err: %s %s" % (type(e).__name__, str(e)[:80]))
|
||||||
|
|
||||||
|
got = 0
|
||||||
|
tries = 0
|
||||||
|
while got < N and tries < N * 6:
|
||||||
|
tries += 1
|
||||||
|
t0 = time.time()
|
||||||
|
try:
|
||||||
|
r = client.receive_message(
|
||||||
|
QueueUrl=queue_url, MaxNumberOfMessages=1, VisibilityTimeout=30)
|
||||||
|
lat_recv.append(time.time() - t0)
|
||||||
|
msgs = r.get("Messages", [])
|
||||||
|
except Exception as e:
|
||||||
|
errs += 1
|
||||||
|
if errs <= 3:
|
||||||
|
print(" recv err: %s %s" % (type(e).__name__, str(e)[:80]))
|
||||||
|
continue
|
||||||
|
for m in msgs:
|
||||||
|
got += 1
|
||||||
|
t0 = time.time()
|
||||||
|
try:
|
||||||
|
client.delete_message(QueueUrl=queue_url, ReceiptHandle=m["ReceiptHandle"])
|
||||||
|
lat_del.append(time.time() - t0)
|
||||||
|
except Exception as e:
|
||||||
|
errs += 1
|
||||||
|
if errs <= 3:
|
||||||
|
print(" del err: %s %s" % (type(e).__name__, str(e)[:80]))
|
||||||
|
|
||||||
|
total = time.time() - t_start
|
||||||
|
print("== %s (N=%d) ==" % (label, N))
|
||||||
|
stats(" send ", lat_send)
|
||||||
|
stats(" receive", lat_recv)
|
||||||
|
stats(" delete ", lat_del)
|
||||||
|
print(" received=%d/%d errors=%d total=%.1fs" % (got, N, errs, total))
|
||||||
|
print(" throughput: send=%.1f op/s, recv+del=%.1f op/s" %
|
||||||
|
(N / total if total else 0, got / total if total else 0))
|
||||||
|
return errs
|
||||||
|
|
||||||
|
|
||||||
|
def drain(client, queue_url):
|
||||||
|
while True:
|
||||||
|
r = client.receive_message(QueueUrl=queue_url, MaxNumberOfMessages=10)
|
||||||
|
msgs = r.get("Messages", [])
|
||||||
|
if not msgs:
|
||||||
|
break
|
||||||
|
for m in msgs:
|
||||||
|
try:
|
||||||
|
client.delete_message(QueueUrl=queue_url, ReceiptHandle=m["ReceiptHandle"])
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
shared = mk_client(SHARED_EP, "us-east-1", SHARED_AK, SHARED_SK)
|
||||||
|
ymq = mk_client(YMQ_EP, "ru-central1", YMQ_AK, YMQ_SK)
|
||||||
|
|
||||||
|
# Временная очередь shared-SQS
|
||||||
|
qname = "cmp-%s" % uuid.uuid4().hex[:8]
|
||||||
|
shared_url = shared.create_queue(QueueName=qname)["QueueUrl"]
|
||||||
|
print("shared queue: %s" % shared_url)
|
||||||
|
|
||||||
|
# YMQ newsqs: перед тестом чистим
|
||||||
|
drain(ymq, YMQ_QUEUE_URL)
|
||||||
|
print("ymq queue: %s (drained)" % YMQ_QUEUE_URL)
|
||||||
|
|
||||||
|
print()
|
||||||
|
e1 = bench(shared, shared_url, "shared-SQS (наш сервис)")
|
||||||
|
print()
|
||||||
|
e2 = bench(ymq, YMQ_QUEUE_URL, "Yandex YMQ (newsqs)")
|
||||||
|
|
||||||
|
# Очистка
|
||||||
|
drain(ymq, YMQ_QUEUE_URL)
|
||||||
|
try:
|
||||||
|
shared.delete_queue(QueueUrl=shared_url)
|
||||||
|
print("\ncleanup: shared temp queue deleted, ymq drained")
|
||||||
|
except Exception as ex:
|
||||||
|
print("\ncleanup err: %s" % str(ex)[:80])
|
||||||
|
|
||||||
|
print("\nRESULT: errors shared=%d ymq=%d" % (e1, e2))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user