Экономия затрат с помощью таймаутных защелок в JavaScript

Я поддерживаю это приложение под названием:

casset.app

которое, по сути, транслирует в прямом эфире песню, которую я слушаю на Spotify, и позволяет пользователям присоединиться и синхронизироваться со мной.
Оно не передает буферы аудио, только название песни.

Архитектура довольно проста:

  • опрашивать spotify api в фоновом режиме
  • поддерживать состояние в памяти
  • передавать изменения состояния клиенту через вебсокеты.

И это работает довольно хорошо, приемлемая задержка, никаких ограничений скорости (трудно, когда опрашивается только мой пользователь).
Но каждый месяц я получаю счет на 25$ от Digital Ocean.

Это происходит потому, что я использую их кластер k8s и балансировщик нагрузки. Что, как я знаю, является излишеством для чего-то подобного.

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

Пересмотр архитектуры

Во-первых, нам нужно перейти от режима «всегда включен» к режиму «по требованию».
Падает ли дерево в лесу только тогда, когда мы его наблюдаем? В нашем случае — да. Потому что тогда нам не нужно ничего рендерить/вычислять без необходимости.

Поэтому мы убираем слой сокетов и позволяем клиенту опрашивать нашу систему.

Теперь у нас есть два опроса:

  • Клиент опрашивает нашу систему
  • Система опрашивает Spotify

Ничего, если мы воспользуемся коротким путем и объединим эти два опроса? То есть, вызывать API Spotify только тогда, когда клиент вызывает нашу систему.

Теоретически это может сработать, но в этом случае количество запросов API к Spotify будет зависеть от количества клиентов. Один клиент — это нормально, может быть даже 10, но 100? 100,000?

Вот тогда у нас возникнут проблемы. Такие проблемы, как ограничение скорости и исчерпание квоты.
Кроме того, допустим, я слушаю песню в течение нескольких минут, имеет ли смысл вызывать API spotify 100 000k раз, чтобы проверить текущий трек?

Таким образом, мы видим, что объединение этих двух систем — не самая лучшая идея. Что же нам делать дальше?
Нам нужно опрашивать spotify, но не без необходимости, только когда у нас есть намерение, но мы не хотим тесно связываться с входящими запросами.

Слабое сопряжение?
Допустим, у нас есть таймер для опроса spotify, скажем, 10 секунд. Наша система будет опрашивать spotify каждую секунду в течение 10 секунд, а затем остановится. Пока не поступит запрос, таймер сбрасывается на 10 с, и цикл начинается снова.
Если в систему поступает 100 000 запросов, максимум, что может сделать запрос — это сбросить таймер, это не влияет на скорость опроса Spotify, просто удлиняет процесс.

Визуально это лучше всего описать здесь:

Защелка тайм-аута / iostreamer / Observable

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

observablehq.com

Защелка тайм-аута

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

Но мне не хотелось накладных расходов на создание и удаление таймеров только для сброса часов.

Поэтому я создал пользовательский планировщик, который тикает каждые 1 мс. С другой стороны, у нас есть latch, по сути, просто объект со счетчиком.
Задача планировщика — разряжать защелки, уменьшая счетчики.
В этой вселенной планировщик создает время с помощью тиков, а защелки отражают это время с помощью связанных счетчиков.

Это довольно просто и понятно, но, как я уже упоминал, я хотел бы исследовать, можем ли мы платить только за фактическую работу, которую мы делаем?

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

И это последнее, что нам нужно сделать, — остановить планировщик, если все защелки выполнены или отменены.

Если добавляется что-то новое или сбрасывается старая защелка, то снова запустить планировщик.

Код поддерживается здесь и также опубликован на npm.

iostreamer-X / timeout-latch

Простая защелка тайм-аута. Как обратное протекающее ведро.

timeout-latch

Простая защелка тайм-аута. Как обратное «дырявое ведро».

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

Почему не обычный таймаут?

Обычные таймауты могут работать не так просто. Например, можно запустить тайм-аут и управлять состоянием вокруг него.

function run() {
    setTimeout(
        () => {
            // your callback       
        },
        3000
    )
}
function reset() { // when you want to reset the timer
    run();
}
Вход в полноэкранный режим Выход из полноэкранного режима

Но мы понимаем, что сброс работает только тогда, когда тайм-аут завершил свою работу. Если сброс произойдет до этого, то у нас просто будет два таймаута!

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

Посмотреть на GitHub

Почему это лучше?

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

С текущей настройкой, особенно с timeout-latch, мы находимся в состоянии, когда ничего не выполняется без необходимости, и останавливается, если намерение выполнить больше не возникает.

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

Даже за простые приложения на Digital Ocean придется заплатить минимум 5 долларов.
Но для платформы, которая забирает у вас «сервер», можно по-настоящему использовать взрывной характер работы.

И это служит примером того, как мы трансформировали опрос (очень непрерывный) в то, что является взрывным и «по требованию».

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

Оцените статью
devanswers.ru
Добавить комментарий