Що таке noisy tenants?
Noisy tenants - це клієнти в багатоорендній системі, які споживають надмірну частку спільних ресурсів (CPU, пам'ять, мережу, з'єднання з БД), через що падає продуктивність для всіх інших на тій самій інфраструктурі.
Теорія
TL;DR
- Аналогія: сусід у будинку, який цілодобово гримить музикою і тримає ліфт - всі інші чекають довше
- Головна відмінність: спільна інфраструктура підсилює вплив; у single-tenant проблема ізольована на одному клієнті
- Якщо один tenant споживає більше 50% спільних ресурсів - це вже проблема noisy tenant
- Варіанти рішень: ліміти на tenant, квоти ресурсів, або виділена інфраструктура для найважчих клієнтів
- Правило вибору: приймаємо спільний ризик при більше 50 tenantів зі схожими патернами; ізолюємо коли один перевищує 2x середнього
Швидкий приклад
# AWS Lambda: спільний пул конкурентності (1000 одиниць на весь акаунт)
# Noisy tenant заливає запитами, виснажує пул, інші стоять у черзі
import boto3
lambda_client = boto3.client('lambda')
def handle_request(tenant_id, payload):
# tenant_A надсилає 1000 паралельних запитів
# всі 1000 одиниць конкурентності займає tenant_A
response = lambda_client.invoke(
FunctionName='processor',
Payload=payload
)
return response
# tenant_A: обробляє 1000 запитів нормально
# tenant_B: "Concurrency limit exceeded" - чекає 30+ секунд
# Рішення: aws lambda put-function-concurrency --reserved-concurrent-executions 100Tenant A займає весь спільний пул конкурентності. Запити tenant B стоять у черзі. Жоден код tenant B не змінився. Проблема живе повністю на рівні інфраструктури.
Чому це проблема системного дизайну
У single-tenant налаштуваннях стрибок одного клієнта потрапляє на його власне залізо. Все. У багатоорендному SaaS на AWS або Kubernetes ресурси об'єднані між усіма клієнтами. Один tenant з 80% CPU уповільнює всіх інших через конкуренцію за ресурси.
Найхитріша частина: noisy tenant зазвичай не знає, що спричиняє проблеми. Їхні запити виконуються успішно. А клієнти-жертви бачать повільні відповіді або помилки і пишуть тікети на ваш продукт. Ви дебажите не той кінець системи.
Коли яку стратегію обирати
- Більше 50 tenantів зі схожими патернами використання: приймаємо спільний ризик, додаємо моніторинг через Prometheus
- Нерівномірне навантаження, велике бізнес-значення клієнтів: квоти на tenant через API Gateway або rate limiter перед сервісом
- Фінанси або охорона здоров'я, регульовані дані: строга ізоляція через namespace, незалежно від патернів
- Один tenant стабільно вище 2x середнього: переводимо на виділену інфраструктуру або виставляємо за це рахунок
- MVP стартапу: спочатку спільна інфраструктура, потім міграція після перших скарг
Як це працює на рівні інфраструктури
Kubernetes використовує cgroups для обмеження CPU і пам'яті на pod. Noisy pod досягає 100% своєї квоти і автоматично throttle-иться. Scheduler також може виселяти поди з нижчим пріоритетом при нестачі ресурсів на ноді. Саме звідси виникають каскадні падіння. Один OOMKilled noisy pod запускає виселення podів інших tenantів, що ділять ту ж ноду.
AWS Lambda працює інакше. Дефолтний ліміт конкурентності на акаунт - 1000 одиниць. Будь-яка функція може використати всі. ReservedConcurrency на рівні tenant обмежує, скільки одиниць він може утримувати, і захищає пул для всіх інших.
Для баз даних проблема виникає в пулах з'єднань. Без обмежень на tenant один клієнт може відкрити 900 з 1000 PostgreSQL-з'єднань. Інші tenants не можуть підключитися. pgBouncer з розміром пулу на tenant вирішує це.
Типові помилки
Немає квот на tenant у спільному пулі з'єднань БД
-- Неправильно: один глобальний ліміт без обмежень на tenant
SET max_connections = 1000;
-- Noisy tenant відкриває 900 з'єднань під час batch-задачі
-- Всі інші отримують: "too many connections"
-- Рішення: pgBouncer з розміром пулу на tenant
-- max_client_conn = 100 на пул tenantОдин tenant, який запускає bulk-експорт опівночі, може заблокувати всіх інших до завершення задачі.
Спільний Redis без ізоляції за namespace
// Неправильно: без префіксу tenant, noisy tenant запускає KEYS *
redis.set('user:123', data);
// KEYS * від одного tenant блокує Redis на 10+ секунд
// Всі інші: таймаути на кожній операції Redis
// Правильно: префікс за tenant, SCAN замість KEYS
redis.set(`tenant:${tenantId}:user:123`, data);
redis.scan(0, 'MATCH', `tenant:${tenantId}:*`, 'COUNT', 100);Автомасштабування за глобальними метриками CPU
Масштабування за загальним CPU кластера означає, що стрибок одного noisy tenant подвоює витрати на інфраструктуру для всіх. Кастомні метрики на рівні tenant дозволяють throttle-ити конкретного клієнта:
aws cloudwatch put-metric-data \
--namespace Tenants \
--metric-name CPU.TenantA \
--value 85Відсутність reserved concurrency у Lambda
Дефолтна Lambda: без резервування конкурентності. Один tenant заливає запитами, досягає ліміту 1000 одиниць, ставить у чергу всіх інших на 30+ секунд. Одна команда на функцію вирішує це:
aws lambda put-function-concurrency \
--function-name tenant-a-handler \
--reserved-concurrent-executions 100Ігнорування бурстових tenantів під час пікового трафіку
Під час Black Friday або batch-задач tenant може за секунди досягти 10x свого звичайного навантаження. Жорсткі ліміти через cgroups зупиняють процеси надійно, але дають стрибки latency. М'які ліміти через throttling плавніші, але noisy tenant повільно деградує довше. Жоден варіант не ідеальний. Вибір залежить від SLO і від того, чи ви хочете різкий обрив або повільний спуск.
Де зустрічається в реальних системах
- Kubernetes: ResourceQuota на namespace,
resources.limits.cpu: 2на namespace tenant - AWS RDS: parameter groups і RDS Proxy для обмеження з'єднань на tenant
- Salesforce: governor limits, 100 SOQL-запитів на транзакцію на tenant
- RabbitMQ: virtual hosts з TTL черги на tenant
- API Gateway: usage plans з обмеженнями throttling на tenant
Питання на співбесіді
Q: Як виявляти noisy tenants у продакшені?
A: Prometheus-запит: sum(rate(container_cpu_usage_seconds_total{tenant=~".+"}[5m])) by (tenant). Сповіщення на топ-5% за використанням. p99 latency вище 500ms на tenant - теж надійний сигнал.
Q: Чи можна вирішити проблему без змін у коді застосунку?
A: Так. Kubernetes ResourceQuotas і LimitRanges вирішують це на рівні платформи. Авто-виселення вмикається при 90% CPU ноди. Код застосунку не потрібно чіпати.
Q: Яка різниця між жорсткими і м'якими лімітами?
A: Жорсткі ліміти через cgroups зупиняють процеси при досягненні порогу - надійно, але дають стрибки latency. М'які ліміти throttle-ять запити поступово - плавніше, але noisy tenant довше впливає на сусідів перед тим як ліміт спрацює.
Q: Як обробляти noisy tenant під час стрибка трафіку на кшталт Black Friday?
A: Per-tenant auto-scaling groups і circuit breakers. Якщо tenant перевищує 2x свого середнього базового навантаження, тимчасово переводимо на виділену інфраструктуру.
Q: (Senior) Спроектуй систему квот для 10 000 tenantів з SLO 99.99%. Як виглядатиме архітектура?
A: Envoy proxy sidecar на tenant з динамічними фільтрами квот. Kafka партиціонована за tenant для ізоляції подій. Виявлення аномалій на метриках tenant для перехоплення стрибків до того, як вони стануть каскадом. Окремі пули конкурентності на тарифний план, а не єдиний ліміт на весь акаунт.
Q: Як проблема noisy tenant змінюється в мультирегіональних налаштуваннях?
A: Глобальні сервіси на кшталт DynamoDB поширюють проблему через затримку реплікації (1-5 секунд). Регіональні черги на tenant обмежують радіус ураження одним регіоном і не дають noisy tenant у us-east-1 деградувати користувачів у eu-west-1.
Приклади
Базовий: rate limiting на tenant в Express
const express = require('express');
const Redis = require('ioredis');
const rateLimit = require('express-rate-limit');
const RedisStore = require('rate-limit-redis');
const redis = new Redis();
const app = express();
const tenantLimiter = rateLimit({
store: new RedisStore({ client: redis }),
keyGenerator: (req) => req.headers['x-tenant-id'], // ізоляція за tenant
windowMs: 60 * 1000, // вікно 1 хвилина
max: 100, // 100 запитів на tenant на хвилину
message: 'Tenant quota exceeded'
});
app.use('/api/', tenantLimiter);
// Noisy tenant: 429 після 100-го запиту
// Звичайний tenant: завжди 200, не залежить від noisy neighborКожен tenant отримує свій лічильник у Redis. Noisy tenant отримує 429, а всі інші бачать 200. Це мінімальний захист від noisy tenant - дешево додати і відловлює очевидні flood-кейси до того, як вони дістануться бази даних.
Kubernetes: квоти на namespace і OOM-каскад
# ResourceQuota на namespace tenant
apiVersion: v1
kind: ResourceQuota
metadata:
name: tenant-a-quota
namespace: tenant-a
spec:
hard:
requests.cpu: "2"
requests.memory: 4Gi
limits.cpu: "4"
limits.memory: 8Gi
---
# Noisy pod, який ігнорує ліміти, отримає OOMKilled
apiVersion: v1
kind: Pod
metadata:
name: noisy-job
namespace: tenant-a
spec:
containers:
- name: app
image: busybox
resources:
limits:
memory: "64Mi"
command: ["/bin/sh", "-c", "while true; do dd if=/dev/zero bs=1M; done"]
# досягає ліміту пам'яті, отримує OOMKilled
# без квоти namespace може виселити поди інших tenantівКвота namespace обмежує, що tenant-a може споживати в усьому кластері. Без неї OOMKilled pod запускає виселення через нестачу ресурсів на ноді, яке може зачепити поди інших tenantів на тій самій ноді. Цей каскад дуже важко дебажити о 2-й ночі, коли три різних клієнти одночасно відкривають інциденти.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.