English {#english}
Resource Optimization — Safe Adoption Plan
Purpose: Analyze resource-reduction policies (Redis tuning, Gunicorn max-requests, CDN/Static offload, Docker resource limits, MariaDB/App optimization) and plan stepwise adoption of only those that do not negatively impact existing Prego.
No code generation — analysis, applicability, prerequisites, and application order only.
References: redis-separation-pulumi-ansible-plan.md, saas-db-separation-and-scaling-plan.md, prego-docker-implementation-plan.md, mariadb-mycnf-optimization-plan.md, pregoi-infrastructure-r2-api-update-plan.md.
One-page summary: resource-optimization-quick-reference.md.
1. Scope and principles
- Noisy Neighbor: Prevent one tenant from monopolizing resources; use Docker cgroups (CPU/memory limits) and Control Plane–driven plan-based limits (Free/Pro/Enterprise).
- Principles: Keep existing provisioning, multitenancy, Stripe, Control Plane, Ansible/Docker flow unchanged. Prefer config/tuning over structural change; apply in stages with verification; make changes rollbackable via Ansible vars and overrides.
2. Policy analysis (summary)
- Redis: Structure (single Redis, DB 0/1/2/3) per redis-separation plan. Tuning only here: maxmemory, allkeys-lru; save/appendonly non-persistent for cache DB only (queue/socketio per ops policy). Apply: conf → Ansible/Redis role → restart.
- Gunicorn: max_requests 1000, jitter 100, timeout 120. Apply: common_site_config or Procfile; no behavior change.
- CDN/Static: Offload /assets (and optionally /files) to R2+CDN. Prerequisites: R2 upload path for site assets, assets_base_url support, Nginx redirect. Apply on staging first. Defer until prerequisites done.
- Docker limits: Plan-based CPU/memory (Hard + Soft), memswap_limit = memory limit. Control Plane passes plan_limits to Ansible; OOM and CPU throttle monitoring. Apply: docker-compose/Ansible deploy.resources; plan_limits per §4.4.
- MariaDB: Within scope of mariadb-mycnf-optimization-plan only. ProxySQL excluded for now.
- Excluded / deferred: PyPy, SocketIO separate container. Optional: Zuplo Edge Cache (short TTL, read-only APIs).
3–7. Order, config, consistency, isolation, summary
- §3 Application order: (1) Redis conf (2) Gunicorn (3) Docker limits (4) CDN/Static after prereqs (5) MariaDB my.cnf per existing plan. Optional: Edge Cache.
- §4 Config: Redis common_site_config + redis.conf; Gunicorn vars; CDN steps (assets_base_url, Nginx 301, Cloudflare TTL); §4.4 plan_limits table (Free/Pro/Enterprise CPU, memory Hard/Soft), Ansible integration, OOM/throttle alerts.
- §5 Consistency: Aligns with redis-separation, saas-db-separation, mariadb-mycnf, prego-docker, R2 plans; ProxySQL/D1 hybrid out of scope.
- §6 Isolation: Edge (Zuplo rate limits) → App (Docker cgroups, plan_limits) → DB (sharding/separate servers). Control Plane supplies plan_limits to Ansible.
- §7 Summary: Safe to apply now: Redis tuning (cache only non-persistent), Gunicorn, Docker plan limits + monitoring. After prereqs: CDN/Static. In-scope only: MariaDB my.cnf. Excluded: Redis migration, ProxySQL, PyPy, D1. Optional: Edge Cache, upgrade nudges.
Full tables (§1.1, §2.1–§2.7, §3, §4.1–§4.4, §5, §6) and exact wording are in the Korean section below.
한국어 {#korean}
리소스 사용량 절감 — 기존 시스템 영향 최소 적용 기획서
목적: 리소스 사용량을 줄이기 위한 정책들(Redis 통합·튜닝, Gunicorn max-requests, CDN/Static Offload, Docker 자원 제한, MariaDB/App 최적화 등)을 분석하고, 기존 Prego 시스템에 부정적 영향을 주지 않는 항목만 단계적으로 적용하기 위한 기획.
코드 생성 없음 — 분석·적용 가능 여부·선행 조건·적용 순서만 정의.
참조: redis-separation-pulumi-ansible-plan.md, saas-db-separation-and-scaling-plan.md, prego-docker-implementation-plan.md, mariadb-mycnf-optimization-plan.md, pregoi-infrastructure-r2-api-update-plan.md.
한 페이지 요약: resource-optimization-quick-reference.md.
1. 범위 및 원칙
1.1 Noisy Neighbor 방지·격리 목표 (SaaS 관점)
SaaS 환경에서 **한 테넌트가 자원을 독점해 다른 고객 서비스까지 느려지게 만드는 현상(Noisy Neighbor)**을 막기 위해, Docker 컨테이너 수준에서 CPU·메모리를 cgroups로 하드웨어적 격리하고, Control Plane(Ansible/Pulumi)이 패키지 등급(Free/Pro/Enterprise)에 따라 동적으로 제한값을 설정하는 것을 목표로 한다.
- 장점: 특정 테넌트의 부하가 호스트 전체로 전파되지 않음. 플랜별 공정한 자원 배분·예측 가능한 성능.
| 원칙 | 내용 |
|---|---|
| 기존 동작 유지 | 프로비저닝·멀티테넌시·Stripe·Control Plane·Ansible/Docker 배포 흐름을 바꾸지 않음. |
| 설정·튜닝 우선 | 구조 변경(테넌트별 Redis → 단일 Redis 등)은 이미 별도 기획(redis-separation)에 있으면 그대로 따르고, 본 문서는 튜닝·옵션 추가만 다룸. |
| 단계적 적용 | 적용 순서를 정하고, 각 단계 후 동작 검증(health check, 로그인, API) 후 다음 단계 진행. |
| 롤백 가능 | 설정 변경은 Ansible 변수·common_site_config·docker-compose 오버라이드로 관리해, 문제 시 이전 값으로 복귀 가능하게 함. |
2. 정책별 분석 및 적용 여부
2.1 Redis 통합·튜닝
| 항목 | 제안 | 기존 Prego | 영향·결론 |
|---|---|---|---|
| 구조 | 테넌트별 별도 Redis → 단일 Redis, Key Prefix(또는 DB 번호)로 분리 | redis-separation-pulumi-ansible-plan.md에서 이미 단일 Redis·DB 0/1/2/3(cache/queue/socketio) 목표. | 구조 변경은 해당 기획서대로 진행. 본 문서에서는 구조 변경 없이 기존(또는 전환 후) 단일 Redis에 대한 튜닝만 다룸. |
| redis.conf | maxmemory 2gb, maxmemory-policy allkeys-lru, save "", appendonly no | Redis 서버는 Pulumi·Ansible으로 배포. conf는 Ansible role에서 관리. | 적용 시 주의: save ""·appendonly no는 영속성 없음. cache DB(1)에는 적용 가능; **queue(2)·socketio(3)**는 작업 유실 시 재시도 가능한지 운영 정책에 따름. 권장: cache용 DB만 비영속, queue/socketio는 appendonly yes 또는 운영 정책 유지. |
| common_site_config | redis_cache/queue/socketio URL (redis:6379/0,1,2) | redis-separation에서 이미 단일 호스트·DB 번호 설계. | 정합. URL은 Ansible·redis-separation 기획에 맞게 유지. |
적용 가능(기존 시스템에 영향 없음)
- Redis conf:
maxmemory,maxmemory-policy allkeys-lru적용.save ""·appendonly no는 cache DB에만 적용하거나, queue/socketio는 기존 정책 유지. - 적용 순서: redis.conf 수정 → Ansible/Redis role 반영 → Redis 재시작 → bench restart.
제외·별도 기획
- 테넌트별 Redis → 단일 Redis 이전 자체: redis-separation Runbook·기획서에 따름.
2.2 Gunicorn max-requests
| 항목 | 제안 | 기존 Prego | 영향·결론 |
|---|---|---|---|
| 설정 | gunicorn_max_requests 1000, max_requests_jitter 100, timeout 120, workers 2 | Frappe/Docker 이미지·Ansible에서 gunicorn 실행. common_site_config 또는 Procfile. | 동작 변경 없음. Worker가 N회 요청 처리 후 재시작되어 메모리 초기화. jitter로 동시 재시작 방지. |
적용 가능(기존 시스템에 영향 없음)
- common_site_config.json 또는 Procfile에
gunicorn_max_requests,gunicorn_max_requests_jitter,gunicorn_timeout,gunicorn_workers반영. - 값: max_requests 1000, jitter 100, timeout 120. workers 수는 Ansible 변수·플랜별로 유지.
- 적용 순서: 설정 추가 → 이미지/Ansible 배포 → bench/컨테이너 재시작.
2.3 CDN/Static Offloading (R2 + CDN)
| 항목 | 제안 | 기존 Prego | 영향·결론 |
|---|---|---|---|
| 목표 | /assets, /files를 Frappe가 아닌 R2+CDN에서 서빙. App 서버 트래픽·메모리 감소 | prego-static-assets R2 버킷·static.pregoi.com(폰트 등) 이미 사용. Frappe 사이트의 /assets(빌드 에셋)·/files(업로드 파일)는 현재 App에서 서빙. | 구조적 변경. Frappe의 assets_base_url·Nginx 리다이렉트·R2에 사이트별 assets 업로드 파이프라인이 필요. 잘못 적용 시 404·빈 화면. |
적용 가능(선택·선행 조건 필요)
- 선행 조건: (1) Frappe 빌드 에셋을 R2에 업로드하는 경로(bench build 후 sync 또는 CI) 확보. (2) R2 버킷(또는 prego-static-assets 내 경로)에 사이트/테넌트별 prefix 정책. (3)
serve_default_site·assets_base_url지원 여부 확인(Frappe 버전). - 적용 범위: 우선 빌드 에셋(/assets)만 CDN으로 오프로드;
/files(사용자 업로드)는 보안·경로 정책 검토 후 단계적. - Cloudflare 캐시: cdn.pregoi.com(또는 static.pregoi.com)/assets/* TTL 1달; API는 bypass.
- 영향 최소화: 기존 URL이 동작하도록 Nginx에서 301 리다이렉트로 전환하면, 캐시 미스 시에도 R2로 fallback 가능.
제외(본 단계)
- R2 업로드·assets_base_url 파이프라인 없이 Nginx만 리다이렉트하면 404 위험. 선행 작업 완료 후 적용.
2.4 Docker 자원 제한 (Soft/Hard·패키지별 동적 설정)
| 항목 | 제안 | 기존 Prego | 영향·결론 |
|---|---|---|---|
| limits/reservations | 플랜별 CPU·Memory limits (Free 1GB, Pro 2GB, Enterprise 8GB 등) | prego-docker·Ansible으로 컨테이너 배포. 현재 제한 없거나 role 기본값. | 추가만 하면 됨. 기존 동작은 유지; 한 테넌트가 호스트를 독점하는 것만 방지. |
| Soft vs Hard | Hard Limit: 최대 사용 상한(초과 시 OOM·스로틀). Soft(Reservation): 최소 보장 메모리. | — | 장점: Soft로 최소 성능 보장, Hard로 Noisy Neighbor 상한 차단. |
| memswap_limit | 스왑 사용 상한을 메모리 limit과 동일하게 두어 스왑으로 인한 성능 저하·불확실성 방지 | — | 장점: 컨테이너가 스왑에 과도 의존하지 않도록 예측 가능한 성능 유지. |
| Control Plane 연동 | 테넌트 생성·업그레이드 시 패키지(plan)에 따라 Ansible에 plan_limits[user_plan] 전달 → 컨테이너 memory·memory_reservation·cpus·memswap_limit 동적 설정 | 프로비저닝 시 Ansible에 tenant_id·site 등 전달. | 장점: 플랜 변경 시 재배포만으로 자원 한도 자동 반영. |
적용 가능(기존 시스템에 영향 없음)
- docker-compose 또는 Ansible
community.docker.docker_container에서memory(Hard),memory_reservation(Soft),cpus,memswap_limit(메모리 limit과 동일 권장) 설정. - 플랜별 수치: Ansible 변수
plan_limits[user_plan]로 분기. Free/Pro/Enterprise 예시는 §4.4 표 참조. - OOM 모니터링:
docker events --filter event=oom또는 Prometheus cAdvisor. OOM 사전 예방:container_memory_usage_bytes가 limit 대비 90% 도달 시 Alertmanager → Control Plane 알림(§16 연계). - 주의: limit을 너무 낮게 주면 정상 부하에서도 OOM 가능. Free 플랜은 최소 512MB~1GB 등 운영 정책에 맞게 설정.
Over-provisioning vs 격리 (운영 팁)
- Shared 노드(Free/Pro): 실제 물리 자원보다 더 많은 컨테이너를 띄울 수 있되(Over-provisioning),
memory_reservation으로 각 테넌트 최소 성능 보장. - Dedicated 노드(Enterprise): 다른 테넌트와 섞이지 않도록 Docker Node Label·Constraint로 전용 Hetzner 서버에만 배치하는 옵션을 둘 수 있음(saas-db-separation·Placement 정책과 연계).
실시간 모니터링·스로틀링 (장점)
- OOM Kill 사전 방지: Prometheus가
container_memory_usage_bytes를 수집하고, limit 대비 90% 도달 시 Alertmanager로 알림 → 운영자 또는 Control Plane이 대응(재시작·업그레이드 유도). - CPU Throttling 감지:
container_cpu_cfs_throttled_seconds_total메트릭으로, CPU 제한 때문에 성능이 저하되는 테넌트를 식별 → 상위 플랜 업그레이드 유도 또는 limit 조정 검토. - 자동 업그레이드 유도(선택): 자원 부족이 잦은 테넌트에 Zuplo 등을 통해 상위 패키지 업그레이드 안내·리다이렉트 정책을 둘 수 있음(비즈니스 정책 범위).
2.5 MariaDB·ProxySQL·Buffer Pool
| 항목 | 제안 | 기존 Prego | 영향·결론 |
|---|---|---|---|
| Connection Pooling (ProxySQL) | 테넌트당 커넥션 대신 풀링, 메모리 500MB→100MB 수준 절감 | saas-db-separation-and-scaling-plan.md, MariaDB 별도 서버. ProxySQL은 별도 도입 검토. | 구조·동작 변경. 연결 방식 변경으로 인한 타임아웃·락 이슈 가능. |
| table_definition_cache | 메타데이터 캐시 타이트하게 | mariadb-mycnf-optimization-plan.md에서 my.cnf 튜닝. | 기존 기획서에서 다룸. 본 문서에서는 my.cnf 범위 내만 권장; table_definition_cache는 조정 시 성능 모니터링 필요. |
적용 가능(기존 시스템에 영향 없음)
- MariaDB my.cnf 튜닝만: 이미 mariadb-mycnf-optimization-plan에 있으면 해당 계획 따름. 신규 변경은 buffer_pool·connection 수 등만 조정하고, table_definition_cache 등은 보수적 유지.
제외(본 단계)
- ProxySQL 도입: 별도 기획·POC 후 적용.
- D1(SQLite) 하이브리드: Frappe 코어 수정 필요, 장기 검토.
2.6 Frappe App 기타 (PyPy·SocketIO 분리)
| 항목 | 제안 | 영향·결론 |
|---|---|---|
| PyPy | CPython 대신 PyPy로 메모리·속도 개선 | 라이브러리·C 확장 호환성 리스크. 제외 — 기존 시스템 영향 큼. |
| SocketIO 별도 컨테이너 | Node.js 등으로 분리해 App 부하 감소 | frappe-socketio는 이미 별도 프로세스. 별도 컨테이너로 분리 시 네트워크·배포 변경. 단계적 검토 — 우선 제외. |
2.7 Edge Cache (Zuplo)
| 항목 | 제안 | 영향·결론 |
|---|---|---|
| 단순 조회·대시보드 | Frappe까지 가지 않고 Zuplo Edge Cache에서 응답 | API별 캐시 가능 여부·무효화 정책 정의 필요. 잘못 적용 시 stale 데이터. 선택 — 특정 읽기 전용 API만 TTL 짧게 적용 후 검토. |
적용 가능(선택)
- GET 특정 경로(예: 대시보드 집계)만 짧은 TTL(예: 60초) Edge Cache. 무효화 정책 명시. 본 문서에서는 우선순위 낮음으로 두고, 나중에 API 목록 정리 후 적용.
3. 적용 순서 요약 (기존 시스템 영향 없음 위주)
| 순서 | 항목 | 선행 조건 | 비고 |
|---|---|---|---|
| 1 | Redis conf 튜닝 | Redis 서버 배포 완료(redis-separation 또는 현행). | maxmemory, allkeys-lru. save/appendonly는 cache DB만 비영속 또는 유지. |
| 2 | Gunicorn max-requests·jitter·timeout | common_site_config 또는 Procfile 배포 경로 확보. | 설정 추가 후 재시작. |
| 3 | Docker 자원 제한 | Ansible·compose에서 deploy.resources 적용 가능. | 플랜별 변수화. OOM 모니터링 추가. |
| 4 | CDN/Static Offload | R2에 Frappe 사이트 assets 업로드 경로·assets_base_url 지원 확인. | Nginx 301 + Cloudflare 캐시. 적용 시 Staging에서 먼저 검증. |
| 5 | MariaDB my.cnf | mariadb-mycnf-optimization-plan 참조. | 기존 기획 범위 내만. |
| (선택) | Edge Cache (Zuplo) | 캐시할 API 목록·TTL·무효화 정책. | 읽기 전용·짧은 TTL부터. |
4. 우선순위별 구체 설정 요약 (참고)
아래는 설계·Ansible/이미지 반영 시 참고용. 코드/설정 파일 생성은 별도 작업.
4.1 Redis (단일 인스턴스·DB 번호)
- common_site_config.json: redis_cache → redis://host:6379/1, redis_queue → /2, redis_socketio → /3. (redis-separation 기획과 동일.)
- redis.conf: maxmemory 2gb, maxmemory-policy allkeys-lru. save ""·appendonly no는 캐시 DB만 또는 운영 정책에 따라 queue/socketio는 유지.
4.2 Gunicorn
- common_site_config.json: gunicorn_workers, gunicorn_max_requests(1000), gunicorn_max_requests_jitter(100), gunicorn_timeout(120).
- 또는 Procfile: —max-requests 1000 —max-requests-jitter 100 —timeout 120.
4.3 CDN/Static (R2)
- Step 1: common_site_config에 serve_default_site false, assets_base_url https://cdn.pregoi.com (또는 static.pregoi.com).
- Step 2: Nginx에서 /assets (및 선택 /files) → 301 to CDN URL.
- Step 3: Cloudflare 캐시 규칙 — cdn./assets/ TTL 1달; API bypass.
4.4 Docker 자원 제한 (패키지별·Soft/Hard)
패키지별 자원 할당 예시
| 패키지 | CPU (Core) | 메모리 (Hard Limit) | 메모리 (Reservation/Soft) |
|---|---|---|---|
| Free | 0.5 | 512MB ~ 1GB* | 256MB ~ 512MB |
| Pro | 1.5 | 2GB | 1GB |
| Enterprise | 4.0 | 8GB | 4GB |
* Free는 운영 정책에 따라 512MB(경량) 또는 1GB(권장 최소) 선택.
- Ansible 연동: Control Plane이 테넌트 생성·업그레이드 시
user_plan(또는 plan_tier)을 Ansible 변수로 전달.plan_limits[user_plan].mem_limit,plan_limits[user_plan].mem_soft,plan_limits[user_plan].cpu_limit으로docker_container의memory,memory_reservation,cpus설정. memswap_limit은 메모리 Hard limit과 동일하게 두어 스왑으로 인한 성능 저하 방지. - OOM 감지: docker events 또는 cAdvisor → Prometheus. 사전 알림:
container_memory_usage_bytes / limit≥ 90% 시 Alertmanager → §16 Observability 연계. - CPU 스로틀 감지:
container_cpu_cfs_throttled_seconds_total수집 → 제한에 자주 걸리는 테넌트 식별.
5. 기존 기획서와의 정합성
| 기존 기획서 | 본 문서와의 관계 |
|---|---|
| redis-separation-pulumi-ansible-plan | Redis 구조(단일·DB 번호)는 그대로 따름. 본 문서는 conf 튜닝만 추가. |
| saas-db-separation-and-scaling-plan | DB 서버 분리·Phase 유지. ProxySQL·D1 하이브리드는 본 문서에서 제외. |
| mariadb-mycnf-optimization-plan | my.cnf 튜닝은 해당 기획 범위. 본 문서는 “기존 계획 내에서만” 적용. |
| prego-docker-implementation-plan | 이미지·compose에 Gunicorn 설정·자원 제한 반영 시 해당 기획과 통합. |
| pregoi-infrastructure-r2-api-update-plan | R2·static.pregoi.com 이미 사용. Frappe 사이트 assets CDN은 추가 도메인/경로 정책만 정합. |
6. 격리 계층 요약 (Noisy Neighbor 방지)
자원 격리는 계층별로 적용하면 효과가 크다.
| 계층 | 수단 | 역할 |
|---|---|---|
| Edge | Zuplo | API 호출 횟수(Rate Limit)·테넌트별 제한으로 진입 단계에서 과부하 차단. |
| App Server | Docker (cgroups) | CPU·메모리 Hard/Soft 제한으로 컨테이너 간 물리적 격리. 패키지별 동적 설정. |
| Database | Sharding·별도 서버 | saas-db-separation 등으로 데이터·I/O 간섭 차단. |
Control Plane은 프로비저닝·플랜 변경 시 Ansible에 plan_limits를 전달하여 App 계층 자원 제한을 동적으로 적용한다.
7. 요약
- 즉시 적용 가능(기존 동작 유지): Redis conf(maxmemory, allkeys-lru, cache DB만 비영속 선택), Gunicorn max-requests·jitter·timeout, Docker 플랜별 자원 제한(Soft/Hard·memswap)·OOM·CPU 스로틀 모니터링. Control Plane이 plan에 따라 Ansible에 plan_limits 전달.
- 선행 조건 후 적용: CDN/Static Offload(R2 업로드·assets_base_url·Nginx 리다이렉트).
- 기존 기획 범위 내만: MariaDB my.cnf.
- 제외·장기 검토: Redis 구조 이전(별도 기획), ProxySQL, PyPy, D1 하이브리드, SocketIO 별도 컨테이너. 선택: Zuplo Edge Cache(API별·짧은 TTL), 자원 부족 시 상위 플랜 업그레이드 유도 정책.
이 순서와 범위로 적용하면 리소스 사용량을 줄이면서도 Noisy Neighbor를 억제하고 기존 시스템 동작에 부정적 영향을 주지 않는다.