Профиль ЦФТ или я его слепила из того, что было

Привет, Хабр! Меня зовут Людмила, я технический лидер команды тестирования производительности в ОТП Банке. Направление моей работы — это организация тестирования производительности разных наших систем, одна из которых — АБС ЦФТ, являющаяся нашей core-системой. В этой статье расскажу, как мы собирали профиль нагрузочного тестирования для нашей ЦФТ-системы. Казалось бы, подход для всех один, но есть нюансы.

Как собрать профиль тестирования для микросервисной архитектуры примерно понятно — по логам балансировщика, существующему мониторингу сервисов (jvm throughput) и тд. А как быть, если система достаточно сложная и коробочная… Расскажу, как мы собирали по крупицам профиль тестирования для ЦФТ. Статья будет полезна нагрузочным тестировщикам, перед которыми стоит задача протестировать их систему, командам, которые хотят улучшить производительность своей системы ЦФТ, но не знают с чего начать. Также набросала верхоуровневые подсказки, которые можно применить для любой системы, по которой вы хотите собрать профиль тестирования. Надеюсь, эта статья будет для вас интересна.

Архитектура нашей сиситемы

PS: Ищите женщину… Кто она наша система? из чего состоит?
PS:  Ищите женщину… Кто она наша система?  из чего состоит?

Итого в нашей системе:

  • 2 сервера базы данных (БД) — active/passive;

  • Адаптеры для интеграции с другими системами через RMQ, kafka, IBM MQ;

  • Сервера приложений, на которых работают некоторые фоновые процессы;

  • Сервера для отчетов;

  • Сервера приложений, на которых выполняются процедуры по закрытию операционного дня;

  • Сервера приложений, которые участвуют в работе пользователей;

  • Сервера 2mca, при помощи которых смежные системы могу вызвать наши api.

После того, как мы определили текущую архитектуру системы, мы приступили к анализу текущей утилизации ресурсов наших серверов БД, СП, прокси-серверов.

370c16c6f206efe1d72252b137f3d733.png

Предположите где узкое горлышко.

В нашей системе основная нагрузка приходится на БД, на серверах приложений работает ограниченное количество фоновых процессов и нагрузка на них невысокая. Поэтому основной фокус у нас будет на БД. Наша система ЦФТ интегрирована с другими системами банка и кажется очевидным составить профиль на основе интеграционных потоков. Однако не все так очевидно…

be339a9e358fc12be86e4a4dcc01f6a3.png

Для начала рассмотрим внешние взаимодействия. Нагрузку на систему можно классифицировать по следующим типам.

Внешние взаимодействия

Установите все взаимодействия.

Взаимодействие через шину, например, через IBM MQ → AQ очередь на БД (если используется классический mgw agent) или может реализовано что-то свое Rabbit, Kafka и тд. В этой части мы отказались от обычного mgw agent из MQ в Aq, и разработали свой адаптер. Наш адаптер масштабируемый и поддерживает разные протоколы взаимодействия (Rabbit. Kafka, IBM MQ).

  1. Вызов API. Если вы не только используете основной модуль БД, но есть еще web-proxy, соответственно, взаимодействие может также быть через https.

  2. Файловое хранилище tfs. Фоновые процессы могут вычитывать файлы из определенной папки и определенного типа (откуда читать указано в настройке) — зависит от реализации решения. 

  3. Пользовательская активность.

 Есть еще и фоновые процессы. Но давайте по порядку.

Интеграция

Рассмотрим первый тип взаимодействия — асинхронный. Его используют чаще всего.

В ЦФТ есть 4 служебные полезные таблицы: z#cit_abonent, z#cit_bo, z#cit_in_request, z#cit_out_request.

Для каждого взаимодействия с другой системой в ЦФТ создается свой абонент — запись в справочнике z#cit_abonent. Каждый абонент поднимает свои обработчики.

Если инициатором запроса была внешняя система, то запись об этом создается в z#cit_in_request. Будем называть это взаимодействие «Входящим интеграционным потоком».

А если ЦФТ сама была инициатором запроса во внешнюю систему, то запись об этом логируется в z#cit_out_request. Это взаимодействие будем называть «Обратным потоком».

