📑 이 챕터에서 다룰 내용
봇 1개는 누구나 운영할 수 있습니다. 그러나 도메인이 늘어나면 (TSV·AlbaFlow·viewLab·줍줍...) 봇이 4·8·12개가 됩니다. 8GB RAM Cloud VPS 10에서 12개 봇을 안정적으로 운영하려면 체계적인 접근이 필요해요.
- pm2 ecosystem.config.js 심화 설정
- 자원 분배·메모리 한도 설정
- 봇 간 통신 (Redis Pub/Sub)
- 부하 분산 (Cloudflare Worker로 일부 분리)
- 모니터링 종합 (한 대시보드에서 모든 봇 관리)
기본 설정 (단순 버전)
module.exports = {
apps: [
{ name: "tsv-bot", script: "tsv/main.py", interpreter: "python3" },
{ name: "albaflow-bot", script: "albaflow/main.py", interpreter: "python3" },
]
};심화 설정 (12개 봇)
module.exports = {
apps: [
// === TSV 4개 봇 ===
{
name: "tsv-monitoring",
script: "tsv/monitoring/main.py",
interpreter: "/home/ubuntu/.venv/bin/python3",
env: {
DATABASE_URL: process.env.DATABASE_URL,
ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY,
DISCORD_TOKEN: process.env.TSV_DISCORD_TOKEN,
LOG_LEVEL: "info",
},
max_memory_restart: "300M",
autorestart: true,
restart_delay: 5000,
max_restarts: 5,
min_uptime: "30s",
log_date_format: "YYYY-MM-DD HH:mm:ss",
error_file: "/var/log/pm2/tsv-monitoring-error.log",
out_file: "/var/log/pm2/tsv-monitoring-out.log",
merge_logs: true,
},
{
name: "tsv-b2b",
script: "tsv/b2b/main.py",
interpreter: "/home/ubuntu/.venv/bin/python3",
env: { /* ... */ },
max_memory_restart: "200M",
},
{
name: "tsv-position-c",
script: "tsv/position_c/main.py",
interpreter: "/home/ubuntu/.venv/bin/python3",
cron_restart: "0 0 * * *", // 매일 자정 재시작
max_memory_restart: "200M",
},
{
name: "tsv-faq",
script: "tsv/faq/main.js",
env: { /* ... */ },
max_memory_restart: "300M",
},
// === AlbaFlow 4개 봇 ===
{
name: "albaflow-owner",
script: "albaflow/owner/main.py",
interpreter: "/home/ubuntu/.venv/bin/python3",
env: { /* ... */ },
max_memory_restart: "300M",
},
{
name: "albaflow-alba",
script: "albaflow/alba/main.js",
env: { /* ... */ },
max_memory_restart: "300M",
},
{
name: "albaflow-labor-law",
script: "albaflow/labor_law/main.py",
interpreter: "/home/ubuntu/.venv/bin/python3",
cron_restart: "0 0 * * *",
max_memory_restart: "200M",
},
{
name: "albaflow-store",
script: "albaflow/store/main.py",
interpreter: "/home/ubuntu/.venv/bin/python3",
cron_restart: "0 9 * * *", // 매일 09:00
max_memory_restart: "200M",
},
// === viewLab 4개 봇 ===
{ name: "viewlab-dealer", script: "viewlab/dealer/main.py", interpreter: "python3", max_memory_restart: "300M" },
{ name: "viewlab-content", script: "viewlab/content/main.py", interpreter: "python3", max_memory_restart: "200M" },
{ name: "viewlab-customer", script: "viewlab/customer/main.js", max_memory_restart: "300M" },
{ name: "viewlab-store", script: "viewlab/store/main.py", interpreter: "python3", max_memory_restart: "200M" },
]
};max_memory_restart— 메모리가 이 한도를 초과하면 자동 재시작. 메모리 누수를 막는 가장 중요한 설정이에요.min_uptime: "30s"— 30초 미만에 죽으면 비정상 종료로 간주. 무한 재시작 루프를 방지합니다.max_restarts: 5— 5번 재시작 실패 시 pm2가 포기하고 멈춥니다. Sentry 알림으로 즉시 파악하세요.cron_restart— 정해진 시간에 정기 재시작. 메모리 누수가 있는 봇에 유용합니다.
자원 계획 — 12개 봇 기준
| 항목 | 설정 기준 | 비고 |
|---|---|---|
| 12개 봇 × 평균 250MB | 3GB | max_memory_restart로 제어 |
| 8GB RAM 대비 봇 사용량 | 37% | 안전한 수준 |
| 시스템 + PostgreSQL + 기타 | ~2GB | 예상치 |
| 여유 메모리 | ~3GB | 충분한 여유 |
메모리 분배 계획 (8GB RAM 기준)
| 구성 요소 | 메모리 할당 |
|---|---|
| 시스템 + Linux | 1GB |
| PostgreSQL | 1GB |
| Redis | 200MB |
| nginx | 50MB |
| 봇 12개 합계 | 3GB (평균 250MB × 12) |
| 여유 | 2.75GB |
봇별 메모리 패턴
| 봇 유형 | 평균 메모리 | Anthropic 호출 시 |
|---|---|---|
| Python 봇 | 200~300MB | +100MB (일시적) |
| Node.js 봇 | 250~400MB | +100MB (일시적) |
| 대용량 데이터 처리 시 | +200MB (일시적) — max_memory_restart로 관리 | |
CPU 고려사항 (4 코어)
12개 봇이 동시에 실행되지만, 봇은 대부분 이벤트 기반으로 동작해요. 메시지가 없을 때는 거의 idle 상태입니다.
- 동시 CPU 집중 작업: 최대 4개 (코어 수 기준)
- 주의: 한 봇이 무한 루프에 빠지면 CPU 100% → 다른 봇 영향
- 대응:
max_memory_restart와 함께 CPU 모니터링 필수
디스크 분배 계획 (100GB 기준)
| 항목 | 예상 용량 |
|---|---|
| 시스템 | 5GB |
| 봇 코드 | 1GB |
| DB (PostgreSQL) | 10~50GB (사용자 규모에 따라 다름) |
| 로그 | 5~10GB |
| 여유 공간 | 35~80GB |
- 로그가 무한정 커질 수 있어요 → logrotate 설정 필수
- DB 행 수가 늘면 인덱스 성능 저하 → 정기 vacuum
/var/log/pm2/폴더 크기를 주기적으로 확인하세요
12개 봇이 서로 정보를 공유해야 할 때가 있습니다. 예를 들어 TSV 봇이 새 이벤트를 감지하면 운영자 알림 봇에 전달하는 구조입니다. 1인 운영자가 여러 도메인을 통합 관리할 때 유용해요.
Redis Pub/Sub 구현 예시
import redis.asyncio as redis
import json
class BotPubSub:
def __init__(self):
self.redis = redis.Redis(host='localhost', port=6379)
async def publish(self, channel: str, message: dict):
"""이벤트 발행"""
await self.redis.publish(channel, json.dumps(message))
async def subscribe(self, channel: str, handler):
"""이벤트 구독"""
pubsub = self.redis.pubsub()
await pubsub.subscribe(channel)
async for message in pubsub.listen():
if message['type'] == 'message':
data = json.loads(message['data'])
await handler(data)
# --- 사용 예시 ---
# TSV 모니터링 봇: 이벤트 발행
await pubsub.publish("system_alert", {
"type": "high_error_rate",
"domain": "tsv",
"severity": "warning"
})
# 운영자 알림 봇: 이벤트 구독 (모든 도메인 종합)
async def handle_alert(data):
if data["severity"] == "warning":
await send_discord_alert(data)
await pubsub.subscribe("system_alert", handle_alert)Redis 활용 범위
| 용도 | 구체적 예시 |
|---|---|
| 봇 → 봇 알림 | TSV 모니터링 → 운영자 알림: "일별 리포트 준비 완료" |
| 응답 없음 감지 | AlbaFlow 사장님 봇 → 운영자: "응답 지연 감지" |
| Heartbeat | 모든 봇 → 모니터링 봇: 정기 생존 신호 |
| 메시지 큐 | BullMQ·Celery 백엔드로 활용 |
| 캐시 | Anthropic 응답 결과 캐싱 |
| 세션 | 사용자 대화 컨텍스트 저장 |
| Rate limit | 봇별 API 호출 횟수 제한 |
VPS 자원에 한계가 있을 때 가벼운 작업은 Cloudflare Worker로 분리하면 VPS 부하를 크게 줄일 수 있어요.
Worker vs VPS — 작업 분배 기준
| Cloudflare Worker 적합 | VPS 적합 |
|---|---|
| ✓ FAQ 응답 (단순·캐시 가능) | ✓ 장시간 처리 (Anthropic stream) |
| ✓ Webhook 수신 (Discord·Telegram·Stripe) | ✓ 큰 메모리 작업 |
| ✓ HTTP API 처리 | ✓ Persistent 연결 (Discord 봇 WebSocket) |
| ✓ Rate limit·인증 처리 | ✓ DB 읽기·쓰기 |
| ✓ 정적 데이터 처리 | ✓ cron 작업 (시간 정확도 필요) |
분리 전후 비교
| 구분 | VPS 메모리 사용 | Worker 비용 |
|---|---|---|
| 분리 전 (모두 VPS) | 1.7GB / 8GB | — |
| 분리 후 (Worker 활용) | 1.0GB / 8GB (40% 절약) | 100K req/일 무료 |
하루 100,000 요청까지 무료입니다. FAQ 봇·Webhook 처리 정도는 무료 한도 안에서 충분히 운영 가능해요.
초과 시: $5/월 (1,000만 요청까지) — 여전히 매우 저렴합니다.
옵션 A — 단순 구성 (권장: 처음 12개 봇)
| 도구 | 역할 | 비용 |
|---|---|---|
| pm2 monit | CLI에서 모든 봇 상태·메모리·CPU 확인 | 무료 |
| Sentry | 에러 수집·알림 | 무료 (소규모) |
| Anthropic Console | API 비용·사용량 | 무료 |
| Cloudflare Dashboard | Worker 요청 수·에러율 | 무료 |
옵션 B — 고급 구성 (봇 50개+ 시 검토)
| 도구 | 역할 | 메모리 |
|---|---|---|
| Grafana | 차트·대시보드·알림 | ~250MB |
| Prometheus | 메트릭 수집 | ~200MB |
| 합계 추가 비용 | 8GB 중 총 2.2GB 사용 (27%) | ~500MB |
봇 12개 수준에서는 pm2 monit + Sentry 조합으로 충분합니다. Grafana·Prometheus는 봇이 50개를 넘어서 복잡해질 때 도입을 검토하세요.
지금 당장 복잡한 모니터링 시스템을 구축하는 것보다 봇 품질을 높이는 편이 훨씬 효과적이에요.
문제: 12개 봇이 매일 09:00에 동시 시작 → CPU·API 호출 폭발 → Anthropic rate limit 초과
해결: cron_restart로 시작 시간을 5분씩 분산하세요.
cron_restart: "0 9 * * *", // 봇 1 — 09:00 cron_restart: "5 9 * * *", // 봇 2 — 09:05 cron_restart: "10 9 * * *", // 봇 3 — 09:10 cron_restart: "15 9 * * *", // 봇 4 — 09:15 // ... 5분 간격으로 배치
문제: TSV 봇 메모리 누수 → 4GB 점유 → 다른 봇 메모리 부족 → 연쇄 종료
해결: 모든 봇에 max_memory_restart 필수 설정. 봇당 300~500MB 한도를 권장합니다.
문제: 한 API 키 공유 → 한 봇의 코드 오류 → 모든 봇의 API 차단
해결: 도메인별로 별도 API 키를 사용하세요 (TSV 키·AlbaFlow 키·viewLab 키...). 한 봇에 문제가 생겨도 다른 봇에 영향이 없습니다.
문제: 12개 봇 × 각 봇 10개 connection = 120개. PostgreSQL 기본 max_connections는 100이에요.
해결 (3가지 중 택1):
- PgBouncer 도입 — connection pool 미들웨어. 가장 권장하는 방법
- PostgreSQL max_connections 증가 (200~500) — 메모리 사용량 증가 감안
- 봇당 connection 수 제한 (1~3개) — 코드 레벨에서 pool 크기 설정
매일 점검 (4분)
| 시간 | 작업 |
|---|---|
| 09:00 | pm2 status — 12개 봇 상태 일괄 확인 |
| 09:01 | free -h — 메모리·CPU 현황 |
| 09:02 | Sentry 에러 확인 |
| 09:03 | Anthropic 비용 확인 |
| 09:04 | 완료 ✅ |
새 봇 추가 절차 (월 1회 정도)
현재 자원 사용량을 확인합니다. 새 봇이 사용할 예상 메모리를 계산하고 여유 공간이 충분한지 확인하세요.
로컬에서 먼저 실행해보고 단위 테스트 + 통합 테스트를 모두 통과시킵니다.
새 봇 항목을 추가합니다. max_memory_restart와 환경 변수 설정을 반드시 포함하세요.
git push → GitHub Actions 자동 배포 (별책부록 9편) → pm2 reload ecosystem.config.js
배포 후 하루 동안 메모리·에러를 면밀히 관찰합니다. 자원이 안정적으로 유지되면 완료입니다.
📌 17편 정리
- 8GB Cloud VPS 10 한 대로 12개 봇을 안정적으로 운영할 수 있습니다 — 메모리 사용량은 37%에 불과해요.
- pm2 ecosystem.config.js 심화: max_memory_restart + min_uptime + max_restarts 3종 세트는 필수입니다.
- 자원 분배: 봇당 300~500MB 한도, logrotate, 정기 DB vacuum으로 장기 안정성을 확보하세요.
- Redis Pub/Sub: 봇 간 통신·캐시·세션·Rate limit을 한 곳에서 처리할 수 있습니다.
- Cloudflare Worker 분리: 가벼운 작업을 분리하면 VPS 메모리를 40% 절약할 수 있어요.
- 4가지 함정 회피: 시작 시간 분산 / 메모리 한도 / 도메인별 API 키 / PgBouncer로 DB 연결 관리.