НФТ к производительности: расчет нагрузки в rps на практическом примере

как расчитать нагрузку пример, обучение системных и бизнес-аналитиков. примеры НФТ, Школа прикладного анализа и проектирования информационных систем

Как рассчитать нагрузку в rps и задать нефункциональные требования к производительности в точных цифрах: калькуляция на примере интернет-магазина.

Описание контекста

Требования к производительности системы – одни из самых важных НФТ (нефункциональных требований), которые необходимо определить в техническом задании (ТЗ), т.к. они в большей степени влияют на выбор архитектурных решений. Если система не справится с нагрузкой, пострадает бизнес, а, значит, цель автоматизации не будет достигнута. Часто в ТЗ требования к производительности системы задаются в количестве одновременно работающих пользователей и частоте запросов от них в единицу времени.

Однако, для проектирования хранилища данных важно понимать, какие виды запросов (на чтение или на запись) будут преобладать. Это позволит принять решение о необходимости разделять единое хранилище на несколько разных, реализуя архитектурный паттерн CQRS (Command and Query Responsibility Segregation). Такое разделение запросов на чтение и команд на мутации данных позволяет улучшить масштабируемость системы при высоких нагрузках, ее быстродействие и повысить безопасность благодаря дополнительным проверкам при мутациях.  Однако, для сохранения целостности данных нужно обеспечить быструю синхронизацию изменений между разными хранилищами. Это повышает архитектурную сложность системы и стоимость ее эксплуатации. Впрочем, архитектурное проектирование – это всегда поиск компромисса.

Архитектурный паттерн CQRS
Архитектурный паттерн CQRS

Эта диаграмма создана с помощью следующего PlantUML-скрипта:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
@startuml
title CQRS (Command and Query Responsibility Segregation)
actor Пользователь
rectangle "Система" {
Пользователь --> (Изменить данные) : команда
Пользователь --> (Прочитать данные) : запрос
database "БД на запись" AS WDB
(Изменить данные) --> WDB
database "БД на чтение" AS RDB
(Прочитать данные) --> RDB
WDB -> RDB : Обновление
}
@enduml
@startuml title CQRS (Command and Query Responsibility Segregation) actor Пользователь rectangle "Система" { Пользователь --> (Изменить данные) : команда Пользователь --> (Прочитать данные) : запрос database "БД на запись" AS WDB (Изменить данные) --> WDB database "БД на чтение" AS RDB (Прочитать данные) --> RDB WDB -> RDB : Обновление } @enduml
@startuml
title CQRS (Command and Query Responsibility Segregation)
actor Пользователь
rectangle "Система" {
  Пользователь --> (Изменить данные) : команда
  Пользователь --> (Прочитать данные) : запрос


  database "БД на запись" AS WDB
  (Изменить данные) --> WDB

database "БД на чтение" AS RDB
  (Прочитать данные) --> RDB

WDB -> RDB : Обновление
}

@enduml

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

Сначала надо определить сценарии работы с данными и частоту выполнения каждого из них. Имея в ТЗ реестр вариантов использования (ВИ) системы, сделать первое довольно просто. Например, пользовательскими ВИ для интернет-магазина будут Посмотреть каталог товаров, Найти товар, Посмотреть товар, Добавить товар в корзину, Оформить заказ, Посмотреть историю заказов, Найти заказ, Посмотреть заказ, Изменить заказ. Еще появятся системные ВИ, связанные с регистрацией и аутентификацией пользователя, а также обновлением данных в клиентском профиле.

Таблица 1. Реестр ВИ

ВИ Актор
UC-1 Посмотреть каталог товаров Любой пользователь
UC-2 Найти товар Любой пользователь
UC-3 Посмотреть товар Любой пользователь
UC-4 Добавить товар в корзину Любой пользователь
UC-5 Оформить заказ Зарегистрированный клиент
UC-6 Посмотреть историю заказов Зарегистрированный клиент
UC-7 Найти заказ Зарегистрированный клиент
UC-8 Посмотреть заказ Зарегистрированный клиент
UC-9 Изменить заказ Зарегистрированный клиент
UC-10 Войти в систему Любой пользователь
UC-11 Регистрация Любой пользователь
UC12- Аутентификация Зарегистрированный клиент
UC-13 Обновить профиль Зарегистрированный клиент
UML-диаграмма вариантов использования для интернет-магазина
UML-диаграмма вариантов использования для интернет-магазина

Эта диаграмма создана с помощью следующего PlantUML-скрипта:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
@startuml
skinparam packageStyle rectangle
actor Пользователь as U
actor Клиент as C
C --|> U
rectangle "Интернет-магазин" {
U -> (Войти в систему)
U ---> (Посмотреть каталог товаров)
U --> (Найти товар)
U --> (Посмотреть товар)
U -> (Добавить товар в корзину)
C --> (Посмотреть историю заказов)
C --> (Посмотреть заказ)
C -> (Обновить профиль)
(Найти заказ) ..> (Посмотреть историю заказов) : extend
(Посмотреть заказ) <.. (Изменить заказ) : extend
(Аутентификация) <.. (Посмотреть заказ) : include
(Посмотреть каталог товаров) <. (Найти товар) : extend
(Добавить товар в корзину) <. (Оформить заказ) : extend
(Добавить товар в корзину) ..> (Посмотреть товар): include
(Посмотреть товар) ...> (Найти товар): include
(Оформить заказ) .> (Аутентификация) : include
(Посмотреть заказ) ..> (Найти заказ): include
(Войти в систему) <. (Регистрация) : extend
(Войти в систему) <.. (Аутентификация) : extend
(Обновить профиль) ..> (Аутентификация) : include
}
@enduml
@startuml skinparam packageStyle rectangle actor Пользователь as U actor Клиент as C C --|> U rectangle "Интернет-магазин" { U -> (Войти в систему) U ---> (Посмотреть каталог товаров) U --> (Найти товар) U --> (Посмотреть товар) U -> (Добавить товар в корзину) C --> (Посмотреть историю заказов) C --> (Посмотреть заказ) C -> (Обновить профиль) (Найти заказ) ..> (Посмотреть историю заказов) : extend (Посмотреть заказ) <.. (Изменить заказ) : extend (Аутентификация) <.. (Посмотреть заказ) : include (Посмотреть каталог товаров) <. (Найти товар) : extend (Добавить товар в корзину) <. (Оформить заказ) : extend (Добавить товар в корзину) ..> (Посмотреть товар): include (Посмотреть товар) ...> (Найти товар): include (Оформить заказ) .> (Аутентификация) : include (Посмотреть заказ) ..> (Найти заказ): include (Войти в систему) <. (Регистрация) : extend (Войти в систему) <.. (Аутентификация) : extend (Обновить профиль) ..> (Аутентификация) : include } @enduml
@startuml
skinparam packageStyle rectangle
actor Пользователь as U
actor Клиент as C
C --|> U
rectangle "Интернет-магазин" {
    U -> (Войти в систему)
    U ---> (Посмотреть каталог товаров)
    U --> (Найти товар)
    U --> (Посмотреть товар)
    U -> (Добавить товар в корзину)
    C --> (Посмотреть историю заказов)
    C --> (Посмотреть заказ)
    C -> (Обновить профиль)
    (Найти заказ) ..> (Посмотреть историю заказов) : extend
    (Посмотреть заказ) <.. (Изменить заказ) : extend
    (Аутентификация) <..  (Посмотреть заказ) : include
    (Посмотреть каталог товаров) <. (Найти товар) : extend
    (Добавить товар в корзину) <. (Оформить заказ) : extend
    (Добавить товар в корзину) ..> (Посмотреть товар): include
    (Посмотреть товар) ...> (Найти товар): include
    (Оформить заказ) .> (Аутентификация) : include
    (Посмотреть заказ) ..> (Найти заказ): include
    (Войти в систему) <. (Регистрация) : extend
    (Войти в систему) <.. (Аутентификация) : extend
    (Обновить профиль) ..> (Аутентификация) : include
}
@enduml

Расчет нагрузки

Теперь выделим из реестра ВИ сценарии на чтение и на запись данных. К операциям на чтение относятся Посмотреть каталог товаров,      Найти товар, Посмотреть товар, Посмотреть историю заказов, Найти заказ, Посмотреть заказ. К операциям на запись относятся Добавить товар в корзину, Оформить заказ, Изменить заказ, Регистрация, Обновить профиль. Вход в систему и Аутентификация будем рассматривать как операции чтения, поскольку они включают в себя доступ к уже существующей информации без её изменения. При аутентификации система считывает введенные пользователем данные (например, логин и пароль) и проверяет, что они соответствуют хранящейся в базе данных информации. Этот процесс не вносит никаких изменений в данные пользователя, а только проверяет их достоверность, выполняя доступ к данным без их изменения. Хотя на практике в процессе аутентификации могут выполняться операции записи, связанные с созданием пользовательской сессии и логированием, для упрощения примера будем считать, что ВИ аутентификации ориентирован на чтение данных. Отметим это в таблице ВИ.

Таблица 2. Реестр ВИ с определением видов операций

