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 последних сообщений для антиспама, массив песен в очереди на каждый сервер: все они не ограничены, если вы их не ограничили. Поставьте предел и вытеснение на каждую коллекцию в вашем коде, иначе процесс будет расти, пока контейнер его не остановит.
Интенты определяют вашу память раньше, чем ваш код#
Интенты шлюза - это подписка, которую вы объявляете при подключении. Они управляют тем, что присылает вам Discord, а то, что он присылает, библиотека кэширует. Правильно выбрать их - самое эффективное решение по подбору ресурсов, и оно занимает одну строку.
Три интента привилегированные: их нужно включить в Developer Portal, а не только в коде, и бот более чем на 100 серверах должен пройти верификацию, чтобы их сохранить:
GUILD_MEMBERS- входы, выходы и обновления участников. Именно он заполняет память. Без него ваш бот вообще не получает список участников.GUILD_PRESENCES- статус в сети и активность каждого участника каждого сервера. Чудовищно дорогой и почти никогда не нужный. Если вы включили его, чтобы показывать, кто в сети, он вам почти наверняка не был нужен.MESSAGE_CONTENT- текст сообщений, в которых бот напрямую не упомянут. Нужен для команд с префиксом, для слэш-команд не нужен.
Бот, целиком построенный на слэш-командах, нуждается в GUILDS и очень немногом другом. Переход с команд с префиксом на слэш-команды - это, стало быть, оптимизация памяти, а не только смена интерфейса, и заодно она снимает необходимость верификации для MESSAGE_CONTENT.
Как урезать кэш#
Обе основные библиотеки позволяют явно ограничить кэш. Сделайте это заранее, а не после остановки из-за нехватки памяти.
В discord.js v14 makeCache задаёт пределы для каждого менеджера, а sweepers вытесняют записи по таймеру:
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 то же самое задаётся в конструкторе клиента:
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 |
|---|---|---|
| 1 | 30-80 MB | 5-15% ядра |
| 5 | 150-400 MB | 0.3-0.8 ядра |
| 20 | 0.6-1.5 GB | 1.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 MB | 0.2-0.5 ядра | 2 GB, 1 vCPU |
| Экономика или уровни с базой данных | 400 MB - 1 GB | 0.3-0.7 ядра | 2-4 GB |
| Музыка, 1-3 одновременных потока | 400-800 MB | 0.5-1 ядро | 2 GB, 1 vCPU |
| Музыка, 10+ одновременных потоков | 1.5-3 GB | 2-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 необработанное отклонение промиса по умолчанию завершает процесс. Обработайте его, запишите в журнал и убедитесь, что запись происходит до выхода:
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. Мы храним имя, которое вы ввели, текст и время - больше ничего. Количество ссылок ограничено, разметка не отображается.