Сколько RAM нужно для AFFiNE
AFFiNE выглядит как лёгкая замена Notion — белый интерфейс, блоки, канбан, доска. Но за этим интерфейсом стоит не статический сайт, а Node.js-сервер с GraphQL API, движком синхронизации на CRDT и двумя дополнительными процессами — PostgreSQL и Redis. Поставить его на тот же тариф, что и связку из блога на Hugo, не получится: сервер будет либо падать в OOM при первой одновременной правке документа двумя людьми, либо тормозить так, что курсор будет отставать от нажатий клавиш. Ниже — из чего складывается память AFFiNE, сколько закладывать под разные сценарии и рабочий docker-compose под небольшой VPS.
Содержание
- Из чего состоит AFFiNE и почему это не блог на статике
- Сколько RAM закладывать: по сценариям
- docker-compose: рабочий минимум
- PostgreSQL и Redis: тонкая настройка под небольшую память
- Как измерить фактическое потребление и поймать OOM заранее
- Что происходит при нехватке памяти и как подстраховаться
- Когда AFFiNE — не лучший выбор по памяти
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →Из чего состоит AFFiNE и почему это не блог на статике
AFFiNE — гибрид заметок, канбана и вики: один документ может содержать текстовый блок, встроенную таблицу базы данных и рядом — бесконечную доску (whiteboard) с фигурами и стикерами. Технически self-host собирается из трёх компонентов:
- Node.js-сервер — раздаёт собранный фронтенд, отвечает на GraphQL-запросы (аутентификация, workspaces, история версий) и держит WebSocket-соединения для совместного редактирования.
- PostgreSQL — хранит метаданные документов, пользователей, права доступа и снапшоты CRDT-состояния.
- Redis — pub/sub между процессами сервера (когда реплик несколько) и очередь фоновых задач: обработка вложений, экспорт, индексация.
Ключевое отличие от условного DokuWiki или Wiki.js — здесь совместное редактирование работает через Yjs (CRDT), а не через блокировку файла или строки в базе. Пока документ открыт хотя бы одним человеком, его состояние синхронизации живёт в памяти процесса Node.js, а не только в базе. Открыли двадцать документов разными пользователями одновременно — в памяти двадцать активных CRDT-состояний, а не одно. Именно это, а не объём хранимого текста, задаёт реальный расход памяти.
Поэтому вопрос «сколько нужно RAM» для AFFiNE правильнее ставить не как «сколько байт весят мои заметки», а как «сколько людей одновременно редактируют документы прямо сейчас».
Сколько RAM закладывать: по сценариям
Цифры — ориентир для docker-compose на Node.js 20 LTS, PostgreSQL 16 и Redis 7 на Ubuntu 24.04, без внешнего S3 под вложения (файлы лежат локально). Точные мегабайты зависят от версии AFFiNE и профиля нагрузки — считайте это отправной точкой, а не гарантией.
| RAM | Что реально тянет | Комментарий |
|---|---|---|
| 2 ГБ | 1 человек, изредка 2 | Минимум для теста. Совместное редактирование двумя людьми уже создаёт риск свопа |
| 4 ГБ | Семья или команда 3-8 человек, 1-3 одновременных редактора | Рабочий минимум для личного или семейного инстанса |
| 8 ГБ | Команда 10-25 человек, 5-8 одновременных редакторов, активные доски (whiteboard) | Комфортный вариант для небольшой компании |
| 16 ГБ | 30-60 человек, регулярные доски с десятками фигур, много вложений | Здесь уже имеет смысл разносить Postgres и Redis по разным контейнерам с лимитами |
Как и с любым сервисом реального времени, узкое место — не общее число пользователей, а число одновременно активных сессий редактирования. Пятьдесят человек, из которых пишут документы одновременно трое, ведут себя как маленькая команда с точки зрения памяти. Доски (whiteboard) с изображениями и множеством фигур весят в CRDT-состоянии заметно больше, чем текстовый документ той же «видимой» длины — если у вас используются доски активно, закладывайте на ступень выше.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверdocker-compose: рабочий минимум
Официальный self-host AFFiNE разворачивается контейнером сервера плюс двумя зависимостями. Ниже — компактный вариант под тестовый или личный сервер; для продакшена вынесите пароли в .env и добавьте reverse proxy с TLS.
# docker-compose.yml
services:
affine:
image: ghcr.io/toeverything/affine:stable
restart: unless-stopped
ports:
- "3010:3010"
environment:
- NODE_ENV=production
- AFFINE_SERVER_HOST=affine.example.com
- AFFINE_SERVER_PORT=3010
- AFFINE_SERVER_HTTPS=true
- DATABASE_URL=postgresql://affine:affine_pass@postgres:5432/affine
- REDIS_SERVER_HOST=redis
volumes:
- affine_storage:/root/.affine/storage
- affine_config:/root/.affine/config
depends_on:
- postgres
- redis
deploy:
resources:
limits:
memory: 1536M
postgres:
image: postgres:16-alpine
restart: unless-stopped
environment:
- POSTGRES_USER=affine
- POSTGRES_PASSWORD=affine_pass
- POSTGRES_DB=affine
volumes:
- affine_pg:/var/lib/postgresql/data
deploy:
resources:
limits:
memory: 512M
redis:
image: redis:7-alpine
restart: unless-stopped
command: redis-server --maxmemory 128mb --maxmemory-policy noeviction
volumes:
- affine_redis:/data
deploy:
resources:
limits:
memory: 192M
volumes:
affine_storage:
affine_config:
affine_pg:
affine_redis:
Точные названия переменных окружения (AFFINE_SERVER_HOST, DATABASE_URL, REDIS_SERVER_HOST и другие) меняются между релизами — перед запуском на проде сверьте их с .env.example из репозитория toeverything/affine под конкретную версию образа, которую ставите. Секцию deploy.resources.limits в обычном (не Swarm) docker-compose up Docker Compose v2 тоже уважает — это удобный способ заранее увидеть, во что упрётся контейнер, прежде чем это увидит OOM killer.
redis-server --maxmemory-policy noeviction здесь принципиален: AFFiNE использует Redis не только как кеш, но и как очередь фоновых задач. Политика вытеснения allkeys-lru в очереди недопустима — сервер может молча потерять задачу на обработку вложения вместо того, чтобы получить ошибку нехватки памяти и корректно её обработать.
PostgreSQL и Redis: тонкая настройка под небольшую память
На VPS с 2-4 ГБ дефолтные настройки PostgreSQL из официального образа избыточно щедры для конкурентных подключений, но недостаточны по shared_buffers. Базовая правка для небольшого инстанса — через command в compose или отдельный postgresql.conf:
shared_buffers = 128MB
effective_cache_size = 384MB
work_mem = 8MB
maintenance_work_mem = 64MB
max_connections = 50
50 подключений — с запасом: Node.js-сервер AFFiNE держит пул на порядок меньше, но если вы когда-нибудь запустите вторую реплику сервера или добавите метрики, лишний запас не помешает. work_mem не задирайте выше 8-16 МБ на этом объёме RAM — это значение умножается на число одновременных сортировок в запросах, и на слабой машине легко устроить себе своп именно из-за него, а не из-за самого AFFiNE.
Redis под AFFiNE обычно не растёт большим — 128 МБ лимита хватает с запасом для очереди задач и pub/sub на команду до полусотни человек. Если видите, что INFO memory показывает приближение к лимиту, это почти всегда означает застрявшую очередь фоновых задач (например, зависший экспорт), а не органический рост — проверьте логи контейнера affine, а не увеличивайте лимит вслепую.
Как измерить фактическое потребление и поймать OOM заранее
Смотреть на top внутри контейнера бессмысленно — интереснее суммарная картина по всем трём сервисам сразу:
docker stats --no-stream affine postgres redis
Для истории пиков (а не только текущего момента) на Ubuntu с cgroup v2 полезно смотреть memory.peak каждого контейнера:
cat /sys/fs/cgroup/system.slice/docker-$(docker inspect -f '{{.Id}}' affine).scope/memory.peak
На хосте — классика с акцентом на колонку available, а не free:
free -h
total used free shared buff/cache available
Mem: 3.8Gi 2.6Gi 180Mi 45Mi 1.1Gi 1.0Gi
Swap: 2.0Gi 310Mi 1.7Gi
Тревожный признак — не сам факт использования swap (Linux подкачивает неактивные страницы всегда), а его рост изо дня в день и available ниже 15% от общего объёма в рабочее время. Если видите такую динамику, откройте docker logs affine --tail 200 — чаще всего перед OOM в логе видны повторяющиеся ошибки соединения с Postgres или Redis: сервер начинает пересоздавать соединения быстрее, чем успевает их закрывать.
Что происходит при нехватке памяти и как подстраховаться
Node.js-процесс AFFiNE при нехватке памяти ведёт себя предсказуемо плохо: сначала растут задержки ответа GraphQL (сборщик мусора V8 останавливает event loop на паузы сборки), затем WebSocket-соединения начинают рваться и переподключаться — пользователи видят «Reconnecting...» в углу экрана прямо во время правки документа. Если памяти не хватает совсем, ядро Linux убивает процесс:
Out of memory: Killed process 8842 (node) total-vm:2847012kB,
anon-rss:1523480kB, file-rss:0kB, shmem-rss:0kB, UID:1000 oom_score_adj:0
Практические меры на небольшом сервере:
- Своп как подушка, не как решение. 2 ГБ файла подкачки на диске из SSD/NVMe сглаживают кратковременные пики без падения контейнера. Как выставить его правильно — в статье про правильный размер swap для VPS.
- Лимиты памяти на каждый контейнер отдельно, как в примере compose выше — так при утечке или пике падает и перезапускается один сервис, а не вся машина через системный OOM killer, который может выбрать жертвой не тот процесс.
- Ограничьте число одновременных вложений и их размер в настройках workspace, если у команды привычка заливать видео и большие PDF в документы — это не столько про RAM, сколько про диск и время обработки в очереди Redis, которое тоже держит память дольше при больших файлах.
- Если сервер уже показывает признаки нехватки памяти прямо сейчас — короткий чек-лист действий в статье что делать при нехватке RAM.
Когда AFFiNE — не лучший выбор по памяти
Честно: если нужна просто вики для документации команды без досок и real-time совместного редактирования, AFFiNE на память дороже альтернатив. Trilium на Node.js без Postgres и Redis укладывается в разы меньший объём для одиночного пользователя, а Outline Wiki при похожем стеке (Postgres + Redis) не держит в памяти CRDT-состояние досок и заметно легче на команду того же размера. Если же ценность именно в гибриде «заметки + канбан + доска» и живом совместном редактировании — переплата в памяти оправдана, просто закладывайте её заранее, а не узнавайте через неделю эксплуатации.
Отдельно стоит Joplin Server — он ближе к синхронизации заметок, чем к совместному редактированию в реальном времени, и по памяти ощутимо экономнее.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Хватит ли 1 ГБ RAM для AFFiNE?
Формально контейнеры запустятся, но это плохая идея даже для одного пользователя: PostgreSQL и Node.js вместе съедают большую часть гигабайта в покое, и любой всплеск — загрузка вложения, экспорт документа — уходит в своп или заканчивается OOM. Практический минимум — 2 ГБ, и то с осознанием ограничений.
Растёт ли потребление RAM с ростом числа документов?
Незначительно. В памяти живёт активное состояние *открытых* документов, а не вся база целиком — PostgreSQL хранит остальное на диске и подгружает по запросу. На расход сильнее влияет число одновременных пользователей и размер отдельных документов (особенно досок с изображениями), а не общее количество заметок в workspace.
Нужен ли Redis обязательно, или можно без него?
В self-host конфигурации AFFiNE Redis используется под очередь фоновых задач и синхронизацию между процессами сервера, поэтому в стандартной поставке отключить его не получится без правок кода. На небольшом инстансе он лёгкий — 128-192 МБ лимита обычно достаточно, не экономьте на нём в первую очередь.
Что съедает память быстрее — текстовые документы или доски (whiteboard)?
Доски. Каждая фигура, стикер и встроенное изображение — отдельный элемент в CRDT-структуре документа, и активно используемая доска с полусотней объектов в памяти весит заметно больше, чем текстовый документ на несколько страниц. Если у команды в ходу большие доски, планируйте память по ним, а не по текстовым заметкам.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →