Что такое RPS (Requests Per Second) и как эта метрика связана с производительностью бэкенда?

Ответ

RPS (Requests Per Second) — это ключевая метрика производительности, которая показывает, сколько запросов (например, HTTP-запросов) система способна обработать за одну секунду.

Она используется для:

  • Оценки пропускной способности: Понять, какую нагрузку может выдержать сервис.
  • Нагрузочного тестирования: Определить пределы производительности и найти узкие места.
  • Планирования ресурсов: Рассчитать необходимое количество серверов или ресурсов для обработки ожидаемого трафика.

RPS vs Latency

Важно анализировать RPS в связке с latency (задержкой) — временем ответа на один запрос. Высокий RPS при высокой задержке может означать, что система обрабатывает много запросов, но каждый из них выполняется медленно, что плохо для пользовательского опыта.

Факторы, влияющие на RPS в Go:

  • Производительность приложения: Эффективность алгоритмов, минимизация аллокаций памяти, правильное использование конкурентности (горутин и каналов).
  • Взаимодействие с базой данных: Оптимизация запросов, использование индексов, пулов соединений и кэширования.
  • Сетевая инфраструктура: Пропускная способность сети и задержки.
  • Производительность зависимых сервисов: Если ваш сервис обращается к другим API, их скорость напрямую влияет на ваш RPS.

Пример измерения в Go

Хотя прямых инструментов для измерения RPS в стандартной библиотеке нет, можно использовать бенчмарки для оценки производительности обработчика, что косвенно связано с RPS.

// handler_test.go
package main

import (
    "net/http"
    "net/http/httptest"
    "testing"
)

func handler(w http.ResponseWriter, r *http.Request) {
    w.WriteHeader(http.StatusOK)
}

// Бенчмарк измеряет, сколько операций (обработок запроса) может быть выполнено в секунду.
func BenchmarkHandler(b *testing.B) {
    req := httptest.NewRequest("GET", "/", nil)
    rec := httptest.NewRecorder()

    // b.N — это количество итераций, которое определяет сам фреймворк для получения статистически значимого результата.
    for i := 0; i < b.N; i++ {
        handler(rec, req)
    }
}

Ответ 18+ 🔞

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

RPS — это, грубо говоря, счётчик «пшиков» в секунду. Сколько запросов твой сервис может проглотить, переварить и выплюнуть ответ за одну секунду. Если твой сервис — это кабак, то RPS показывает, сколько посетителей он может обслужить, пока не начнёт всем говорить «идите нахуй, кухня закрыта».

Зачем это нужно? Да всё просто:

  • Узнать, когда начнётся пиздец. Чтобы понять, при какой нагрузке твоё творение ляжет и начнёт отдавать 500-е ошибки вместо ответов.
  • Устроить пиздец нарочно. Это называется нагрузочное тестирование. Ты заранее пытаешься сломать свою же систему, чтобы понять, где у неё тонкое место.
  • Не остаться без штанов. Чтобы посчитать, сколько серверов тебе надо арендовать, когда на твой сайт внезапно зайдёт не три бота, а десять тысяч живых людей.

RPS и Задержка — они как братья, но один еблан

Смотреть на один RPS — это как судить о машине только по максимальной скорости. Можно выжать 300 км/ч, но если разгоняется она минуту, то в городе ты будешь ебаться как в говне. Так и тут.

Latency (задержка) — это время, за которое приходит ответ на ОДИН запрос. Можно иметь RPS в миллион, но если каждый запрос обрабатывается 10 секунд, то пользователь успеет заварить чай, сходить посрать и ещё и постить в твиттер успеет, пока ему что-то отрисуется. Сочетание высокого RPS и низкой задержки — вот идеал, ёпта.

От чего эта самая RPS в Go зависит? Да от всего на свете!

  • Твой код. Если ты в цикле на каждый запрос создаёшь по три новых массива на гигабайт, а потом их вхолостую перебираешь — ну что ж ты, сука, делаешь? Оптимизируй алгоритмы, меньше аллоцируй память, правильно используй горутины и каналы.
  • База данных. Если на каждый чих ты лезешь в базу с запросом SELECT * FROM users без индекса, то твой RPS будет стремиться к плинтусу. Индексы, пулы соединений, кэш — вот твои друзья.
  • Сеть и всё вокруг. Быстрый ли у тебя хостинг? Не дёргаешь ли ты десять внешних API, каждый из которых отвечает по пять секунд? Всё это ебёт твою производительность по полной.

Как померить? Ну, примерно вот так

В Go нет волшебной палочки measure_rps(), но можно пойти с другой стороны — написать бенчмарк. Он покажет, насколько быстро работает твой обработчик, а это уже полдела.

// handler_test.go
package main

import (
    "net/http"
    "net/http/httptest"
    "testing"
)

func handler(w http.ResponseWriter, r *http.Request) {
    w.WriteHeader(http.StatusOK)
}

// Этот бенчмарк будет долбить твой обработчик запросами и смотреть, сколько итераций влезет в секунду.
func BenchmarkHandler(b *testing.B) {
    req := httptest.NewRequest("GET", "/", nil)
    rec := httptest.NewRecorder()

    // b.N — это магия самого тестинга. Он сам подберёт число, чтобы прогнать тест нормально.
    for i := 0; i < b.N; i++ {
        handler(rec, req)
    }
}

Запустишь это, и увидишь что-то вроде 300000 ops/sec — это и есть показатель того, на что твой кусок кода способен в идеальных лабораторных условиях. А в реальности, конечно, всё будет в три раза хуже, потому что добавится база данных, сеть и ещё какая-нибудь ебучка, о которой ты и не думал. Но начало положено!