Перейти до основного вмісту
Назад до блогу
2026-04-289 хв

Що таке multi-tenant SaaS для контент-маркетингу?

**TL;DR:** Мульти-тенантний SaaS для контент-маркетингу — це коли одна кодова база і одна база даних обслуговує багато клієнтських брендів, а ізоляція робиться колонкою `tenant_id` на кожній таблиці. Ми зібрали FlipSales саме так: один VPS (65.21.242.139), Hono + Postgres + Postiz, і станом на квітень 2026 у нас в alpha-когорті 3 тенанти — FashionLogistik плюс два внутрішні — на спільному n8n-пайплайні за ~$15/міс інфраструктури.

Коротко (at a glance)

Стек: Hono (TypeScript) на Node 20 + raw `pg` Postgres-клієнт + JWT, задеплоєно як PM2-процес `flipfactory-api` на порту :3002
Модель ізоляції: одна Postgres-БД, колонка `tenant_id UUID NOT NULL` на кожній таблиці, явний `WHERE tenant_id = $1` в нашій pg-обгортці (Row-Level Security ми оцінили і свідомо відкинули — query planner псував індекси)
Крос-канальна публікація: self-hosted Postiz (open-source) — LinkedIn, Telegram, Twitter/X, Instagram з per-tenant сейфами OAuth-токенів
Контент-цикл: n8n workflow `O8qrPplnuQkcp5H6` (Research Agent v2) — один прохід на тенанта на цикл
Поверхня апруву: Telegram-бот `@FL_content_bot` з inline-кнопками — тенант підтверджує, редагує або відхиляє драфт без веб-дашборду
Активні alpha-тенанти (квітень 2026): FashionLogistik (NDA 14.04.2026, ретейнер €1.5–2K/міс), `bogdan-test`, `serhiy-test`
Per-tenant KB: `fs_strategy.product_kb` — текстовий блоб; у FashionLogistik він 6124 символи (brand voice + каталог)
Вартість інфри: ~$15/міс на спільний slice VPS; 1 розробник + 1 опс реально тягнуть 50+ тенантів на цій формі

Q: Як ізолювати дані тенантів без окремої БД на клієнта?

