RE:NODE

Ресурсы13 мин чтения

Хостинг Discord-бота: сколько RAM и CPU вам на самом деле нужно

Командному боту хватает 1 GB, музыкальному - нет. Во что на самом деле обходятся кэш участников, интенты, голосовые потоки и шардинг, и какой тариф чаще всего переплачивают.

Обновлено

0 прочтений

Discord-бот - это долгоживущий процесс с одним открытым websocket. Сам websocket почти ничего не стоит: heartbeat раз в сорок с небольшим секунд и те события, на которые вы подписались. Бот со слэш-командами на тридцати серверах займёт 80-150 MB резидентной памяти и несколько процентов ядра, а значит, самый маленький тариф у любого хостера для него уже великоват.

Что действительно стоит ресурсов, так это кэш. Каждая популярная библиотека хранит копию всего, что видела, - серверы, каналы, роли, участников, сообщения, статусы присутствия, - чтобы ваш код мог читать их без обращения по сети. Этот кэш растёт вместе с вашим успехом, а не с вашим кодом, поэтому бот, отлично работавший на десяти серверах, падает на четырёхстах без единой изменённой строки. Вторая затратная вещь - звук, потому что музыкальный бот на деле является сервисом транскодирования с чат-интерфейсом.

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

Что бот на самом деле держит в памяти#

Разложите число на части, и оно перестанет быть загадкой. У типичного бота резидентная память складывается из шести частей:

КомпонентТипичная стоимостьРастёт с
Среда выполнения и библиотеки60-120 MB (Node), 50-90 MB (Python)Почти ни с чем
Кэш серверов, каналов и ролей1-10 MB на несколько сотен серверовЧислом серверов
Кэш участников и пользователейот сотен байт до пары KB на каждогоЧислом участников, если вы их кэшируете
Кэш сообщенийпо умолчанию 200 сообщений на канал в discord.jsКаналы, умноженные на активность
Голос, на каждый активный поток30-80 MB плюс реальный CPUЧислом одновременных слушателей
Ваше собственное состояниевсё, что вы положили в переменнуюОбычно тихо и бесконечно

Кэш участников - это весь ответ для любого бота на крупных серверах. Бот на серверах с суммарно ста тысячами участников, с включённым кэшированием участников и загрузкой серверов целиком при старте, может набрать несколько сотен мегабайт, ещё не сделав ничего полезного. Тот же бот с выключенным кэшированием участников остаётся там, где начал.

Последняя строка заслуживает больше внимания, чем ей достаётся. Всё, что вы держали в словаре, потому что база данных казалась излишеством, - это утечка памяти с планом. Карты кулдаунов по ID пользователя, Map последних сообщений для антиспама, массив песен в очереди на каждый сервер: все они не ограничены, если вы их не ограничили. Поставьте предел и вытеснение на каждую коллекцию в вашем коде, иначе процесс будет расти, пока контейнер его не остановит.

события и heartbeatрастёт с успехомтолько музыкальные ботыважные записиШлюз Discordодин websocketПроцесс ботаNode или PythonКэш библиотекисерверы, участники, сообщениffmpegпо одному на потокБаза данныхсостояние, которое нужно сох
Из чего на самом деле состоит процесс бота

Интенты определяют вашу память раньше, чем ваш код#

Интенты шлюза - это подписка, которую вы объявляете при подключении. Они управляют тем, что присылает вам Discord, а то, что он присылает, библиотека кэширует. Правильно выбрать их - самое эффективное решение по подбору ресурсов, и оно занимает одну строку.

Три интента привилегированные: их нужно включить в Developer Portal, а не только в коде, и бот более чем на 100 серверах должен пройти верификацию, чтобы их сохранить:

  • GUILD_MEMBERS - входы, выходы и обновления участников. Именно он заполняет память. Без него ваш бот вообще не получает список участников.
  • GUILD_PRESENCES - статус в сети и активность каждого участника каждого сервера. Чудовищно дорогой и почти никогда не нужный. Если вы включили его, чтобы показывать, кто в сети, он вам почти наверняка не был нужен.
  • MESSAGE_CONTENT - текст сообщений, в которых бот напрямую не упомянут. Нужен для команд с префиксом, для слэш-команд не нужен.