Проанализировать откуда мы можем собрать статистику.

Входящий поток выглядит примерно так:

02d76e6ef0bbae08d2ed1faa02709e83.png

Обратный поток примерно выглядит так:

ba4fa52040d9d8d8211a66ac1ec98453.png

При вызове апи web proxy (2mca) все гораздо проще, там только используется таблица Z#WS_LOG.

Собираем дневную статистику

За какие дни и часы надо собирать статистику?

Собирать подробную статистику за длительный период времени достаточно трудоемкий. В каждой системе в зависимости от ее назначения есть свои высоконагруженные дни, когда нагрузка больше, чем в среднестатистический день. 

У каждой системы это свои дни.

Мы с ребятами составили следующий список высоконагруженных дней:

  1. Зарплатные дни

  2. Праздники

  3. Предпраздники

  4. После праздников

  5. Первый  рабочий день месяца

  6. Последний рабочий день месяца

Не устали? надеюсь, что нет)) теперь мы плавно переходим на то, а что мы можем вытащить из статистики — какая информация нам пригодится?

Что нас интересует в статистике:

1.Операция: абонент + БО или url

2.Статус (успех/неуспех)

3.Количество в час (в день)

4.Время выполнения: 90 перцентиль, среднее время, максимальное время

Таким образом, чтобы собрать статистику за входящие асинхронные сообщения нам потребуется запрос для анализа:

 Запрос для анализа z#cit_in_request

select to_char(r.c_push_time,'yyyy-mm-dd hh24') – можно брать просто за сутки

