Skip to content

English {#english}

SaaS Infrastructure — DB Separation and Usage-Based Scaling

Purpose: Define a strategy where Frappe is designed to use MariaDB on a separate DB server, Pulumi provisions DB servers based on usage monitoring, and Ansible performs installation and connectivity.
No code generation — planning, phases, and ownership only.

References: pulumi-ansible-step1-step2-plan.md, ansible-implementation-plan.md, intelligent-automation-implementation-plan.md.


1. Project Goals

GoalDescription
Stable SaaS operationsClear failure and scaling units by separating App and DB servers.
Minimize initial cost and complexityPhase 1 starts with a single App server + single DB server.
Scalable architecturePulumi adds DB servers and Replicas based on usage monitoring; Ansible installs and connects.
Avoid DB bottlenecksDefine scaling criteria and move in steps to Phase 2 (Replica) and Phase 3 (sharding).

2. Strategy Summary

PhaseDB strategyOwnerPurpose
Phase 11 App server + 1 DB server (single MariaDB)Pulumi: provision both. Ansible: install MariaDB on DB server, install Frappe on App server and connect to remote DB.Simplicity and cost.
Phase 2Primary DB + Replica DBPulumi: provision Replica based on usage/metrics. Ansible: install Replica and set up replication.Read scaling and resilience.
Phase 3Multitenant sharding (or dedicated DB for large customers)Pulumi: provision additional DB servers by metrics/policy. Ansible: install new DB and connect Frappe site routing.Large-scale SaaS scaling.

2.1 ⚠️ DB Server Count ≠ Customer (Tenant) Count

DB servers are not “one per customer.” One DB server serves thousands to tens of thousands of tenants in a shared multitenancy model.

PhaseDescription
Phase 1One DB server with a single MariaDB; only DB (schema) is per tenant. Hundreds to thousands of tenants share the same DB server.
Phase 2Two DB servers (Primary + Replica). Tenant count unchanged; Replica is for read distribution and failover, so server count becomes 2.
Phase 3 shardingWhen one Primary is overloaded, split tenants into groups across a small number of DB servers. E.g. 100k tenants → 5–10 DB servers (10k–20k per server). Not 100k servers.
Dedicated DB for large customersOption for some customers (e.g. Enterprise) to use a dedicated DB server; higher cost, applied selectively.

Summary: Even with 100k customers, Phase 1–2 use 1–2 DB servers; Phase 3 uses a small number (e.g. 5–20). DB server count does not scale linearly with customer count.

2.2 Redis on Separate Server and Per-Tenant R2 Storage (Extension)

For 100+ tenants and 1,000+ concurrent users, the following are included as extension planning.

ItemContentDetail
Redis on separate serverMove Redis off the App server to a dedicated Redis server (or cluster) for cache/queue/socketio. Reduces impact of queue/cache spikes on App; enables future Redis HA (Sentinel).saas-expanded-multitenancy-redis-storage-plan.md §3
Per-tenant R2 storageIsolate attachments, backups, archives per tenant in R2. Single bucket + tenant prefix (e.g. tenant_id=xxx/) or per-tenant bucket policy. On provisioning, create R2 prefix/bucket and reflect in site config.Same plan §4
  • Target architecture: App cluster → DB layer (separate server) + Redis layer (separate server) + Storage layer (R2, per-tenant).
  • Order: After DB separation (Phase 1), apply Redis separation and R2 per-tenant storage design. Full HA, sharding, Redis HA: see saas-expanded-multitenancy-redis-storage-plan.md.

3. Architecture Roadmap

3.1 Phase 1 — Initial Operation (Single DB-Separated Setup)

Layout: App Server (Bench/Frappe, Redis, Workers) → DB Server (MariaDB single primary, remote).

Requirements: <1,000 concurrent users; <50 tenants. Frappe bench new-site uses --db-host, --db-port to point at the DB server; per-tenant DB (schema) on the same MariaDB.

Pulumi: Provision 1 App server and 1 DB server (Hetzner; firewall: 3306 on private network or App IP only); output app_server_ip, db_server_ip. Ansible: On DB server — install MariaDB, allow remote access, enable binlog for Phase 2. On App server — install Docker, Frappe bench, Redis, Workers; set db_host, db_port, db_root_password and use bench new-site --db-host etc. Private network for DB traffic.

3.2 Phase 2 — Higher Traffic (Primary + Replica)

Trigger: 1,500–2,000+ concurrent users; DB CPU >70% for 10 min; more read/report load; 70–100 tenants.

Layout: App servers → Primary DB; Replica DB (read-only) replicates from Primary; read/reports can use Replica.

Pulumi: Provision Replica server when metrics hit expansion criteria. Ansible: Primary already has binlog and replication user; Replica: MariaDB, CHANGE MASTER TO, START SLAVE, read-only. App or proxy can route read-only queries to Replica.

3.3 Phase 3 — Large-Scale (Multitenant Sharding)

Trigger: 100+ tenants; heavy load from large customers; 3,000+ concurrent users.

Strategy: Shard by DB (tenant groups A/B/C → different DB servers); optional dedicated DB for large/Enterprise customers. Pulumi provisions extra DB servers; Ansible installs and configures; tenant–DB mapping in Control Plane/D1; no cross-DB joins.


4–12. Pulumi Scope, Ansible Roles, Scaling Metrics, Sharding Principles, Complexity, Integration, Summary, Conclusion

  • §4: Pulumi provisions App + DB servers in Phase 1; usage monitoring feeds expansion (Replica/shard). §5: Ansible groups prego_db_servers (MariaDB, remote access, binlog) and prego_nodes (Frappe with db_host/db_port/db_root_password); bench new-site --db-host for remote DB. §6: Primary/Replica Docker design (server-id, log-bin, replication user). §7: Scaling criteria (DB CPU >70%, query latency, connections, slow queries). §8: Sharding principles (tenant per DB, no cross-DB join). §9: Complexity by phase. §10: Links to pulumi-ansible, ansible-implementation, intelligent-automation, provision-tenant, saas-expanded-multitenancy. §11: Summary (remote DB, Pulumi+Ansible, Phase 1 binlog prep, private network; onboarding DB assignment, resource limits, log integration). §12: Conclusion — DB separation, Pulumi for provisioning and scaling, Ansible for install and connectivity.

Full detail for §§4–12 is in the Korean section below.


한국어 {#korean}

SaaS 인프라 기획서 — DB 분리·사용량 기반 확장

목적: Frappe를 별도 DB 서버에 설치된 MariaDB와 연동하도록 설계하고, Pulumi로 DB 서버를 사용량 모니터링에 따라 프로비저닝하며, Ansible으로 실제 설치·연동을 수행하는 전략을 정의한다.
코드 생성 없음 — 기획·단계·책임만 정리.

참조: pulumi-ansible-step1-step2-plan.md, ansible-implementation-plan.md, intelligent-automation-implementation-plan.md.


1. 프로젝트 목표

목표설명
안정적인 SaaS 운영 구조App 서버와 DB 서버 분리로 장애·확장 단위를 명확히 함.
초기 비용·복잡도 최소화Phase 1은 단일 App 서버 + 단일 DB 서버로 시작.
확장 가능한 아키텍처사용량 모니터링에 따라 Pulumi가 DB 서버·Replica를 추가하고, Ansible이 설치·연동.
DB 병목 사전 방지확장 기준 지표를 정의하고, Phase 2(Replica)·Phase 3(샤딩)으로 단계적 전환.

2. 핵심 전략 요약

단계DB 전략담당목적
Phase 1App 서버 1대 + DB 서버 1대 (단일 MariaDB)Pulumi: 두 서버 프로비저닝. Ansible: DB 서버에 MariaDB 설치, App 서버에 Frappe 설치 후 원격 DB 연동.단순성, 비용 절감.
Phase 2Primary DB + Replica DBPulumi: 사용량·지표에 따라 Replica 서버 프로비저닝. Ansible: Replica 설치·복제 연동.읽기 확장, 안정성.
Phase 3멀티테넌트 샤딩(또는 대형 고객 전용 DB)Pulumi: 지표·정책에 따라 추가 DB 서버 프로비저닝. Ansible: 새 DB 설치·Frappe site 라우팅 연동.대규모 SaaS 확장.

2.1 ⚠️ DB 서버 수 ≠ 고객사(테넌트) 수 — 비용·규모 정리

DB 서버는 “고객사 1개당 1대”가 아닙니다. 한 DB 서버 한 대가 수천~수만 테넌트를 함께 담당하는 공유 멀티테넌시 구조입니다.

구분설명
Phase 1DB 서버 1대 안에 MariaDB 하나를 두고, 테넌트마다 DB(schema)만 분리. 수백~수천 테넌트가 동일 DB 서버를 공유.
Phase 2DB 서버 2대(Primary + Replica). 담당 테넌트 수는 그대로. Replica는 읽기 분산·장애 대응용이라 서버 수만 2로 늘어남.
Phase 3 샤딩한 Primary가 감당하기 어려울 때 테넌트를 그룹으로 나누어 소수의 DB 서버에 분산. 예: 10만 테넌트 → DB 서버 510대(한 대당 12만 테넌트). 10만 대가 아님.
대형 고객 전용 DBEnterprise 등 일부 고객만 전용 DB 서버를 쓰는 옵션. 그만큼 비용이 들지만, 선택적 적용.

요약: 10만 고객사라도 Phase 1~2에서는 DB 서버 1~2대, Phase 3에서도 소수(예: 5~20대) 수준으로 설계하면 됩니다. 고객사 수만큼 DB 서버가 선형으로 늘어나지는 않습니다.


2.2 Redis 별도 서버 분리 · 각 고객별 저장공간 R2 분리 (확장)

테넌트 100+ 및 동시 1,000+ 사용자 대응을 위해 아래 두 가지를 확장 기획으로 반영한다.

항목내용상세 기획서
Redis 별도 서버 분리Redis를 App 서버에서 분리하여 전용 Redis 서버(또는 클러스터)에서 cache/queue/socketio 제공. Queue·Cache 폭주 시 App 영향 최소화, 향후 Redis HA(Sentinel) 확장.saas-expanded-multitenancy-redis-storage-plan.md §3
각 고객(테넌트)별 저장공간 R2 분리첨부파일·백업·아카이브를 테넌트 단위로 R2에서 격리. 단일 버킷 + 테넌트 prefix(예: tenant_id=xxx/) 또는 테넌트 전용 버킷 정책. 프로비저닝 시 R2 prefix/버킷 생성·site config 반영.동일 기획서 §4
  • 목표 아키텍처: App Cluster → DB Layer(별도 서버) + Redis Layer(별도 서버) + Storage Layer(R2, 테넌트별 분리).
  • 실행 순서: DB 분리(Phase 1) 완료 후 Redis 분리·R2 테넌트 저장공간 설계 적용. Full HA·샤딩·Redis HA는 saas-expanded-multitenancy-redis-storage-plan.md 참조.

3. 아키텍처 로드맵


3.1 Phase 1 — 초기 운영 (DB 분리형 단일 구성)

구성

[ App Server ] [ DB Server ]
├── Bench (Frappe) ──────► └── MariaDB (Single Primary)
├── Redis (원격 호스트로 연결)
└── Workers

요건

  • 동시 사용자: 1,000 이하
  • 테넌트: 50개 이하
  • 연동 방식: Frappe bench new-site--db-host, --db-port 등으로 DB 서버 지정. 동일 MariaDB 내에서 테넌트별 DB(schema)만 분리(Frappe Multi-tenancy).

Pulumi 역할

  • App 서버 1대 프로비저닝 (기존과 유사: Hetzner Server, 방화벽, 관리용 DNS).
  • DB 서버 1대 프로비저닝 추가: 별도 Hetzner Server, 방화벽(3306은 Private Network 또는 App 서버 IP만 허용), 동일 리전·Private Network 권장.
  • 출력: app_server_ip, db_server_ip (또는 기존 server_ip를 App 전용으로 하고 DB용 추가).
  • 사용량 모니터링: Control Plane·server_metrics 수집 경로와 연동 가능하도록 스택/라벨 정리(Phase 2 트리거용).

Ansible 역할

  • DB 서버 대상: MariaDB 설치(Docker 또는 네이티브), root 비밀번호 설정, 원격 접속 허용(예: bind-address, App 서버 IP 허용), 필요 시 binlog 활성화(Phase 2 대비). 성능 튜닝: 8GB~16GB RAM 환경·Frappe 특화 my.cnf 파라미터는 mariadb-mycnf-optimization-plan.md 참조.
  • App 서버 대상: Docker, Frappe bench, Redis, Workers 설치. DB 연결 정보: db_host={{ db_server_ip }}, db_port, db_root_password 등으로 별도 DB 서버 지정. bench new-site--db-host 등 사용.
  • 프로비저닝 워크플로: 인벤토리에 App 서버·DB 서버 그룹 분리, 또는 순차 play(먼저 DB 서버 설정, 다음 App 서버에 DB 호스트 변수 전달).

이유

  • 다중 DB(Replica/샤딩)는 초기에는 과도한 복잡성. DB만 분리해도 운영·백업·확장 시 DB 서버만 스케일 업/아웃 가능.
  • Private 네트워크 설계로 DB 트래픽이 공인 망을 타지 않도록 함.

3.2 Phase 2 — 트래픽 증가 (Primary + Replica)

트리거 조건 (사용량 모니터링 기반)

  • 동시 사용자 1,500~2,000 이상
  • DB CPU 70% 이상 지속(예: 10분)
  • 리포트·읽기 쿼리 증가
  • 테넌트 70~100개 근접

구성

┌─────────────────┐
│ Replica DB │ (읽기/리포트)
│ (Read-only) │
└────────▲─────────┘
│ 복제
[ App Servers ] ────────────►│ Primary DB
(Bench → db_host) └─────────────────┘

Pulumi 역할

  • 사용량 모니터링 연동: D1 server_metrics·Hetzner 메트릭 또는 에이전트에서 DB 서버 CPU·연결 수·Slow Query 등 수집. 확장 기준 충족 시 Replica 서버 프로비저닝 트리거(수동 또는 decidePlacement 확장 로직).
  • Replica용 Hetzner Server 1대 추가 프로비저닝, Private Network으로 Primary와 동일 VPC.

Ansible 역할

  • Primary DB 서버: 이미 binlog·replication user 설정되어 있음(Phase 1에서 준비). 필요 시 Ansible로 replication user 재확인.
  • Replica DB 서버: MariaDB 설치, CHANGE MASTER TO(Primary 호스트·로그·위치), START SLAVE. Read-only 설정.
  • App 서버: 읽기 전용 쿼리만 Replica로 보내는 설정은 Frappe/설정 또는 프록시 레이어로 구현. Ansible은 필요 시 connection string·설정 파일 배포.

목적

  • 읽기 트래픽 분산, 장애 시 Replica 승격(Failover), SLA 개선.

3.3 Phase 3 — 대규모 확장 (멀티테넌트 샤딩)

트리거 조건

  • 테넌트 100개 이상
  • 특정 대형 고객 DB 부하 집중
  • 동시 사용자 3,000+ 이상

전략 요약

방식설명
DB 단위 샤딩Tenant Group A → DB Server 1, B → 2, C → 3. 테넌트별 DB 분산, 대형 고객 전용 DB 가능.
대형 고객 전용 DBSMB 테넌트 → Shared DB, Enterprise 테넌트 → Dedicated DB.

Pulumi 역할

  • 확장 지표·정책에 따라 추가 DB 서버 프로비저닝. 테넌트 배치(어느 DB에 넣을지)는 Control Plane·D1과 연동.

Ansible 역할

  • 새 DB 서버에 MariaDB 설치·보안 설정.
  • App 서버(또는 라우팅 레이어)에서 테넌트별 DB 호스트 매핑 반영. Cross-DB Join 금지 원칙 유지.

4. Pulumi — DB 서버 및 사용량 모니터링

4.1 Phase 1에서의 Pulumi 범위

리소스설명
App 서버기존과 동일. Hetzner Server, 방화벽(22), 관리용 DNS.
DB 서버Hetzner Server 1대 추가. 이미지 Ubuntu 22.04/24.04. 방화벽: SSH(22), 3306은 Private Network 또는 App 서버 출신만 허용. 동일 location·Private Network 권장.
출력app_server_ip(또는 server_ip), db_server_ip. Private Network IP 있으면 함께 출력.

4.2 사용량 모니터링과의 연동

항목설명
수집 경로Hetzner API 메트릭, 또는 DB 서버에 에이전트/Cron으로 CPU·메모리·연결 수·Slow Query 수집 → Control Plane API 또는 Worker → D1 server_metrics(또는 전용 db_metrics) 저장.
확장 기준 지표DB CPU > 70% 지속 10분, 평균 쿼리 응답 > 100ms, Connection 수 80% 이상, Slow Query 증가 추세. (intelligent-automation §3.4, §3.5와 연동)
Pulumi 트리거수동: 운영자가 지표 확인 후 Replica/샤드 서버 추가 시 pulumi up 또는 별도 스택. 자동: decidePlacement 확장 시 새 DB 서버 리소스 추가 후 Ansible/워크플로로 설치·연동.

4.3 Phase 2/3에서의 Pulumi

  • Replica 서버용 리소스 추가(스택 또는 모듈).
  • 샤딩 시 DB 서버 N대 리소스, 테넌트–DB 매핑은 D1 등으로 관리.

5. Ansible — 설치 및 원격 DB 연동

5.1 인벤토리 구조 (Phase 1)

그룹대상역할
prego_db_serversDB 서버 호스트(들)MariaDB 설치, 원격 접속 허용, binlog(Phase 2 대비).
prego_nodes (App)App 서버 호스트(들)Docker, Frappe bench, Redis, Workers. 변수: db_host, db_port, db_root_password(DB 서버 기준).

5.2 DB 서버용 Ansible

작업내용
MariaDB 설치Docker 이미지(mariadb:10.6 등) 또는 네이티브 패키지.
설정root 비밀번호, bind-address(0.0.0.0 또는 Private IP), App 서버 IP(또는 VPC 대역)만 3306 허용.
Phase 2 대비--server-id=1, --log-bin=mysql-bin, --binlog-format=ROW. Replication user 생성(REPLICATION SLAVE 권한).

5.3 App 서버용 Ansible (Frappe ↔ 별도 DB)

작업내용
Frappe/bench 설치기존 roles(docker, frappe_bench, frappe_site)와 동일하되, DB 연결을 로컬이 아닌 원격으로.
연결 정보db_host: Pulumi 출력 db_server_ip(또는 인벤토리 변수). db_port: 3306. db_root_password: Secret/Vault.
bench new-sitebench new-site {{ site_name }} --db-host {{ db_host }} --db-port {{ db_port }} --db-root-password "{{ db_root_password }}" 등으로 별도 DB 서버에 DB 생성.
API Key기존과 동일. bench add-api-key 등.

5.4 Phase 2 Replica 연동 (Ansible)

대상작업
Replica 서버MariaDB 설치, --server-id=2, --read-only=1, CHANGE MASTER TO(Primary 호스트·로그·위치), START SLAVE.
PrimaryReplication user·권한은 Phase 1에서 준비 또는 Ansible로 확인.

6. MariaDB Primary/Replica Docker 구성 참고 (설계만)

Phase 1에서 DB 서버에 Docker로 MariaDB를 띄울 경우, 향후 Replica 추가를 위해 아래 방향으로 설계한다. 코드 생성 없음, 예시 개념만.

  • Primary: --server-id=1, --log-bin=mysql-bin, --binlog-format=ROW.
  • Replica: --server-id=2, --relay-log=relay-bin, --read-only=1.
  • 복제 설정: Primary에서 CREATE USER 'replica'@'%' + GRANT REPLICATION SLAVE; Replica에서 CHANGE MASTER TO(MASTER_HOST=Primary IP 등), START SLAVE.

상세 docker-compose 예시는 운영 문서 또는 Ansible role defaults에서 다룬다.


7. 확장 기준 지표 정의

DB 확장 판단 기준 (모니터링 수집 항목과 매핑)

지표기준용도
DB CPU> 70% 지속 10분 이상Replica 또는 스케일 업 검토.
평균 쿼리 응답시간> 100ms읽기 분산(Replica) 또는 인덱스·튜닝.
Connection 수사용률 80% 이상max_connections 상향 또는 서버 추가.
Slow Query증가 추세튜닝 또는 Replica로 읽기 이전.

8. 샤딩 전략 설계 원칙 (Phase 3)

원칙설명
테넌트 단위 분리Frappe는 DB(schema) 단위 분리 가능. 테넌트별로 다른 DB 호스트 지정 가능.
대형 고객 전용 DBEnterprise 전용 DB 서버 배치.
DB 서버별 부하 균형테넌트 수·메트릭 기반으로 새 테넌트 배치 결정.
Cross-DB Join 금지설계 단계에서 차단. 테넌트 간 조인은 애플리케이션 레벨에서 하지 않음.

9. 복잡도 관리 전략

단계복잡도관리 전략
Phase 1 (App + DB 1대씩)낮음기본 모니터링, Ansible로 설치·연동 일원화.
Phase 2 (Primary + Replica)중간Failover 절차 문서화, Replica 지연 모니터링.
Phase 3 (샤딩)높음테넌트–DB 매핑(D1), DB 라우팅 레이어 또는 Frappe site별 db_host 설정.

10. 기존 기획서와의 연동

문서연동 내용
pulumi-ansible-step1-step2-plan.md1단계 Pulumi에서 App 서버 + DB 서버 두 대 출력. 2단계 Ansible에서 DB 서버 그룹App 서버 그룹 각각 역할 수행.
ansible-implementation-plan.md변수 추가: db_host, db_port(기본 3306). 인벤토리: prego_db_servers, prego_nodes 및 host 변수. DB 서버용 role(mariadb 또는 frappe_db_server), App 서버용 role에서 --db-host {{ db_host }} 반영.
intelligent-automation-implementation-plan.mdserver_metrics에 DB 서버 메트릭 수집 추가. 확장 결정(decidePlacement 또는 전용 로직)에서 “Replica 필요”/“샤드 추가” 시 Pulumi·Ansible 순서로 반영.
provision-tenant.yml (워크플로)resolve-server 출력에 db_server_ip 추가. 인벤토리에 db_host 전달. 확장 시 Redis 서버 IP, R2 테넌트 prefix/버킷 생성 단계.
saas-expanded-multitenancy-redis-storage-plan.mdRedis 별도 서버 분리, 각 고객별 저장공간 R2 분리, Full HA·샤딩·Redis HA 상세. 확장 SaaS(테넌트 100+) 목표 구조.

11. 최종 전략 요약

지금 설계에서 반영할 것

  • Frappe 설치 시 DB를 별도 DB 서버에 설치된 MariaDB와 연동하는 구조로 변경.
  • Pulumi에서 App 서버 + DB 서버를 프로비저닝하고, 사용량 모니터링을 통해 Phase 2/3 확장(Replica, 샤드) 시 추가 DB 서버 구성.
  • Ansible에서 DB 서버에 MariaDB 설치, App 서버에 Frappe 설치 후 원격 DB 연동(db_host, db_port, db_root_password).
  • Phase 1: 단일 MariaDB(단, 별도 호스트). Replica 추가 가능하도록 binlog·replication user 준비.
  • Private 네트워크 설계로 DB 트래픽 격리.

향후

  • 동시 사용자 1,500~2,000·지표 충족 시 Replica 추가(Pulumi + Ansible).
  • 테넌트 100+ 시 샤딩 설계 및 대형 고객 전용 DB 전략.
  • Redis 별도 서버 분리각 고객(테넌트)별 저장공간 R2 분리 적용 — saas-expanded-multitenancy-redis-storage-plan.md 참조.

확장성 관리 (온보딩·리소스·로그)

항목전략 요약
온보딩 시 DB 할당테넌트가 늘어나면 단일 DB 서버 한계를 줄이기 위해, 온보딩 파이프라인에 “현재 가장 한가한 DB 서버” 선택 로직을 둔다. server_metrics·tenant_count 기반으로 할당. provision-tenant 워크플로의 resolve-server 단계 또는 배치 결정 엔진과 연동. provision-tenant-workflow-design.md §7.1.
리소스 격리특정 테넌트의 무거운 작업이 다른 테넌트에 영향을 주지 않도록 Docker Compose 레벨에서 CPU·Memory 제한(deploy.resources.limits)을 적용. Cgroups 기반 격리.
로그 통합테넌트가 여러 노드에 분산되므로 Cloudflare Logpush 또는 ELK Stack 등으로 로그를 한곳에 모아 조회·알람·감사 체계를 갖춘다. provision-tenant-workflow-design.md §7.1. 상세 기획: cloudflare-logpush-observability-plan.md.

12. 결론

  • DB 분리: App 서버와 DB 서버를 분리하고, Frappe는 별도 DB 서버의 MariaDB와만 연동한다.
  • Pulumi: DB 서버 프로비저닝 및 사용량 모니터링과 연동한 확장(Replica/샤드) 구성.
  • Ansible: DB 서버 실제 설치(MariaDB), App 서버 실제 설치(Frappe)·원격 DB 연동을 담당한다.
  • 초기 복잡도는 낮게 유지하고, 중기 안정성·장기 확장 가능성을 함께 확보하는 구조로 정리한다.
Help