Ускоряем Allure.TestOps

Смирнов Вячеслав

17 октября 2024

Image by Vlad Gerasimov on https://vlad.studio/

Ускоряю Miro

Пишу в qa_load

Любуюсь горами

Тысяча тестов автоматизирована, Allure.TestOps не тормозит на нагрузке более 100 000 результатов тестов в день

Тысяча тестов автоматизирована, Allure.TestOps не тормозит на нагрузке более 100 000 результатов тестов в день

🟣 Allure TestOps : Как устроена система?

🔵 Главная проблема горы тестов : Где взять диски?

🟢 Масштабирование сервисов и баз данных

🟡 Оптимизации настроек и SQL-запросов

🟠 Радикальные оптимизации : кеширование и удаление

🔴 Рекомендации и скрипты : Как автоматизировать?

🟣 Allure TestOps : Как устроена система?

🔵 Главная проблема горы тестов : Где взять диски?

🟢 Масштабирование сервисов и баз данных

🟡 Оптимизации настроек и SQL-запросов

🟠 Радикальные оптимизации : кеширование и удаление

🔴 Рекомендации и скрипты : Как автоматизировать?

🟣 Allure TestOps : Как устроена сиыстема?

🔵 Главная проблема горы тестов : Где взять диски?

🟢 Масштабирование сервисов и баз данных

🟡 Оптимизации настроек и SQL-запросов

🟠 Радикальные оптимизации : кеширование и удаление

🔴 Рекомендации и скрипты : Как автоматизировать?

🟣 Allure TestOps : Как устроена сиыстема?

🔵 Главная проблема горы тестов : Где взять диски?

🟢 Масштабирование сервисов и баз данных

🟡 Оптимизации настроек и SQL-запросов

🟠 Радикальные оптимизации : кеширование и удаление

🔴 Рекомендации и скрипты : Как автоматизировать?

☕️ Оптимизация вашей Allure TestOps

🟣 Allure TestOps

Как устроена система?

Allure Report

Allure TestOps

Потоки подключения к базам данных (JDBC, SQL) быстрее всех заканчиваются

Система масштабируемая

При масштабировании сервиса нужно обращать внимание и на количество подключений к хранилищу

В PostgreSQL есть несколько баз данных, база данных report наиболее нагруженная

В PostgreSQL есть несколько баз данных, база данных report наиболее нагруженная

🔵 Главная проблема горы тестов

Где взять диски?

Как удалить старые данные?

https://help.qameta.io > Knowledge base > General > Maintenance > Storage and database cleanup

Храним 2 недели данные со статусом Broken и Failed

Храним 1 неделю данные со статусом Passed и Skipped

Данные это

  • scenario

    • результаты выполнения шагов
  • fixture

    • результаты выполнения общих шагов
  • attachment

    • вложения, картинки

scenario:

  • Remove scenario on Broken test results after 336 hours
  • Remove scenario on Failed test results after 336 hours
  • Remove scenario on Passed test results after 168 hours
  • Remove scenario on Skipped test results after 168 hours

fixture:

  • Remove fixture on Broken test results after 336 hours
  • Remove fixture on Failed test results after 336 hours
  • Remove fixture on Passed test results after 168 hours
  • Remove fixture on Skipped test results after 168 hours

attachment:

  • Remove attachment on Broken test results after 336 hours
  • Remove attachment on Failed test results after 336 hours
  • Remove attachment on Passed test results after 168 hours
  • Remove attachment on Skipped test results after 168 hours

Delete (tool) > Vacuum

Выросло количество потоков:

rabbitConnectionFactorySharedExecutor -> taskExecutor

Утилизация CPU

на PostgreSQL = 100%

Delete (tool) > Vacuum

Главные проблемы горы тестов : Где взять диски?

И как не положить PostgreSQL при удалении данных?

А давайте все отмасшабируем

🟢 Масштабирование сервисов и баз данных

Как масштабировать?

Мы масштабировали report сервис.

Мы масштабировали report сервис, потоков стало кратно больше.