ВИ Актор Вид операции
UC-1 Посмотреть каталог товаров Любой пользователь Чтение
UC-2 Найти товар Любой пользователь Чтение
UC-3 Посмотреть товар Любой пользователь Чтение
UC-4 Добавить товар в корзину Любой пользователь Запись
UC-5 Оформить заказ Зарегистрированный клиент Запись
UC-6 Посмотреть историю заказов Зарегистрированный клиент Чтение
UC-7 Найти заказ Зарегистрированный клиент Чтение
UC-8 Посмотреть заказ Зарегистрированный клиент Чтение
UC-9 Изменить заказ Зарегистрированный клиент Запись
UC-10 Войти в систему Любой пользователь Чтение
UC-11 Регистрация Любой пользователь Запись
UC-12 Аутентификация Зарегистрированный клиент Чтение
UC-13 Обновить профиль Зарегистрированный клиент Запись

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

  • 50 000 клиентов, т.е. зарегистрированных пользователей;
  • DAU (Daily Active Users), ежедневное количество активных пользователей = 10 000, из них 70% — это зарегистрированные пользователи;
  • ежедневно регистрируется 500 новых клиентов;
  • количество входов в систему в день = 20 000, т.е. по 2 сессии на пользователя в день;
  • среднее количество операций за сессию = 15;
  • средняя длительность пользовательской сессии = 10 минут;
  • пиковое количество одновременно работающих пользователей = 1 000;
  • основная активность пользователей происходит после рабочего дня, с 18:00 до 21:00 МСК;
  • пользователи просматривают каталог товаров 50 000 раз за день;
  • количество поисковых запросов по товарам 30 000 в день;
  • отдельные товары просматриваются 40 000 раз в день;
  • просмотр истории заказов выполняется около 500 раз в день;
  • поиск по заказам запрашивается примерно 1000 раз в день;
  • отдельные заказы просматриваются не более 800 раз в день;
  • в среднем в заказе 5 товаров;
  • ежедневно оформляется 5 000 заказов;
  • ежедневно отменяется или изменяется 1 000 заказов;
  • около 2 000 обновлений в день происходит с данными пользовательских профилей.

Таблица 3. Реестр ВИ с определением частоты выполнения операций

ВИ Актор Вид операции Частота

(количество в день)

UC-1 Посмотреть каталог товаров Любой пользователь Чтение 50 000
UC-2 Найти товар Любой пользователь Чтение 30 000
UC-3 Посмотреть товар Любой пользователь Чтение 40 000
UC-4 Добавить товар в корзину Любой пользователь Запись 25 000
UC-5 Оформить заказ Зарегистрированный клиент Запись 5 000
UC-6 Посмотреть историю заказов Зарегистрированный клиент Чтение 500
UC-7 Найти заказ Зарегистрированный клиент Чтение 1 000
UC-8 Посмотреть заказ Зарегистрированный клиент Чтение 800
UC-9 Изменить заказ Зарегистрированный клиент Запись 1 000
UC-10 Войти в систему Любой пользователь Чтение 20 000
UC-11 Регистрация Любой пользователь Запись 500
UC-12 Аутентификация Зарегистрированный клиент Чтение 20 000
UC-13 Обновить профиль Зарегистрированный клиент Запись 2 000

Посчитаем количество операций на чтение в день:

  • Посмотреть каталог товаров = 50 000;
  • Найти товар = 30 000;
  • Посмотреть товар = 40,000;
  • Вход в систему и аутентификация = 20 000;
  • Посмотреть историю заказов = 500;
  • Найти заказ = 1 000;
  • Посмотреть заказ = 800.

Итого в день выполняется 142 300 операций на чтение:

50 000 + 30 000 + 40 000 + 20 000 + 500 + 1 000 + 800 = 142 300

Теперь выполним аналогичный расчет количества операций на запись (в день):

  • Добавить товар в корзину = 25 000;
  • Оформить заказ = 5 000;
  • Изменить заказ = 1 000;
  • Регистрация = 500;
  • Обновить профиль = 2 000

Итого в день выполняется 33 500 операций на запись:

25 000 + 5 000 + 1 000 + 500 + 2 000 = 33 500

Для расчёта нагрузки в запросах в секунду (rps, request per second) необходимо разделить количество операций на чтение и запись, выполняемых за день, на количество секунд в сутках (86400 секунд). Итого получим

  • 142 300 / 86 400 ≈ 1,64 rps на чтение;
  • 33 500 / 86 400 ≈ 0,39 rps на запись;

Это означает, что каждую секунду имеем 1,64 запросов на чтение и 0,39 запросов на запись. Таким образом, примерно 98 запросов на чтение и 23 запроса на запись в минуту. Это довольно низкая нагрузка, которая не требует сложных архитектурных решений, таких как CQRS, которое разделяет операции на команды, изменяющие состояние системы, и запросы, читающие данные. Поскольку текущие нагрузки очень низкие, применение CQRS избыточно и неоправданно. Подробнее об этом расскажу в следующей статье. А в заключение еще раз подчеркну важность количественного определения НФТ для качественного проектирования и точной реализации.

Как именно выявить и описать в ТЗ требования, достаточные проектирования и разработки информационных систем, вы узнаете на моих курсах Школы прикладного бизнес-анализа в нашем лицензированном учебном центре обучения и повышения квалификации системных и бизнес-аналитиков в Москве:

 

Я даю свое согласие на обработку персональных данных и соглашаюсь с политикой конфиденциальности.