Повышение производительности на 1500 % за один раз

API

Вообще говоря, моя инфраструктура здесь выигрывает от уровней CDN, OSS и шлюза.Мне не нужно нести сюда слишком много трафика.Пока я не пишу много ДТП, в бэкенде нет узкого места в производительности .

Но на этот раз последнюю функцию оттенков серого нужно совместить с бизнесом.До этого наши оттенки серого основывались на DNS как стандарте для региональных оттенков серого, но теперь трафик должен быть направлен напрямую на серверную часть, чтобы судить.Если нет путь к оттенкам серого, если он закэширован спереди - так что никак, мы можем только пойти прямо к мосту и нести его.

Кратко опишем логику нашего кода: запрос 1 -> логика обработки -> запрос 2 -> запрос 3 -> логика обработки -> оценка оттенков серого -> возврат.

Первоначально сохранил самый оригинальный код без установки какого-либо кеша, который удовлетворял потребности в прошлом, потому что кеш был добавлен в заголовок обратным прокси уровня шлюза, включая s-max-age и max-age.

Затем откройте давление:wrk -c20 -t20 -d1m:

20 threads and 20 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   195.58ms   73.94ms 592.81ms   68.39%
    Req/Sec     5.59      2.83    20.00     63.45%
  6155 requests in 1.00m, 3.94MB read
Requests/sec:    102.40
Transfer/sec:     67.19KB

Сначала мы думали, что это из-за того, что количество соединений было слишком маленьким.После увеличения QPS не увеличился, а уменьшился.Пообщавшись с большими парнями, которые являются шлюзами, мы узнали следующие два момента:

  1. QPS - это не то, сколько вы нажимаете, а еще зависит от вычислительной мощности сервера
  2. ЦП и память используются только более чем на десять процентов, что указывает на то, что узкого места здесь нет, скорее всего, это ввод-вывод.

Часть ввода-вывода нашего оператора — это только операция базы данных сетевого ввода-вывода, поэтому мы добавляем слой кэша LRU к операции базы данных, которая примерно выглядит так:

let result
if (cache(query) is not empty) {
  result = cache(query)
} else {
  result = await db(query)
}

cache.set(result)

Конечно, это всего лишь кусок псевдокода.

Потом снова нажали:

  2 threads and 400 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   337.68ms  485.34ms   2.00s    84.04%
    Req/Sec    79.44     31.82   323.00     81.98%
  9237 requests in 1.00m, 5.92MB read
  Socket errors: connect 0, read 0, write 0, timeout 4952
Requests/sec:    153.71
Transfer/sec:    100.92KB

Улучшение успеваемости очень ограничено (на самом деле вышеприведенный абзац проблематичен, читатели должны сначала подумать об этом, а потом говорить об этом). просмотрите код вместе со мной, и мы снова начали сомневаться в размере пула соединений.

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

Затем мы провели локальную отладку, и после того, как мы начали ведение журнала, мы обнаружили, что оператор SQL все еще делает повторные запросы, даже несмотря на то, что кеш был. В это время мы вспомнили об этом, потому что мы не объединяли запросы (а объединять запросы непросто , потому что строки запросов разные), параллельные запросы Если входящие данные не были обработаны, они не были сохранены в кэше в это время, и другие запросы будут продолжать запрашивать до тех пор, пока не будет завершена первая волна запросов, что серьезно влияет QPS.

Таким образом, мы кэшируем обещание запроса, а не результат запроса. (На самом деле, я уже делал подобный проект раньше, но какое-то время не вспоминал об этом)

let resultPromise
if (cache(query) is not empty) {
  resultPromise = cache(query)
} else {
  resultPromise = db(query)
}

cache.set(resultPromise)
result = await resultPromise

Испытание под давлением:

  2 threads and 400 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   206.88ms  305.24ms   2.39s    89.53%
    Req/Sec     1.46k   580.86     2.65k    71.35%
  28138 requests in 10.10s, 18.00MB read
Requests/sec:   2785.71
Transfer/sec:      1.78MB

И использование процессора и памяти в мониторинге также ниже, чем раньше (но, конечно, нет явной тяги QPS), система может обрабатывать 1500% запросов одновременно.

Конечно, чтобы избежать использования кеша навсегда, мы устанавливаем тайм-аут кеша здесь на пять минут и выполняем только тогда, когда кеша нет.cache.set(Поскольку set будет обновлять счетчик времени кеша), то есть он запрашивается только один раз каждые пять минут, что считается допустимым для наших пользователей (бизнес-партнеров).