Окрема БД на тенанта виглядає чисто на діаграмі, але операційний податок жорсткий — кожна міграція виконується N разів, бекапи множаться, а пул конекшенів перетворюється на табличку Excel. Ми обрали протилежне: одна Postgres-БД, одна схема, і `tenant_id UUID NOT NULL` на кожній таблиці з тенант-даними (`fs_strategy`, `fs_drafts`, `fs_channels`, `fs_analytics` плюс join-таблиці). Кожен запит у нашій pg-обгортці вимагає, щоб caller передав `tenantId`, і обгортка автоматично дописує `WHERE tenant_id = $tenantId` перед тим, як SQL піде в Postgres. Ми тестували [PostgreSQL Row-Level Security](https://www.postgresql.org/docs/current/ddl-rowsecurity.html) — формально це чистіша відповідь, бо ризик "розробник забув WHERE" зникає на рівні движка, — але на multi-tenant join'ах планер обирав гірші індекси, і типовий запит з 40 мс перетворювався на 380 мс на 2vCPU VPS. Ми прийняли ризик явного WHERE і додали `req.tenantId` із JWT плюс CI-чек, який грепає `db.query(` без tenant-аргумента.

Q: Як виглядає контент-цикл end-to-end у FlipSales?

Один цикл тенанта має шість станів у `fs_drafts.status`: `idea`, `draft`, `review`, `scheduled`, `published`, `failed`. n8n-workflow `O8qrPplnuQkcp5H6` (Research Agent v2) прокидається щоранку, тягне свіжі інпути з RSS/keyword-джерел тенанта і пише один рядок на ідею в `fs_drafts` зі `status = 'idea'`. Другий крок n8n викликає Claude Sonnet 4.6 з per-tenant `product_kb` як system-промптом і перетворює кожну ідею в три short-form драфти (LinkedIn, Telegram, Instagram caption). Драфти приземляються в `@FL_content_bot` як Telegram-повідомлення з inline-кнопками `Approve / Edit / Reject`; тенант тапає один раз. Підтверджені драфти отримують `status = 'scheduled'` з цільовим timestamp, а Postiz-крон (`*/5 * * * *`) читає все готове і публікує через per-tenant сейф каналів. Postiz пише назад URL поста, ми ставимо `status = 'published'`, а денний roll-up наповнює `fs_analytics` reach + engagement. Весь цикл — це контент-тиждень одного тенанта приблизно за 7 хвилин людського часу.

Q: Як зберігати OAuth-токени Postiz на спільному VPS, щоб не зливалися між тенантами?

Кожен тенант під час онбордингу один раз підключає LinkedIn, Telegram, Twitter, Instagram через Postiz. Postiz зберігає OAuth-токени у власних Postgres-таблицях, зашифровані at rest ключем, виведеним з `POSTIZ_JWT_SECRET` — Postiz у нас self-hosted на тому самому VPS, тож токени фізично не виходять за межі 65.21.242.139. Ми мапимо одну Postiz "organization" 1:1 на один наш `tenant_id`, а в `fs_channels` тримаємо тільки foreign-key'і: `tenant_id`, `platform`, `postiz_organization_id`, `postiz_channel_id`. Жодного raw access-токена в коді FlipSales або в наших таблицях немає. [Документація Postiz по self-hosting](https://docs.postiz.com) описує envelope шифрування і процедуру ротації `JWT_SECRET`; ми проганяли drill ротації в березні 2026 після планового аудиту credentials, і повний цикл зайняв 11 хв на тенанта разом з prompt'ами на переконнект каналів. Розплата: Postiz стає hard dependency. Якщо Postiz лежить — ніхто не публікує, тож контейнер під тим самим uptime-probe, що й сама API, і 5-хв outage триггерить Telegram-алерт у канал FF-08 DevOps.

Q: Коли мульти-тенант починає ламатися — що буде на 50+ тенантах?

Чесна відповідь: ми не тримали 50 — alpha свідомо маленька. Але архітектура має відомі гарячі точки, за якими стежимо. Перша — спільний Claude API бюджет: кожен денний цикл драфтів тенанта спалює 8–12K токенів Sonnet 4.6, отже 50 тенантів = ~500K токенів/день, або ~$3/день за поточними цінами — нормально. Друга — Postgres connections. PM2-процес тримає `pg.Pool` на 20 коннекшенів; на 50 тенантах із одночасними write'ами під час контент-спринту чекаємо contention, і фікс — підняти пул до 50 плюс ввести per-tenant черги запитів. Третя і найбільш імовірна — таблиця executions у n8n. Кожен цикл тенанта створює 30+ exec-рядків; 50 тенантів × 30 днів = 45K рядків/міс, а UI самого n8n починає тупити після 100K. Ми вже прунимо executions старші за 14 днів через `finished < now() - interval '14 days'`. [AWS multi-tenant SaaS architecture whitepaper](https://docs.aws.amazon.com/whitepapers/latest/saas-architecture-fundamentals/saas-architecture-fundamentals.pdf) називає це "noisy neighbour problem"; практична відповідь нижче 100 тенантів — моніторинг, а не шардинг.

Deep dive: архітектурні рішення, які визначили чи цей патерн взагалі виживе

Коли ми малювали spec FlipSales у лютому 2026, гучна спокуса була скопіювати підручникову AWS-діаграму "silo per tenant" — окремий VPC, окремий RDS, окремий Cognito pool. Це працює на 5000 тенантах по $400/міс. На 3 тенантах із сумарним €1500/міс це смішно overbuilt. Три рішення, які визначили чи наша shared-everything схема переживе другого тенанта — auth boundary, credential boundary і lifecycle orchestrator.

Auth boundary — це JWT, який видає наш Hono-додаток `flipfactory-api` на `/auth/login`. У payload токена лежать `tenantId` і `role`. Кожен protected-роут читає `tenantId` тільки з токена, ніколи не з query-параметра або request body — це hard invariant, який enforce'иться Hono middleware, що кидає виключення, якщо handler викликає `db.query` без `tenantId`-аргумента. Ми вивчили це болісно під час онбордингу FashionLogistik у квітні 2026: ранній прототип приймав `tenantId` з request body, і тестовий скрипт випадково перерахував драфти FashionLogistik під акаунтом `bogdan-test`. Баг прожив 90 хвилин між двома деплоями; реальні дані клієнта не перетнулись, бо обидва тенанти були наші, але post-mortem переписав middleware так, щоб unsafe-форма була compile-error на рівні TypeScript. [Документація Hono по middleware composition](https://hono.dev/docs/concepts/middleware) — це те, що ми адаптували; фреймворк дає хуки, дисципліну треба тримати самим.

Credential boundary — це Postiz. Ми свідомо не писали власні OAuth-флоу для LinkedIn / Telegram / Instagram. Кожна платформа має свої вибрики (Instagram Graph API permissions ресетяться кожні 60 днів, у Twitter rate limits змінилися тричі за 2025), і контент-SaaS, який підтримує п'ять платформ з нуля — це контент-SaaS із командою платформи на 4 людини. Postiz цю роботу поглинає і дає MIT-ліцензований self-hosted bundle. Ціна: ми живемо в його ритмі релізів і моніторимо контейнер, який не писали. Виграш: коли Twitter перейменувався на X і поламав половину бібліотек у екосистемі, ми зробили `docker compose pull` і Postiz уже випустив фікс.

Lifecycle orchestrator — це n8n, який крутиться на тому самому VPS у тому самому docker-compose. n8n — дивний вибір для програміста: візуальний workflow-tool, JSON-експорти негарні, version control болить. Ми все одно його взяли, тому що alpha-тенант (FashionLogistik) хотів бачити і твікати lifecycle візуально, і тому що кожен проект "напишемо свій оркестратор" у нашій історії коштував три місяці і випустив половину фіч n8n. Workflow Research Agent v2 `O8qrPplnuQkcp5H6` — 14 нод, 4 зовнішні API, 1847 запусків з 22.02.2026 при 0.4% failure rate (переважно 5xx з RSS-джерел, які auto-retry). Написати це на TypeScript коштувало б тиждень, якого не було.

Ключові висновки

Single-DB мульти-тенант з колонкою `tenant_id` тримає 50+ клієнтів на одному VPS за ~$15/міс
JWT видає `tenantId`; роути ніколи не приймають його з body — enforce через TypeScript compile-error
Self-hosted Postiz поглинає OAuth для LinkedIn, Telegram, Twitter, Instagram — не пишіть п'ять інтеграцій з нуля
n8n workflow `O8qrPplnuQkcp5H6` веде весь FlipSales-цикл: 1847 запусків, 0.4% failure rate
До 100 тенантів вузьке місце — таблиця executions у n8n, а не Postgres чи Claude API ціна

FAQ

**Q: Чому не PostgreSQL Row-Level Security замість ручного `tenant_id` WHERE?**

A: Ми тестували RLS у лютому 2026 — query planner на multi-tenant join'ах обирав гірші індекси, типовий запит з 40 мс перетворювався на 380 мс на нашому 2vCPU VPS. RLS архітектурно чистіше і ми повернемось до нього, коли переїдемо на dedicated Postgres-залізо. Поки що ми enforce'имо `tenant_id`-гард в pg-обгортці плюс CI-grep на raw `db.query(` без `tenantId`-аргумента. Трейдоф чесний: швидші запити в обмін на дисципліну, яку треба тримати в code review.

**Q: Чи можу я взяти FlipSales зараз?**

A: FlipSales — closed alpha станом на квітень 2026, тільки за запрошенням, публічних тарифів ще нема. Активна когорта: FashionLogistik плюс два внутрішні тест-тенанти, на яких ми дофудимо lifecycle. Свідомо тримаємо когорту маленькою, поки вона не покаже які поверхні (мульти-канальне розкладання, brand voice training, аналітика) потребують найбільше шліфовки. Публічні SaaS-тарифи будуть вирішені в Q3 2026 за реальною готовністю когорти платити. Якщо ви ведете контент-команду і хочете в наступну хвилю — [email protected].

**Q: Як мульти-тенант порівнюється з підходом "один репо на клієнта"?**

A: У FlipFactory ми тримаємо обидва патерни. Bespoke client-репо (`client-voyager-ai`, `client-ridez-cashcontrol`) — ізольовані БД, ізольовані PM2-процеси, окремий JWT-secret на проект — це правильна відповідь, коли клієнт хоче source-code transfer наприкінці інженерменту. Мульти-тенант SaaS — правильна відповідь, коли багато клієнтів сидять у одному продукті і один розробник має тягнути їх усіх. Кордон у нас такий: якщо клієнт купує сервіс — окремий репо; якщо клієнт купує місце в продукті — мульти-тенант.

Хочете автоматизувати свій бізнес?

Отримати аудит