Мы масштабировали report сервис, потоков стало кратно больше, некоторых потоков слишком много

Мы масштабировали report сервис, потоков стало кратно больше, некоторых потоков слишком много, некоторых же недостаточно много

Отличаются на 200 (потребители и исполнители)

Отличаются на уже 2000 после масштабирования в 10 раз

Issue #102

Настройки по умолчанию заданы для 1-го сервиса

report:
  replicaCount: 1
  taskExecutorCorePoolSize: 200
  maxDBConn: 10
  maxConcurrency: 5
  maxS3Concurrency: 200

Если у нас больше реплик, то нужно пересчитать

report:
  replicaCount: 1
  taskExecutorCorePoolSize: 200

replicaCount больше, а taskExecutorCorePoolSize меньше

report:
  replicaCount: 10
  taskExecutorCorePoolSize: 20

И нужно больше JDBC подключений, значение по умолчанию 10 это очень мало

Пусть будет 100 JDBC-подключений

report:
  replicaCount: 10
  taskExecutorCorePoolSize: 20
  maxDBConn: 100

Мы масштабировали report сервис, потоков стало кратно больше, некоторых потоков слишком много, некоторых же недостаточно много

Мы масштабировали report сервис, потоков стало кратно больше, некоторых потоков слишком много, некоторых же недостаточно много

Мы масштабировали report сервис, потоков стало кратно больше, количество task-потоков снизили, а HicaryCP увеличили

🟠 Оптимизации

настроек и SQL-запросов

Мы масштабировали report сервис.

Мы масштабировали report сервис, активность работы с базами данных PostgreSQL выросла.

Мы масштабировали report сервис, активность работы с базами данных PostgreSQL выросла, производительность PostgreSQL конечна

Добавим ресурсов для PostgreSQL

Оптимизируем SQL-запросы к PostgreSQL

Оптимизируем SQL-запросы к PostgreSQL, возможности поменять текст запросов Allure.TestOps нет

Оптимизируем SQL-запросы к PostgreSQL, возможности поменять текст запросов Allure.TestOps нет, создадим индексы

Cоздадим индексы в PostgreSQL

А какие надо ли создавать индексы?

В Allure.TestOps уже есть индексы на все поля

Индекс

Но некоторых индексов не хватает

Несколько полей

В другом порядке

С фильтрацией по значению

browser == 'Chrome'

С фильтрацией по новым данным

id >= 9995

10 х 10 х 10

10 х 10 х 10

10 х 10 х 10

На какие запросы создавать индексы?

⚡️ Мгновенная cтатистика по PostgreSQL

доступная сразу на текущий момент времени или за всю историю

pg_stat_activity

текущие активные запросы

pg_stat_statement

успешно выполненные запросы

Логи

тексты запросов со всеми параметрами

🌟 Интервальная cтатистика по PostgreSQL

за выбранный период, ее надо накопить

pg_stat_activity

⚡️ данные за текущий момент

🌟 суммарные данные

pg_stat_statement

⚡️ суммарные данные за долгое время

🌟 дельта данных

логи PostgreSQL

⚡️ отсортированные по длительности

🌟 суммарные данные в отчете

pg_stat_activity

⚡️ данные за текущий момент

🌟 суммарные данные

pg_stat_statement

⚡️ суммарные данные за долгое время

🌟 дельта данных

логи PostgreSQL

⚡️ отсортированные по длительности

🌟 суммарные данные в отчете

Ложный TOP-1 запрос

SELECT $2 
FROM ONLY "public"."launch" x 
WHERE "id" OPERATOR(pg_catalog.=) $1 
FOR KEY SHARE OF x
update "test_result" 
set 
    "scenario_key" = $1, 
    "last_modified_by" = $2, 
    "last_modified_date" = $3 
where "test_result"."id" in ($4)
update "test_result" 
set 
    "scenario_key" = $1, 
    "last_modified_by" = $2, 
    "last_modified_date" = $3 
where "test_result"."id" in ($4, $5)

Очень много запросов-дубликатов