Бот, целиком построенный на слэш-командах, нуждается в GUILDS и очень немногом другом. Переход с команд с префиксом на слэш-команды - это, стало быть, оптимизация памяти, а не только смена интерфейса, и заодно она снимает необходимость верификации для MESSAGE_CONTENT.

Как урезать кэш#

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

В discord.js v14 makeCache задаёт пределы для каждого менеджера, а sweepers вытесняют записи по таймеру:

javascript
const { Client, GatewayIntentBits, Options } = require("discord.js");const client = new Client({  intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages],  makeCache: Options.cacheWithLimits({    ...Options.DefaultMakeCacheSettings,    MessageManager: 25,    PresenceManager: 0,    GuildMemberManager: {      maxSize: 50,      keepOverLimit: (member) => member.id === member.client.user.id,    },  }),  sweepers: {    ...Options.DefaultSweeperSettings,    messages: { interval: 3600, lifetime: 1800 },  },});

Строка keepOverLimit - не украшение. Библиотеке нужен её собственный участник сервера, чтобы определять свои права, поэтому жёсткий предел без этого исключения ломает проверки прав так, что найти причину трудно. В документации также перечислены менеджеры, которые ограничивать нельзя вовсе - среди них серверы, каналы и роли, - потому что остальная библиотека полагает их полными. Ограничивайте сообщения, участников и статусы присутствия; структурные кэши не трогайте.

В discord.py то же самое задаётся в конструкторе клиента:

