Skip to content

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

[ 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_queueBackground 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 사용자)

항목예상 메모리
Cache1~2GB
Queue500MB~1GB
SocketIO200MB
여유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 핵심 설계 원칙

  1. 최소 분리 필수: DB 분리
  2. 동시 1,000+ 기준: DB + Redis 분리
  3. 대규모 SaaS: DB HA + Redis HA + Storage(R2) 테넌트 분리 + App 수평 확장
  4. 테넌트 격리: DB 단위 격리(기본), 그룹 샤딩(확장), Enterprise 전용 DB·Redis(선택)

5.2 서비스 분리 대상

계층내용
DB LayerMariaDB Primary + Replica (별도 서버). 기존 saas-db-separation-and-scaling-plan.md 참조.
Redis Layer별도 Redis 서버 → 단일 서버 1대 → 향후 Master+Replica+Sentinel.
Storage LayerR2 — 각 고객(테넌트)별 저장공간 분리(prefix 또는 전용 버킷). 첨부·백업·아카이브.
App ClusterBench/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 자동화

  1. DB 그룹 선택(또는 전용 DB)
  2. DB 사용자·권한, bench new-site (원격 DB)
  3. Redis 연결 정보 반영(common_site_config: redis_cache/queue/socketio)
  4. R2 테넌트 저장공간 생성(prefix 정책 또는 전용 버킷) 및 site config 반영
  5. 도메인/서브도메인 라우팅, 모니터링 대상 등록

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.mdPhase 1~3 DB 분리·확장. 여기에 Redis 서버 1대 추가 프로비저닝R2 테넌트 prefix/버킷 정책을 확장 적용.
pulumi-ansible-step1-step2-plan.mdPulumi에서 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.ymlresolve-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-cache8GB캐시가 가장 크게 증가(리스트/폼/권한/메타)
redis-queue4GB대량 작업 적체 시 안전 버퍼
redis-socketio1~2GB연결/이벤트 규모에 따라
OS/오버헤드2GB+AOF, fragmentation, background save
합계15~16GB안정 운영 목표치

최소 8GB로도 구성 가능하나, 동시 3,000에서 eviction/queue 폭주 위험이 커서 운영 리스크가 큼.

11.3 Redis 정책 권장

용도maxmemory-policy
cacheallkeys-lru
queuenoeviction (큐 데이터 유실 시 장애로 직결)
socketiovolatile-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
.env

12.2 .env

Terminal window
REDIS_PASSWORD=StrongRedisPass
MASTER_NAME=mymaster
QUORUM=2

12.3 redis-master.conf

bind 0.0.0.0
port 6379
requirepass StrongRedisPass
masterauth StrongRedisPass
appendonly yes
appendfsync everysec

12.4 redis-replica.conf

bind 0.0.0.0
port 6379
requirepass StrongRedisPass
masterauth StrongRedisPass
replicaof redis-master 6379
appendonly yes
appendfsync everysec

12.5 sentinel1.conf (2/3은 port만 26380, 26381 등으로 변경)

port 26379
sentinel monitor mymaster redis-master 6379 2
sentinel auth-pass mymaster StrongRedisPass
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 60000
sentinel parallel-syncs mymaster 1

12.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수평 확장(노드 추가)
DBPrimary 1 + Replica 1(필요 시 Read replica 추가)
RedisHA(최소 2노드 + Sentinel 3)
StorageS3/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 실전 전략 (효과 큰 순서)

  1. Queue 분리(Short/Default/Long) + 작업 분류

    • 이메일/알림/가벼운 작업 → short
    • 일반 백그라운드 → default
    • 리포트/대량처리 → long
  2. Worker 수평 확장

    • App 노드별 worker 추가
    • long worker는 별도 노드로 격리 권장
  3. 동시성(Worker concurrency) 튜닝

    • DB가 버티는 범위에서만 concurrency 증가. 무리한 증가 시 DB/Redis 선행 부하.
  4. Rate limit / Backpressure

    • 특정 테넌트가 큐를 독점하지 못하도록 테넌트별 제한
    • 대량 Import/급여는 “예약 실행(스케줄 윈도우)“로 분산
  5. 리포트/집계 최적화

    • 무거운 리포트는 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 목표 구조를 만족시킨다.
Help