,a.c_code abonent – код абонента 
,b.c_code bo – бизнес операция которую выполнял абонент
,b.c_name  – расшифровка бизнес операции  
,C_ST  – в зависимости от реализации это статус выполнения. обычно 0 - успех, но не всегда (улыбка)
,round(percentile_disc(0.90) within group (order by r.c_time_stats#recieve_time),4) intime90prc
,round(avg(r.c_time_stats#recieve_time),4) intimeavg
,round(max(r.c_time_stats#recieve_time),4) intimemax
,round(min(r.c_time_stats#recieve_time),4) intimemin
,count(*)
from z#cit_in_request r, z#cit_bo b, z#cit_abonent a
where r.c_abonent = a.id
and r.c_bo = b.id
and r.c_push_time >to_date('2025-01-09','yyyy-mm-dd')
and r.c_push_time < to_date('2025-01-10','yyyy-mm-dd')
group by to_char(r.c_push_time,'yyyy-mm-dd hh24'),a.c_code,b.c_code,b.c_name,C_ST

Выполнение может занимать продолжительное время, можно отдельно собрать статистику по дням без группировки по часам и бизнес-операциям, а уже вторым этапом — по конкретному дню с группировкой по часам и минутам.

Замеры по времени нам также понадобятся для дальнейшего анализа результатов тестирования. Однако стоит еще учесть, что это время начинается от того, как обработчик абонента взял в работу это сообщение. Другими словами, если в очереди AQ накопилось много сообщений (количество обработчиков абонента недостаточно) — это время фиксироваться не будет. Например: если мы отправляем письмо, по которому мы ожидаем ответ, то у нас есть только замер, когда получатель письма забрал письмо, а время, которое доставлялось и время ожидания письма на почте не учитывается.

4bf52e49b115d4e94dd4b2ca3c5f2d56.jpg

Если время между отправкой и получением сообщения не укладывается в sla и при этом нет накоплений ни во входящей/исходящей AQ или MQ очереди, то обратите внимание на количество обработчиков и расписание их работы. 

Для сбора информации по исходящему потоку, можно воспользоваться данным примером:

 Запрос для анализа Z#cit_out_request

select to_char(C_push_time,'yyyy-mm-dd hh24'),o.c_abonent,b.c_code,b.c_name, count(*)
from z#cit_out_request o, z#cit_bo b
where o.c_bo = b.ID
and C_push_time > to_date('2025-01-09','yyyy-mm-dd')
and C_push_time < to_date('2025-01-10','yyyy-mm-dd')
group by to_char(C_push_time,'yyyy-mm-dd hh24'),o.c_abonent,b.c_code,b.c_name

Если нужна, например, информация, как быстро отвечала смежная система, то можно посчитать разницу между c_push_time и c_pop_time (но внешняя система может и не формировать ответ).

Перейдем к сбору статистики с web proxy

К сожалению, по таблице невозможно оценить сколько времени отрабатывала в проде API (если знаете способ — пишите в комментариях). PS: замер по полям C_TIME_START и c_time_finish показывает также, как и в случае z#cit_in_request время обработки на стороне самой бд.

Мы можем получить информацию следующую информацию:

 Запрос для анализа Z#WS_LOG:

select to_char(C_TIME_START,'yyyy-mm-dd hh24')
      , REGEXP_SUBSTR(DBMS_LOB.SUBSTR(log.C_REQ_BODY,2000,1),'REQUEST_URI>.+')
      , count(*)
      , round(avg(log.c_time_finish - log.c_time_start)*60*60*24,4)
      , round(percentile_disc(0.9) within group (order by (log.c_time_finish - log.c_time_start)*60*60*24 ),4)
      , round(max(log.c_time_finish - log.c_time_start)*60*60*24 ,4)
from Z#WS_LOG log
where log.C_TIME_START >= to_date('2025-01-09','yyyy-mm-dd')
and log.C_TIME_START < to_date('2025-01-10','yyyy-mm-dd')
group by to_char(C_TIME_START,'yyyy-mm-dd hh24')
       , REGEXP_SUBSTR(DBMS_LOB.SUBSTR(log.C_REQ_BODY,2000,1) ,'REQUEST_URI>.+')

Итак, у нас есть 3 таблицы, которые отвечают Z#cit_in_request, z#cit_out_request, Z#WS_LOG. Мы собираем статистику за разные дни. Выбираем несколько топовых дней. Смотрим статистику по часам за пиковые дни. Подбиваем профили — как минимум дневной и ночной будет отличаться. Составляем профиль тестирования, радуемся, уточняем информацию у аналитиков, скриптуем. Ура, допустим мы заскриптовали все, ну, или почти все…

Как я говорила ранее, есть фоновые процессы: bgp, задания по расписанию, текстовые задания. И вот как с ними быть? Можно просто все включить как есть на продуктиве…

Но есть проблемы:

  1. Есть джобы, которые запускают пользователи и как о них узнать?

  2. Также есть джобы, которые работают, например, раз в сутки ночью или по определенному расписанию. 

  3. Для некоторых джобов надо подготавливать данные.

  4. Фиксация успешности/неуспешности отработки джоба.

Эти факты надо учитывать, иначе тестирование вслепую даст слепые результаты.  

Портал «Аналитика  производительности»

У ЦФТ есть портал «Аналитика производительности», который на основе исторических таблиц (платформенных и history) может показать статистику распределения по компонентам в рамках нагрузки на БД. И наши, допустим, 100% покрытые интеграции составили лишь только, например, от 11% до 13% в зависимости от дня.

Как мы можем себя перепроверить?

6b98658db050b1c852b4b0b4f982b791.png

Расходимся.

bb5c8ae0f09eeac528f773a6bada1b38.jpeg

Выпили чашечку горячего бодрящего и дальше повторяем наш процесс снова. Для каждой компоненты собираем статистику по операциям за разные дни за топовые часы.

Если кликнуть на компоненту, то можно получить информацию по операциям.

799764758b47ef5cb8fe7fc38d838ded.png

Отмечу, что и с точки зрения нагрузки на БД в интеграции можно получить другой топ абонентов, чем тот топ, который мы получали из z#cit_in_request.

Это связано с несколькими аспектами:

Количество обработчиков на каждом абоненте разное, а так как 1 обработчик создает 1 сессию на базе, то чем больше обработчиков — тем больше нагрузка на базе.

«Маленькие раки, но по 3. Большие, но по 5». Один абонент может выполнять кучу маленьких быстрых запросов, а другой меньше запросов, но более медленных.
Статистика по количеству у первого будет больше, но качество нагрузки на базу будет ниже.

ab1d58eadc80dbbc682dcc2a20dea498.png

Мне попадалось много так называемого «Другое», когда куча операций составляет меньше 1% от нагрузки в рамках всех компонент или внутри компоненты.

Оцениваем процент всех «черных» дыр

Для нашей системы таких НЛО (Другое + Unknown) составило суммарно 30% (это если все сложить по всем компонентам).

и внутри уже самих компонентов я часто видела
и внутри уже самих компонентов я часто видела

Итак. Добавляем в профиль фоновые и задания, которые удалось раскопать. Идем к аналитикам и разработчикам. Но вот что же делать с 30%. Что еще можно сделать…

Какие объекты создаются в системе?

А давайте еще проанализируем документы, которые формируются у нас в системе. Эту информацию можно получить из таблицы z#main_docum.

 Запросы из z#main_docum

select p.c_name
   ,to_char(C_astr_date_prov, 'yyyy/mm/dd hh24')
   , count(distinct md.ID) total
  from z#main_docum md,z#NAME_PAYDOC p
 where
md.c_date_prov>= to_date('2025-01-09 00','yyyy-mm-dd hh24')
and md.c_date_prov< to_date('2025-01-10 00','yyyy-mm-dd hh24')
and md.C_astr_date_prov >= to_date('2025-01-09 00','yyyy-mm-dd hh24')
and md.C_astr_date_prov <= to_date('2025-01-10 00','yyyy-mm-dd hh24')
and md.c_vid_doc = p.id
group by p.c_name,to_char(C_astr_date_prov, 'yyyy/mm/dd hh24')
order by 2,3 desc

Что получилось — благодаря интеграции формируется только часть документов, остальное недобор.

cfc4bb2e7f7d48ff39bf15ad36fff130.jpg

Что ж, не будешь же по каждому документу искать концы.
А пока я пыталась сделать это так. Возможно, вам это тоже поможет.

Запрос по анализу z#main_docum часть 2:

select p.c_name
   ,to_char(C_astr_date_prov, 'yyyy/mm/dd hh24')
  -- , (count(distinct m.ID)/24)*(24)) CountperHour
   ,min( extract ( day from ((hh.time)  - h.time) )*86400
    + extract ( hour from (hh.time - h.time) )*3600
    + extract ( minute from (hh.time - h.time) )*60
    + extract ( second from (hh.time - h.time) ) ) nmin
   ,avg( extract ( day from (hh.time - h.time) )*86400
    + extract ( hour from (hh.time - h.time) )*3600
    + extract ( minute from (hh.time - h.time) )*60
    + extract ( second from (hh.time - h.time) ) ) navg
  ,max( extract ( day from (hh.time - h.time) )*86400
    + extract ( hour from (hh.time - h.time) )*3600
    + extract ( minute from (hh.time - h.time) )*60
    + extract ( second from (hh.time - h.time) ) ) nmax
  , percentile_disc(0.9) within group (order by ( extract ( day from (hh.time - h.time) )*86400
    + extract ( hour from (hh.time - h.time) )*3600
    + extract ( minute from (hh.time - h.time) )*60
    + extract ( second from (hh.time - h.time) ) )) prc90
    ,hh.user_id
    ,h.user_id
    , count(distinct md.ID) total
  from z#main_docum md, AUD.IBS_OSH h, AUD.IBS_OSH hh, z#NAME_PAYDOC p
 where
md.c_date_prov>= to_date('2025-01-09 00','yyyy-mm-dd hh24')
and md.c_date_prov< to_date('2025-01-10 00','yyyy-mm-dd hh24')
and md.C_astr_date_prov >= to_date('2025-01-09 00','yyyy-mm-dd hh24')
and md.C_astr_date_prov <= to_date('2025-01-10 00','yyyy-mm-dd hh24')
   and to_char(md.id) =hh.obj_id
   and h.State_Id = 'FORM'
   and hh.obj_id = h.obj_id
   and hh.state_id = 'PROV'
   and md.c_vid_doc = p.id
   --and hh.user_id = h.user_id
group by p.c_name,to_char(C_astr_date_prov, 'yyyy/mm/dd hh24'),hh.user_id,h.user_id
order by 2, 9 desc

Тут есть замеры по времени проведения. Если времена большие — скорее всего, проводились пользователями, а не фоновыми процессами.

Также по user_id можно понять, под каким пользователем были созданы документы.

Сейчас мы рассмотрели только Z#main_docum как общий пример. Но есть и другие объекты, которые вам необходимо тоже сравнить по количеству и качеству. В результате такого анализа может всплыть еще много интересных операций которые необходимо добавить в наш профиль.

После того, как выровняли профиль по количеству объектов (документов), все равно остается вопрос с процентом «Другое» и оценкой того, насколько профиль это покрывает. 

Что можно сделать:

  1. Смириться — ничто в нашем мире не идеально. 

2216e37d406bef5763f6d421c9e7ab12.jpg

2. Раскопать каждый метод, который суммарно занимает меньше 1% нагрузки на бд.

3. Добавить в профиль селективную — фоновую нагрузку на базу.

4. Отмасштабировать текущий профиль на разницу 30%.

Я отдаю предпочтение пункту 3, так как это золотая середина между пунктом 2 и 4: менее трудоемкий, чем пункт 2 и более точным, чем пункт 4.  Пока у нас это только в планах, но давайте я опишу, как мы будем это делать.

Под селективной нагрузкой подразумеваем только воспроизведение запросов на базе вида select, которые не выполняют операции изменения и удаления данных — нас интересует только выборка данных.

Есть в oracle исторические таблицы dba_hist_active_sess_history, dba_hist_snapshot, dba_hist_sqltext, dba_hist_sqlbind.

Таблица dba_hist_active_sess_history — нам поможет выявить sql_id, которые выполнялись в продуктиве, но не выполнялись во время теста. В таблице записывается активные сессии на БД в рамках снепшота (dba_hist_snapshot).

Что такое sql_id — это хэш от текста запроса. Нам нужно вытащить только те запросы, которые выполняют select. Только надо быть внимательным и исключать конструкции select for update.

Текст запроса, который выполнялся, можно получить из представления dba_hist_sqltext, а значения переменных для данного запроса можно взять из представления dba_hist_sqlbind по указанному sql_id.

Итого мы должны получить список запросов, количество одновременно работающих сессий, которые выполняют этот запрос и значения, подставляемые в качестве переменных.

Вообще полезно посматривать в таблицу dba_hist_active_sess_history, так как там много полезной информации:

  1. Какой пользователь выполнял запрос.

  2. С какого хоста пришел запрос.

  3. Модуль, под которым выполнялся запрос (например, для навигатора там будет Novo).

Вывод

Интеграция не покрывает весь профиль. Надо смотреть на вклад операций в систему, а не только интеграционное взаимодействие. Благодаря нашему подходу мы смогли получить профиль тестирования, покрывающий систему на 70%,  а также возможность его еще больше улучшить благодаря фоновой селективной активности.

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

Бонусом мы получили вектор для развития и оптимизации работы фоновых еще даже не проводя тестирование.

Из советов:

  1. Не бывает лишней информации. Активно спрашиваем и вовлекаем продуктивные команды.

  2. Собираем статистику обязательно за разные дни в течение всего года. Да, процесс муторный и нудный, но под чашечку другую крепкого кофею накопать можно.Также в результате этого процесса вы поймете, как живет ваша система, когда ей тяжко, какая динамика роста запросов и объектов от месяца к месяцу, когда пиковые часы и тд.

  3. Если в результатах раскопок попадаются таблицы, в которые фиксируется та или иная информация — смотри шаг 2. Если кроме количественного замера можно получить замер скорости — тоже используем и анализируем. Потом, когда перейдем к тестам, нам будет с чем сравнивать и сопоставлять наши результаты.

  4. Изучайте детальнее фоновые процессы — это не только поможет лучше понимать процесс, но также увидеть точки роста для оптимизации в будущем.

Коллеги, поделитесь, пожалуйста, своим опытом сбора профиля нагрузочного тестирования и спасибо за внимание!  :)

И приходите к нам в канал)

4f6ebc46cab582139379cf751d86d754.jpg

© Habrahabr.ru