select ... from test_result where id in ($1)

select ... from test_result where id in ($1, $2)

select ... from test_result where id in ($1, $2, 3)

select ... from test_result where id in ($1, $2, $3, $4)

select ... from test_result where id in ($1, $2, $3, $4, $5)

select ... from test_result where id in ($1, $2, $3, $4, $5, $6)

...

Можно схлопнуть дубликаты за счет регулярных выр

select query_key, query, queryid, 
    sum(total_exec_time) as time_sum
from (select
  regexp_replace(regexp_replace(regexp_replace(regexp_replace(
	regexp_replace(query, 
	' in [(][^)]+[)]', ' IN KEYS', 'g'),
	'[(]values [(].+[))]', '(VALUES (VAL))', 'g'),
	' values [(].+[)]', ' VALUES (VAL)', 'g'),
	'([0-9]+)', '1', 'g'),
	'(\n|\t|\s|\r|\v|\a|\b| )+', ' ', 'g')
	as "query_key", -- Template
	total_exec_time, query, queryid
  from pg_stat_statements
) foo
group by query_key, query, queryid
order by total_exec_time_sum desc, query_key, query

Можно схлопнуть дубликаты за счет регулярных выражений

select query_key, query, queryid, 
    sum(total_exec_time) as time_sum
from (select
  regexp_replace(regexp_replace(regexp_replace(regexp_replace(
	regexp_replace(query, 
	' in [(][^)]+[)]', ' IN KEYS', 'g'),
	'[(]values [(].+[))]', '(VALUES (VAL))', 'g'),
	' values [(].+[)]', ' VALUES (VAL)', 'g'),
	'([0-9]+)', '1', 'g'),
	'(\n|\t|\s|\r|\v|\a|\b| )+', ' ', 'g')
	as "query_key", -- Template
	total_exec_time, query, queryid
  from pg_stat_statements
) foo
group by query_key, query, queryid
order by total_exec_time_sum desc, query_key, query

Можно посчитать изменение метрики

Создать слепок:

	select * 
	into pg_stat_statements_2024_10_10
	from pg_stat_statements

Подождать и получить разницу

select "query_key", count(queryid) as "count", max(query) as "query",
	round(sum(total_exec_time)) as total_exec_time, sum(calls) as calls, 
	sum(total_exec_time)/sum(calls) as "mean",
	round(sum(shared_blks_hit)/sum(calls)) as "shared_blks_hit"
from ( select 
		regexp_replace(regexp_replace(regexp_replace(regexp_replace(regexp_replace(p.query,       
		' in [(][^)]+?[)]', ' IN KEYS', 'g'),
		'\(values \(.+?\)\)', '(VALUES (VAL))', 'g'),
		' values \(.+?\)', ' VALUES (VAL)', 'g'),
		'([0-9]+)', '1', 'g'),
		'(\n|\t|\s|\r|\v|\a|\b| )+', ' ', 'g') as "query_key",
		p.queryid, p.query,
		p.total_exec_time - prev.total_exec_time as total_exec_time,
		p.shared_blks_hit - prev.shared_blks_hit as shared_blks_hit,
		p.calls - prev.calls as calls,
		(p.total_exec_time - prev.total_exec_time) / (p.calls - prev.calls) as mean
	from  pg_stat_statements p join pg_stat_statements_2024_10_10 prev
		on (p.queryid = prev.queryid)
	where p.calls > prev.calls
) as stat
group by "query_key" order by total_exec_time desc

shared_blk_hit

pg_stat_activity

⚡️ данные за текущий момент

🌟 суммарные данные в VictoriaMetrics

pg_stat_statement

⚡️ суммарные данные за долгое время

🌟 дельта данных

логи PostgreSQL

⚡️ отсортированные по длительности

🌟 суммарные данные в отчете

TOP-20 запросов

50 partial-индексов

Среднее ускорение x2

🔴 Радикальные оптимизации

кеширование и удаление

Есть очень медленные HTTP запросы на странице проекта /project/{ID}/dashboards

