English {#english}
Expanded SaaS Multitenancy — Redis, R2 Tenant Storage, Full HA
Purpose: Define Redis on a separate server, per-tenant storage separation in R2, DB/App/Storage/Redis layer separation, and a Full HA (high availability) roadmap for 100+ tenant SaaS.
No code generation — planning, phases, and design principles only.
Reference: saas-db-separation-and-scaling-plan.md (DB separation Phase 1–3).
1. Purpose and Scope
1.1 Purpose
- Build a SaaS architecture that can stably support 100+ tenants
- Achieve fault isolation and scalability by separating App / DB / Redis / Storage layers
- Run Redis on a separate server — minimize impact on App when queue/cache spikes
- Separate per-tenant storage in R2 — isolate attachments, backups, and archives by tenant prefix/bucket
- Shard by tenant group to avoid DB bottlenecks
- Establish automation (Provisioning / Monitoring / Failover)
1.2 Scope
- Horizontal scaling of App cluster (Bench / Workers / Scheduler)
- DB layer: MariaDB Primary + Replica (separate servers, same as existing plan)
- Redis layer: dedicated Redis server → future Redis HA (Sentinel)
- Storage layer: Cloudflare R2 — per-tenant storage (prefix or bucket strategy)
- Multitenancy sharding: tenant group distribution + Enterprise dedicated DB
2. Target Architecture (Recommended)
[ Load Balancer ] ↓[ App Cluster ] - Bench (Web/API) - Workers (RQ/Celery) - Scheduler ↓[ DB Layer ] [ Redis Layer ] [ Storage Layer ] - MariaDB Primary - Redis Master - R2 (per-tenant prefix/bucket) - MariaDB Replica - Redis Replica - Attachments / Backups / Archive - Sentinel (when HA)- Redis on a separate server: Do not run Redis on the App server; provide cache/queue/socketio from a dedicated Redis server (or cluster).
- Per-tenant storage in R2: Isolate storage by tenant using an R2 bucket prefix (e.g.
tenant_id=xxx/) or per-tenant bucket policy.
Full detail for §§3–12 (Redis design, R2 strategy, provisioning, monitoring, capacity, execution order, integration, Redis sizing, Sentinel Docker) is in the Korean section below.
한국어 {#korean}
확장 SaaS 멀티테넌시 기획서 — Redis 분리·R2 테넌트 저장공간·Full HA
목적: 테넌트 100+ 대규모 SaaS를 위한 Redis 별도 서버 분리, 각 고객(테넌트)별 저장공간 R2 분리, DB/App/Storage/Redis 계층 분리 및 Full HA(고가용성) 로드맵을 정의한다.
코드 생성 없음 — 기획·단계·설계 원칙만 정리.
참조: saas-db-separation-and-scaling-plan.md (DB 분리 Phase 1~3).
1. 목적과 범위
1.1 목적
- 테넌트 100개 이상을 안정적으로 수용 가능한 SaaS 아키텍처 구축
- App / DB / Redis / Storage 계층 분리로 장애 격리 및 확장성 확보
- Redis를 별도 서버로 분리 — Queue·Cache 폭주 시 App 영향 최소화
- 각 고객(테넌트)별 저장공간은 R2로 분리 — 첨부파일·백업·아카이브를 테넌트 단위 prefix/버킷으로 격리
- DB 병목 회피를 위한 테넌트 그룹별 샤딩
- 운영 자동화(Provisioning / Monitoring / Failover) 기반 마련
1.2 범위
- App Cluster(Bench / Workers / Scheduler) 수평 확장
- DB Layer: MariaDB Primary + Replica (별도 서버, 기존 기획서와 동일)
- Redis Layer: 별도 Redis 서버 → 향후 Redis HA(Sentinel)
- Storage Layer: Cloudflare R2 — 테넌트별 저장공간 분리(prefix 또는 버킷 전략)
- 멀티테넌시 샤딩: 테넌트 그룹 분산 + Enterprise 전용 DB
2. 목표 아키텍처 (권장 구조)
[ Load Balancer ] ↓[ App Cluster ] - Bench (Web/API) - Workers (RQ/Celery) - Scheduler ↓[ DB Layer ] [ Redis Layer ] [ Storage Layer ] - MariaDB Primary - Redis Master - R2 (테넌트별 prefix/버킷) - MariaDB Replica - Redis Replica - Attachments / Backups / Archive - Sentinel(HA 시)- Redis도 별도 서버로 분리: App 서버에 Redis를 두지 않고, 전용 Redis 서버(또는 클러스터)에서 cache/queue/socketio 제공.
- 각 고객별 저장공간은 R2로 분리: 테넌트 단위로 R2 버킷 내 prefix(예:
tenant_id=xxx/) 또는 테넌트 전용 버킷 정책으로 저장공간 격리.
3. Redis 별도 서버 분리
3.1 설계 목표
- Redis를 App 서버에서 분리하여 리소스 격리
- Background Job 폭주 시 App 영향 최소화
- 동시 1,000+ 사용자 대응
- 향후 Redis HA(Sentinel)로 확장 가능 구조
3.2 현재 vs 목표
| 구분 | 기존 통합 | 목표 분리 |
|---|---|---|
| Redis 위치 | App 서버 내 (cache + queue + socketio) | 별도 Redis 서버 |
| 장애 영향 | Redis 장애 시 App 전체 영향 | Redis·App 장애 격리 |
| 확장 | App 스케일 시 Redis 함께 증설 | Redis 서버만 독립 확장 |
3.3 Frappe Redis 구조
Frappe는 Redis를 3개 논리 인스턴스로 사용한다.
| 역할 | 기능 |
|---|---|
| redis_cache | 캐시 |
| redis_queue | Background job (RQ) |
| redis_socketio | 실시간 이벤트 |
분리 시 단일 Redis 서버에서 DB 0/1/2로 구분하거나, 성장 단계에서 기능별 Redis 서버(Cache / Queue / SocketIO)로 나눌 수 있음.
3.4 App 서버 설정 (원격 Redis)
common_site_config.json 예시:
{ "redis_cache": "redis://:password@REDIS_SERVER_IP:6379/0", "redis_queue": "redis://:password@REDIS_SERVER_IP:6379/1", "redis_socketio": "redis://:password@REDIS_SERVER_IP:6379/2"}- REDIS_SERVER_IP: Pulumi로 프로비저닝한 Redis 전용 서버 IP(또는 Private Network IP).
- requirepass, AOF, maxmemory 정책은 Redis 서버 측에서 설정.
3.5 Redis 서버 구성 단계
| 단계 | 내용 |
|---|---|
| 1단계 | Redis 전용 서버 1대. App에서 원격 연결. requirepass, AOF, maxmemory 정책 설정. |
| 2단계 | Redis Master + Replica + Sentinel. 자동 Failover. |
3.6 메모리 기준 (동시 1,000 사용자)
| 항목 | 예상 메모리 |
|---|---|
| Cache | 1~2GB |
| Queue | 500MB~1GB |
| SocketIO | 200MB |
| 여유 | 1GB |
→ 권장 Redis 서버 RAM: 최소 4GB.
3.7 Pulumi·Ansible 연동
- Pulumi: Redis 전용 서버 1대 프로비저닝(방화벽: 6379는 App 서버 또는 Private Network만 허용). 출력:
redis_server_ip. - Ansible: Redis 서버에 Docker 등으로 Redis 설치(appendonly yes, requirepass, maxmemory). App 서버 play에서
common_site_config.json에 redis_cache/redis_queue/redis_socketio URL 설정(redis_host, redis_password 변수).
4. 각 고객(테넌트)별 저장공간 R2 분리
4.1 요구사항
- 각 고객별 저장공간은 R2로 분리 필요.
- 대상: 파일 첨부(attachments), 백업(backup), 로그/아카이브(선택).
- 서버 로컬 디스크 의존 제거, 버전닝/라이프사이클 정책 적용 가능.
4.2 분리 전략
| 방식 | 설명 | 적합성 |
|---|---|---|
| 방식 A — 버킷 1개 + 테넌트 prefix | 단일 R2 버킷 내 tenant_id=xxx/ 또는 attachments/xxx/ 경로로 구분. IAM/정책으로 tenant_id별 접근 제어. | 구현 단순, 비용·운영 부담 적음. |
| 방식 B — 테넌트별 전용 버킷 | 테넌트마다 R2 버킷 1개. 격리 최대, 버킷 수 증가. | Enterprise·규제 요구 시. |
권장: 초기에는 방식 A(단일 버킷 + 테넌트 prefix). 대형/Enterprise 고객은 방식 B 또는 전용 prefix 정책으로 선택 적용.
4.3 R2 경로 구조 예시 (방식 A)
- 기존:
prego-static-assets,prego-usage-raw(공통). - 테넌트별 저장공간:
prego-tenant-attachments(또는 기존 버킷 하위):tenant_id=<uuid>/site_name=/files/...— 첨부파일.- 백업:
prego-tenant-backups또는 동일 버킷 내backups/<tenant_id>/....
- Frappe에서 S3-compatible(R2) 연결 시
bucket+prefix(테넌트별) 설정.
4.4 프로비저닝 시 R2 반영
- 신규 테넌트 생성 시:
- R2 버킷이 이미 있으면 prefix만 정책 등록 또는 디렉터리 규칙 문서화.
- 테넌트 전용 버킷(방식 B)을 쓰는 경우: Pulumi 또는 API로 해당 테넌트용 버킷 생성, Frappe site config에 bucket/endpoint 반영.
- Control Plane/D1에 테넌트별
r2_bucket또는r2_prefix저장하여 라우팅·정책에서 사용.
4.5 라이프사이클·버전닝
- R2 버전닝 활성화(선택).
- 오래된 백업/아카이브는 라이프사이클 규칙으로 Archive 또는 삭제(예: 90일 이후).
5. 확장 SaaS 멀티테넌시 요약 (DB·Redis·Storage)
5.1 핵심 설계 원칙
- 최소 분리 필수: DB 분리
- 동시 1,000+ 기준: DB + Redis 분리
- 대규모 SaaS: DB HA + Redis HA + Storage(R2) 테넌트 분리 + App 수평 확장
- 테넌트 격리: DB 단위 격리(기본), 그룹 샤딩(확장), Enterprise 전용 DB·Redis(선택)
5.2 서비스 분리 대상
| 계층 | 내용 |
|---|---|
| DB Layer | MariaDB Primary + Replica (별도 서버). 기존 saas-db-separation-and-scaling-plan.md 참조. |
| Redis Layer | 별도 Redis 서버 → 단일 서버 1대 → 향후 Master+Replica+Sentinel. |
| Storage Layer | R2 — 각 고객(테넌트)별 저장공간 분리(prefix 또는 전용 버킷). 첨부·백업·아카이브. |
| App Cluster | Bench/Workers/Scheduler, Stateless, 세션·캐시는 Redis. |
5.3 복잡도 vs 안정성
| 구조 | 복잡도 | 안정성 | SaaS 적합성 |
|---|---|---|---|
| 통합(DB+Redis+Storage 한 서버) | 낮음 | 낮음 | 낮음 |
| DB 분리만 | 중간 | 높음 | 높음 |
| DB + Redis 분리 | 중간 | 매우 높음 | 매우 높음 |
| Full HA(DB+Redis HA + R2 테넌트 분리) | 높음 | 최고 | 대규모 SaaS |
6. 멀티테넌시 샤딩 전략 (DB)
- 테넌트 그룹 분산: 테넌트 ID 범위/해시로 DB Cluster 배정 (Group A→DB1, B→DB2, …).
- Enterprise 전용 DB: 대형 고객은 전용 DB Cluster. SMB는 Shared DB 그룹(샤딩).
- DB 라우팅: 새 테넌트 생성 시 가용 DB 그룹 선택(자동). 테넌트 이동은 원칙 금지, 필요 시 Migration Tool.
(상세는 saas-db-separation-and-scaling-plan.md §3.3, §8 참조.)
7. 운영/자동화 요구사항
7.1 테넌트 Provisioning 자동화
- DB 그룹 선택(또는 전용 DB)
- DB 사용자·권한,
bench new-site(원격 DB) - Redis 연결 정보 반영(common_site_config: redis_cache/queue/socketio)
- R2 테넌트 저장공간 생성(prefix 정책 또는 전용 버킷) 및 site config 반영
- 도메인/서브도메인 라우팅, 모니터링 대상 등록
7.2 모니터링
- DB: CPU, QPS, replication lag, slow query
- Redis: memory, evictions, ops/sec, connected clients
- App: latency, worker queue length, error rate
- R2: 버킷/prefix별 사용량(선택)
7.3 장애 대응
- DB Primary 장애 → Replica 승격
- Redis 장애 → Sentinel 자동 Failover(HA 구성 시)
- Scheduler 중복 방지: Redis lock 또는 단일 실행 보장
8. 용량 계획·전환 트리거
- 동시 1,000+: DB + Redis 분리 완료, R2 테넌트 저장공간 적용
- 동시 1,500~2,000: DB Replica 읽기 분산, Redis 모니터링 강화
- 테넌트 100+: DB 샤딩(그룹 분산), R2 prefix/버킷 정책 정리
- 대형 고객: Enterprise 전용 DB·Redis 옵션
9. 권장 실행 순서
우선순위 1 (즉시·단기)
- DB 분리 + Primary/Replica 설계 (현행 기획서 반영됨)
- Redis 별도 서버 분리 (1대, AOF, requirepass, App에서 원격 연결)
- R2 테넌트별 저장공간 설계(prefix 또는 버킷) 및 프로비저닝 시 반영
우선순위 2 (테넌트 100 도달 전)
- Redis HA(Sentinel) 도입
- 테넌트 그룹 샤딩(DB), tenant→db_group 라우팅
- R2 라이프사이클·버전닝 정책
우선순위 3 (대형 고객 확보 시)
- Enterprise 전용 DB·Redis
- 테넌트 전용 R2 버킷(방식 B) 옵션
10. 기존 기획서와의 연동
| 문서 | 연동 내용 |
|---|---|
| saas-db-separation-and-scaling-plan.md | Phase 1~3 DB 분리·확장. 여기에 Redis 서버 1대 추가 프로비저닝 및 R2 테넌트 prefix/버킷 정책을 확장 적용. |
| pulumi-ansible-step1-step2-plan.md | Pulumi에서 Redis 서버 리소스 추가, Ansible에서 redis_server role 및 App 측 redis_host/redis_password 설정. |
| ansible-implementation-plan.md | 변수: redis_host, redis_password, r2_bucket, r2_prefix(테넌트별). Redis 서버 그룹 prego_redis_servers. |
| provision-tenant.yml | resolve-server 출력에 redis_server_ip(선택). 테넌트 생성 시 R2 prefix/버킷 생성 또는 정책 등록 단계. |
11. 동시 3,000 기준 Redis 용량 계산 (운영 안전 산정)
Frappe는 Redis를 3용도로 사용한다: redis-queue(RQ 작업 큐, 폭주/적체 발생), redis-cache(캐시, 메모리 가장 많이 소모), redis-socketio(실시간 이벤트).
11.1 산정 전제
- 동시 3,000명 중 활동 사용자 비율 약 30%(실제 클릭/요청 발생).
- cache hit가 좋으면 DB/CPU 부하는 줄지만 Redis 메모리 증가.
- 파일/리포트/대량작업(급여·출결 마감)이 있는 날은 queue 급증.
11.2 권장 용량 (동시 3,000)
결론: 동시 3,000이면 Redis 총 RAM 16GB급 권장.
| Redis 역할 | 권장 maxmemory | 근거 |
|---|---|---|
| redis-cache | 8GB | 캐시가 가장 크게 증가(리스트/폼/권한/메타) |
| redis-queue | 4GB | 대량 작업 적체 시 안전 버퍼 |
| redis-socketio | 1~2GB | 연결/이벤트 규모에 따라 |
| OS/오버헤드 | 2GB+ | AOF, fragmentation, background save |
| 합계 | 15~16GB | 안정 운영 목표치 |
최소 8GB로도 구성 가능하나, 동시 3,000에서 eviction/queue 폭주 위험이 커서 운영 리스크가 큼.
11.3 Redis 정책 권장
| 용도 | maxmemory-policy |
|---|---|
| cache | allkeys-lru |
| queue | noeviction (큐 데이터 유실 시 장애로 직결) |
| socketio | volatile-lru 또는 allkeys-lru |
12. Redis Sentinel Docker 구성 참고 (Master + Replica + Sentinel 3)
Redis 전용 서버(또는 3노드)에 배치하는 기준 템플릿. 실구현 시 폴더 구조·설정은 이 섹션을 참고한다.
12.1 폴더 구조
redis-ha/ docker-compose.yml redis-master.conf redis-replica.conf sentinel1.conf, sentinel2.conf, sentinel3.conf .env12.2 .env
REDIS_PASSWORD=StrongRedisPassMASTER_NAME=mymasterQUORUM=212.3 redis-master.conf
bind 0.0.0.0port 6379requirepass StrongRedisPassmasterauth StrongRedisPassappendonly yesappendfsync everysec12.4 redis-replica.conf
bind 0.0.0.0port 6379requirepass StrongRedisPassmasterauth StrongRedisPassreplicaof redis-master 6379appendonly yesappendfsync everysec12.5 sentinel1.conf (2/3은 port만 26380, 26381 등으로 변경)
port 26379sentinel monitor mymaster redis-master 6379 2sentinel auth-pass mymaster StrongRedisPasssentinel down-after-milliseconds mymaster 5000sentinel failover-timeout mymaster 60000sentinel parallel-syncs mymaster 112.6 docker-compose.yml
version: "3.8"services: redis-master: image: redis:7 container_name: redis-master command: ["redis-server", "/usr/local/etc/redis/redis-master.conf"] volumes: - ./redis-master.conf:/usr/local/etc/redis/redis-master.conf:ro - redis-master-data:/data ports: - "6379:6379" restart: unless-stopped
redis-replica: image: redis:7 container_name: redis-replica command: ["redis-server", "/usr/local/etc/redis/redis-replica.conf"] volumes: - ./redis-replica.conf:/usr/local/etc/redis/redis-replica.conf:ro - redis-replica-data:/data restart: unless-stopped depends_on: - redis-master
sentinel1: image: redis:7 container_name: redis-sentinel-1 command: ["redis-sentinel", "/usr/local/etc/redis/sentinel.conf"] volumes: - ./sentinel1.conf:/usr/local/etc/redis/sentinel.conf:ro ports: - "26379:26379" restart: unless-stopped depends_on: - redis-master - redis-replica
sentinel2: image: redis:7 container_name: redis-sentinel-2 command: ["redis-sentinel", "/usr/local/etc/redis/sentinel.conf"] volumes: - ./sentinel2.conf:/usr/local/etc/redis/sentinel.conf:ro ports: - "26380:26379" restart: unless-stopped depends_on: - redis-master - redis-replica
sentinel3: image: redis:7 container_name: redis-sentinel-3 command: ["redis-sentinel", "/usr/local/etc/redis/sentinel.conf"] volumes: - ./sentinel3.conf:/usr/local/etc/redis/sentinel.conf:ro ports: - "26381:26379" restart: unless-stopped depends_on: - redis-master - redis-replica
volumes: redis-master-data: redis-replica-data:12.7 App(Frappe)에서 Sentinel 연결
- 옵션 A(권장/간단): HAProxy/Keepalived로
redis vip:6379구성, App은 VIP만 연결. - 옵션 B: App에서 Sentinel-aware 클라이언트 사용(구현 환경에 따라 상이).
운영 단순성 우선 시 옵션 A 권장.
13. DB + Redis + App 3-tier 전체 설계 (확장 SaaS 기준)
13.1 목표 구조
[ Load Balancer ] | v[ App Cluster ] - Nginx - Frappe (Gunicorn) - Workers (RQ) - Scheduler (단일 active) | +--> [ DB Layer ] MariaDB Primary + Replica (Read for reports) | +--> [ Redis Layer ] Redis HA (cache / queue / socketio) | +--> [ Storage Layer ] S3/R2 (files, backups, 테넌트별 분리)13.2 배치 원칙
| 계층 | 원칙 |
|---|---|
| App | 수평 확장(노드 추가) |
| DB | Primary 1 + Replica 1(필요 시 Read replica 추가) |
| Redis | HA(최소 2노드 + Sentinel 3) |
| Storage | S3/R2 분리(서버 로컬 디스크 의존 제거, 테넌트별 prefix/버킷) |
13.3 필수 분리 대상
- DB: 필수
- Redis: 동시 1,000+ 강력 권장, 동시 3,000이면 사실상 필수
- Storage(S3/R2): 테넌트 100+ 권장(백업/첨부/확장성/복구)
14. Worker 병목 제거 전략
동시 3,000에서 병목은 웹 요청보다 **백그라운드 작업(Queue)**에서 자주 발생한다.
14.1 병목 지표 (우선 확인)
- Queue length 증가(default/long/short)
- Job 대기시간 증가(특히 long)
- Worker CPU 80% 지속
- Redis used_memory 급증(큐 적체)
- MariaDB 쿼리 지연(Worker가 DB 과부하 유발)
14.2 실전 전략 (효과 큰 순서)
-
Queue 분리(Short/Default/Long) + 작업 분류
- 이메일/알림/가벼운 작업 → short
- 일반 백그라운드 → default
- 리포트/대량처리 → long
-
Worker 수평 확장
- App 노드별 worker 추가
- long worker는 별도 노드로 격리 권장
-
동시성(Worker concurrency) 튜닝
- DB가 버티는 범위에서만 concurrency 증가. 무리한 증가 시 DB/Redis 선행 부하.
-
Rate limit / Backpressure
- 특정 테넌트가 큐를 독점하지 못하도록 테넌트별 제한
- 대량 Import/급여는 “예약 실행(스케줄 윈도우)“로 분산
-
리포트/집계 최적화
- 무거운 리포트는 Replica에서 읽기 또는 결과 캐싱
- 반복 리포트는 미리 계산(Materialization) 전략
14.3 권장 운영 패턴 예시
- App 노드 3대
- Worker: 노드당 default 4, short 2, long 2 → 총 24 workers
- long queue는 야간/오프피크에 집중 실행
15. 바로 적용 체크리스트
- Redis를 cache/queue/socketio 논리 분리(DB index 분리 또는 인스턴스 분리)
- queue는
noeviction - Redis HA는 Sentinel + (가능하면) VIP/Proxy
- Worker는 long queue 격리
- 테넌트별 큐 독점 방지(레이트리밋)
16. 바로 구축용 템플릿·Runbook (별도 문서)
요청하신 1) Redis 3인스턴스(cache/queue/socketio) 완전 분리 + 각각 HA(Sentinel), 2) App 클러스터용 docker-compose + Frappe 설정 샘플, 3) Worker 튜닝/운영 Runbook, 4) Prometheus+Grafana 모니터링, 5) 테넌트별 리소스 관측성 설계를 바로 구축 가능한 형태로 정리한 패키지는 다음 문서에 수록한다.
포함 내용 요약:
- Redis HA:
infra/redis-ha/{cache,queue,socketio}/디렉터리 구조, 각 역할별 docker-compose·redis-master/replica·sentinel 설정 예시, VIP/Proxy(HAProxy+Keepalived) 권장. - App 클러스터:
common_site_config.json,.env, nginx default.conf 요지, web/socketio/scheduler/worker_short·default·long 구성의 docker-compose, 클러스터 운영 규칙(scheduler 단일·sites 공유). - Worker Runbook: short/default/long 큐 설계, 3노드×24 worker 배치 권장, 절대 금지 사항, 모니터링 트리거·테넌트 독점 방지·장애 대응 순서.
- 모니터링: Prometheus scrape(target: node, redis-cache/queue/socket, mysql, nginx), Alert rules(Queue eviction·메모리·백로그·MySQL·HTTP 5xx·지연), Grafana 대시보드 구조, 동시 3,000 알림 전략 표.
- 테넌트 관측성: tenant label·Custom metric(HTTP·Queue)·Grafana Top-N·Drilldown·Enterprise SLA, 자동 분리 기준, Basic/Pro/Enterprise 격리 수준.
실서비스에서는 각 Redis HA 세트를 서로 다른 VM/노드에 분산 배치하는 것을 권장한다.
17. 결론
- Redis도 별도 서버로 분리하여 App·Queue·Cache 격리 및 동시 1,000+ 대응. 동시 3,000은 Redis 16GB급·HA 권장.
- 각 고객(테넌트)별 저장공간은 R2로 분리 — 단일 버킷 + 테넌트 prefix 또는 테넌트 전용 버킷 정책으로 첨부·백업·아카이브 격리.
- DB 분리 + Redis 분리 + R2 테넌트 저장공간 분리 + **Worker 병목 제거(Queue 분리·수평 확장·레이트리밋)**를 적용하여 확장 SaaS(테넌트 100+) 및 동시 3,000 수준 Full HA 목표 구조를 만족시킨다.