python
import discordintents = discord.Intents.default()intents.message_content = Falseintents.members = Falseintents.presences = Falseclient = discord.Client(    intents=intents,    max_messages=None,                       # default is 1000, None disables    chunk_guilds_at_startup=False,    member_cache_flags=discord.MemberCacheFlags.none(),)

chunk_guilds_at_startup - то, что люди упускают. При включённом интенте участников он по умолчанию равен true, и библиотека запрашивает полный список участников каждого сервера в момент подключения. У бота на нескольких сотнях больших серверов это долгий и прожорливый по памяти запуск, который повторяется при каждом перезапуске. Выключите его и запрашивайте нужных участников через guild.fetch_member(id).

После любого из изменений понаблюдайте за графиком памяти в консоли в течение суток, а не доверяйте первому показанию. Память при старте - это не память в установившемся режиме, а Node в особенности с удовольствием растёт к любому потолку кучи, который считает своим, прежде чем сборщик мусора возьмётся всерьёз. В статье почему приложение на Node падает на 2 GB на тарифе в 4 GB объяснён этот потолок и то, как задать его осознанно.

Музыкальные и голосовые боты - другой зверь#

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

Каждый одновременный поток включает получение источника, декодирование, передискретизацию до 48 kHz стерео, кодирование в Opus, шифрование и отправку 50 пакетов в секунду. На практике это как минимум один процесс ffmpeg на поток, часто процесс yt-dlp для предварительного разбора источника и нативный кодировщик Opus. Со стороны Node @discordjs/voice также требует пакет шифрования и библиотеку Opus рядом; со стороны Python нужны ffmpeg в пути и PyNaCl для голоса.

Ориентировочный бюджет:

Одновременных потоковПамять сверх базового ботаCPU
130-80 MB5-15% ядра
5150-400 MB0.3-0.8 ядра
200.6-1.5 GB1.5-3 ядра, плюс всплески

Два практических следствия. Первое: у музыкального бота заканчивается CPU, а не память, а CPU на общем тарифе - жёсткое ограничение долей, которую вы купили. Задушенный цикл событий хуже медленного: он задерживает heartbeat шлюза, Discord перестаёт получать подтверждения, и библиотека переподключается. Бот, который «случайно отключается, когда несколько человек играют музыку», почти всегда страдает от нехватки CPU, а не от проблем сети.

Второе: разбор источника через yt-dlp - скачкообразная, непредсказуемая затрата, не связанная с воспроизведением, и она пишет на диск, если разрешить кэширование. На тарифе в 5 GB он заполняется быстрее, чем вы ожидаете. Задайте каталог кэша, который можно очищать, или отключите кэширование совсем.

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

Шардинг, и где память умножается#

Discord требует шардировать бота, когда он находится более чем на 2 500 серверах. Шлюз сам говорит, сколько шардов ему нужно: отправьте GET /gateway/bot со своим токеном и прочитайте поле shards. Ниже этого порога шардинг решает проблему, которой у вас нет.

Как шардинг влияет на память, целиком зависит от библиотеки, и именно эта деталь застаёт людей врасплох:

  • `ShardingManager` в discord.js запускает отдельный процесс Node на каждый шард. Каждый процесс - полноценный бот со своей средой выполнения, своим экземпляром библиотеки и своим кэшем. Шестнадцать шардов - это шестнадцатикратная базовая память, а обмен между шардами идёт только через менеджер с помощью broadcastEval. Закладывайте с запасом.
  • `AutoShardedClient` в discord.py запускает все шарды в одном процессе на одном цикле событий, поэтому стоимость среды выполнения оплачивается один раз, а кэши общие. Память растёт с данными, а не с числом шардов.

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

Куда деваются данные#

Для небольшого бота по умолчанию используют файл JSON, и он работает, пока не перестаёт. Два способа отказа, оба частые:

  • Запись всего файла при каждом изменении. При нескольких сотнях килобайт и одной записи на команду это нормально; при нескольких мегабайтах это заметная пауза на каждом взаимодействии, потому что цикл событий блокирован, пока идёт сериализация.
  • Убийство процесса посреди записи. Процесс, остановленный между open и close, оставляет обрезанный файл, и бот не запустится. Всегда пишите во временный файл в том же каталоге и переименовывайте его на место с помощью rename - на любой нормальной файловой системе это атомарно.

Переходите на SQLite, когда файл становится неудобным, - для большинства ботов это момент, когда хочется делать запросы, а не загружать всё целиком. SQLite - это один файл, серверу он не нужен, и объём записи бота он обрабатывает, не замечая. Переходите на сетевую базу данных, когда процессов больше одного, когда два процесса пишут одновременно или когда набор данных перестаёт комфортно помещаться в памяти.

Со стороны хостинга: тарифы для приложений включают два слота базы данных, создаваемых в панели, которая генерирует хост, пользователя и пароль. Для более крупного или отдельно масштабируемого хранилища есть отдельные линейки PostgreSQL и MongoDB. Честное сравнение - в статье Postgres или MongoDB, а пулы соединений и лимиты разбирают ошибку, которую большинство ботов совершают первой: открытие соединения на каждую команду и исчерпание сервера.

Что бы вы ни выбрали, токен и строка подключения не должны лежать в репозитории. Кладите их в переменные окружения на вкладке Startup - в статье переменные окружения и секреты объяснено, почему, и что делать в тот день, когда один из них утёк.

Размеры тарифов для настоящих ботов#

За основу взята линейка тарифов для приложений. У каждого уровня одна и та же панель, так что речь тут не о функциях.

БотПамятьCPUРазумный уровень
Слэш-команды, менее 50 серверов150-250 MBменее 0.2 ядра1 GB, 0.5 vCPU
Модерация или журналирование, несколько сотен серверов300-700 MB0.2-0.5 ядра2 GB, 1 vCPU
Экономика или уровни с базой данных400 MB - 1 GB0.3-0.7 ядра2-4 GB
Музыка, 1-3 одновременных потока400-800 MB0.5-1 ядро2 GB, 1 vCPU
Музыка, 10+ одновременных потоков1.5-3 GB2-3 ядра6-8 GB, 2-3 vCPU
Шардированный, 2 500+ серверов (discord.js)250-400 MB на шард0.2 ядра на шардизмерьте, затем умножьте

Диск редко бывает ограничением, но на него стоит взглянуть. Проект discord.js с поддержкой голоса тянет 150-250 MB в node_modules; виртуальное окружение Python обычно меньше. Ставьте зависимости командой npm ci --omit=dev, чтобы зависимости времени сборки не попадали на сервер. Настоящий риск для диска - музыкальный бот, кэширующий аудио, и бот, пишущий журнал без ротации: оба тихо заполняют тариф в 5 GB за пару месяцев.

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

Оставаться в сети: перезапуски, циклы падений и деплой#

Люди покупают хостинг ради того, чтобы бот работал и завтра. Это определяют три детали.

Необработанное отклонение убивает процесс. В современном Node необработанное отклонение промиса по умолчанию завершает процесс. Обработайте его, запишите в журнал и убедитесь, что запись происходит до выхода:

javascript
process.on("unhandledRejection", (error) => {  console.error("unhandled rejection:", error);});process.on("SIGTERM", async () => {  await client.destroy();  process.exit(0);});

Перехват SIGTERM и уничтожение клиента важнее, чем кажется: бот, убитый без закрытия websocket, остаётся видимым как «в сети» ещё до минуты, из-за чего перезапуск выглядит намного более долгим простоем, чем был. Общий шаблон - в статье корректное завершение и проверки работоспособности.

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

Цикл падений замечают. Наблюдатель каждые пару минут проверяет сервер, ушедший офлайн или у которого время работы пошло назад; перезапуски, которые вы заказали сами, не считаются. Три неожиданных перезапуска за час помещают предупреждение на страницу сервера и автоматически открывают тикет, а шесть ведут к приостановке. Такая политика существует, потому что бот, перезапускающийся каждые двадцать секунд, бьёт по шлюзу и сжигает лимит identify, и об этом стоит знать до того, как вы выкатите изменение в полночь и пойдёте спать. Сам цикл разбирается в статье почему ваш сервер постоянно перезапускается.

Деплой - вторая половина. Подключение репозитория GitHub даёт два переключателя: pull при каждом запуске и deploy при push, который перезапускает только уже работающий сервер. Оба стоит включить для бота, потому что альтернатива - загружать файлы по SFTP и забывать, какая версия сейчас работает. Пошаговый разбор - в статье деплой приложения на Node.js из GitHub, и для Python он применим так же.

FAQ#

Сколько RAM нужно Discord-боту?

Боту со слэш-командами на нескольких десятках серверов нужно 150-250 MB, так что самый маленький тариф уже с запасом. Память становится вопросом, только когда вы кэшируете участников, кэшируете сообщения, воспроизводите звук или храните состояние в переменных. Измеряйте установившееся значение неделю, прежде чем покупать больше.

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

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

Нужно ли шардировать бота?

Нет, пока этого не потребует Discord, то есть свыше 2 500 серверов. Спросите шлюз, чего он хочет, командой GET /gateway/bot, а не гадайте. Ранний шардинг добавляет процессы, сложность и, в discord.js, умноженный счёт за память без всякой пользы.

Можно ли запустить несколько ботов на одном тарифе?

Технически два процесса помещаются в один контейнер, но это ложная экономия: у них общий лимит памяти и общее ограничение CPU, а бот, ушедший в цикл падений, утянет за собой второго. Отдельные небольшие тарифы сужают радиус поражения и делают графики читаемыми.

Лучше ли бот на VDS?

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

Почему мой музыкальный бот заикается, когда им пользуются несколько человек?

CPU. Каждый поток - это транскодирование, CPU на тарифе жёстко ограничен купленной долей, и как только цикл событий задушен, аудиопакеты уходят с опозданием, а вместе с ними с опозданием уходит и heartbeat шлюза. Смотрите график CPU в этот момент; если он упирается в ваш лимит, это и есть ответ.


Комментарии

Полностью анонимно: без аккаунта, без почты, без cookie. Мы храним имя, которое вы ввели, текст и время - больше ничего. Количество ссылок ограничено, разметка не отображается.

0/2000