/api/rs/analytic/{ID}/statistic_trend /api/rs/analytic/{ID}/automation_chart

180 секунд на GET-запрос

180 секунд будут заняты 2 JDBC-подключения к реплике базы данных, на каждого пользователя, открывшего проект

Индексами проблему не решить (пробовал)

Можно отключить GET-запросы вообще

Можно отключить GET-запросы вообще: /api/rs/analytic/3/statistic_trend и /api/rs/analytic/3/automation_chart

Используя NGinx мы отключим или закешируем два аналитических GET-запроса для самого большого проекта (№ 3)

Закешируем

http {
    proxy_cache_path /data/nginx/cache keys_zone=mycache:60m max_size=1g inactive=120m;
    limit_conn_zone $server_name zone=perserver:10m;
    upstream backend {server allure:443}
    server {
        listen 80;

        location /api/rs/analytic/3/statistic_trend {
            include shared.cache.conf;
        }
        location /api/rs/analytic/3/automation_chart {
            include shared.cache.conf;
        }
        location / {
            proxy_pass https://backend;
        }
    }
}

Закешируем

http {
    proxy_cache_path /data/nginx/cache keys_zone=mycache:60m max_size=1g inactive=120m;
    limit_conn_zone $server_name zone=perserver:10m;
    upstream backend {server allure:443}
    server {
        listen 80;
        location /api/rs/analytic/3/statistic_trend {
            include shared.cache.conf;
        }
        location /api/rs/analytic/3/automation_chart {
            include shared.cache.conf;
        }
        location / {
            proxy_pass https://backend;
        }
    }
}

Содержимое файле shared.cache.conf

# кешировать по URL-запроса
proxy_cache_key "$host$request_uri";

# на 60 минут для успешных ответов
proxy_cache_valid 200 302 60m;

proxy_cache_valid 404      1m;
proxy_cache_min_uses 1;
proxy_cache_methods GET;
proxy_cache_background_update on;
proxy_cache_revalidate on;
proxy_cache_lock on;
add_header X-Cache-Status $upstream_cache_status;
proxy_ignore_headers Cache-Control;
proxy_pass https://backend;

Кешировать по URL-запроса

# кешировать по URL-запроса
proxy_cache_key "$host$request_uri";
# на 60 минут для успешных ответов
proxy_cache_valid 200 302 60m;

proxy_cache_valid 404      1m;
proxy_cache_methods GET;
proxy_cache_background_update on;
proxy_cache_revalidate on;
proxy_cache_lock on;
add_header X-Cache-Status $upstream_cache_status;
proxy_ignore_headers Cache-Control;
proxy_pass https://backend;

Кешировать на 60 минут для успешных ответов с кодами 200 или 302

# кешировать по URL-запроса
proxy_cache_key "$host$request_uri";

# на 60 минут для успешных ответов
proxy_cache_valid 200 302 60m;

proxy_cache_valid 404      1m;
proxy_cache_min_uses 1;
proxy_cache_methods GET;
proxy_cache_background_update on;
proxy_cache_revalidate on;
proxy_cache_lock on;
add_header X-Cache-Status $upstream_cache_status;
proxy_ignore_headers Cache-Control;
proxy_pass https://backend;

🟣 Рекомендации и скрипты

Как автоматизировать оптимизации?

github.com/polarnik/ Allure.TestOps.SpeedUp

Доски мониторинга, примеры индексов и скрипт для создания индексов на основе ваших данных

Доски мониторинга, примеры индексов и скрипт для создания индексов на основе ваших данных

Доски мониторинга, примеры индексов и скрипт для создания индексов на основе ваших данных

github.com/polarnik/ Allure.TestOps.SpeedUp

3 месяца работы сжатые в день: мониторинг, оптимизация настроек, добавление индексов

Ваши вопросы

Репозиторий: github.com/polarnik/ Allure.TestOps.SpeedUp, qa_load

Images from vlad.studio. Slides from https://polarnik.github.io/Allure.TestOps.SpeedUp/slides.html