워커 노드 분리
팔로워가 많은 고트래픽 인스턴스의 경우, 웹 서버와 백그라운드 워커를 분리하여 성능을 향상시킬 수 있습니다. 이를 통해 워크로드에 따라 각 구성 요소를 독립적으로 확장할 수 있습니다.
워커 분리를 고려해야 하는 경우
섹션 제목: “워커 분리를 고려해야 하는 경우”다음과 같은 경우 워커 분리가 유용합니다:
- 수천 명의 팔로워가 있고 액티비티 전달이 느린 경우
- 인스턴스가 많은 연합 트래픽(많은 들어오는/나가는 포스트)을 처리하는 경우
- 피크 활동 시간에 웹 서버의 응답성이 떨어지는 경우
- 여러 서버에서 수평적으로 확장하고 싶은 경우
작동 방식
섹션 제목: “작동 방식”Hollo는 여섯 가지 주요 구성 요소로 이루어져 있습니다:
- 웹 서버: HTTP 요청(API, 웹 UI) 처리
- Fedify 메시지 큐: ActivityPub inbox/outbox 메시지 처리
- 임포트 워커: 백그라운드 데이터 가져오기 작업 처리
- 정리 워커: 백그라운드 정리 작업 처리
- 원격 답글 가져오기 워커: 원격 답글 컬렉션을 천천히 가져오기
- 투표 알림 태스크: 만료 후 로컬 작성자와 참여자에게 알림
기본적으로(NODE_TYPE=all) 여섯 가지 모두 단일 프로세스에서 실행됩니다.
NODE_TYPE 환경 변수를 사용하여 분리할 수 있습니다:
NODE_TYPE |
웹 서버 | Fedify 큐 | 임포트 워커 | 정리 워커 | 답글 워커 | 투표 알림 |
|---|---|---|---|---|---|---|
all (기본값) |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
web |
✓ | ✗ | ✗ | ✗ | ✗ | ✗ |
worker |
✗ | ✓ | ✓ | ✓ | ✓ | ✓ |
임포트와 정리 태스크
섹션 제목: “임포트와 정리 태스크”임포트와 정리는 별도의 PostgreSQL 큐에서 Fedify 태스크로 실행되며, 워커 프로세스마다 최대 네 항목을 동시에 처리합니다. 웹 노드는 태스크를 등록하고 큐에 넣지만 소비하지 않습니다. 워커와 통합 노드는 1분마다 종류별 최대 100개의 활성 작업을 확인하여 누락된 전달과 중단된 처리를 복구합니다. 복구 검사 자체는 항목을 실행하지 않습니다. 진행률은 실행을 시작한 항목이 아니라 최종 결과를 셉니다.
업그레이드 전에 모든 구버전 웹·워커 노드를 중지하고 마이그레이션을 적용한 뒤, 모든 노드에 같은 버전을 배포하세요. 서로 다른 태스크 정의를 혼용하지 마세요. 기존 대기·처리 중 항목은 복구됩니다. 불완전한 기존 업로드 작업은 실패로 표시되며 다시 제출해야 합니다. 기존 연합 큐와 메시지는 그대로 유지됩니다.
워커는 기본 풀 20개와 항목 잠금 전용 연결 4개로 최대 24개의 DB 연결을 사용합니다. 큐 분리는 CPU나 저장소 부하를 분리하지 않습니다. 종료 시 새 실행을 중지하고 최대 30초 동안 진행 중 작업을 기다립니다. 강제 종료된 항목은 재시작 후 복구됩니다. 일시적 오류는 5초 간격으로 재시도하며 기록된 실패가 5회이면 항목이 실패합니다. 프로세스 종료는 실패 횟수에 포함되지 않습니다. 외부 액티비티 전달은 중단 후 저장된 ID로 반복될 수 있습니다. 취소는 새 작업을 막지만 이미 수행한 동작을 되돌리지 않습니다. 이미 확정된 import 관계의 저장된 활동은 재시도 중에 취소하더라도 계속 전송됩니다.
모든 노드는 동일한 PostgreSQL 데이터베이스를 공유하며,
LISTEN/NOTIFY를 사용하여 실시간 메시지 전달을 위한 메시지 큐 백엔드로 작동합니다.
원격 답글 수집 태스크
섹션 제목: “원격 답글 수집 태스크”원격 답글 수집은 임포트·정리와 전용 태스크 큐를 공유합니다. 워커마다 애플리케이션 태스크를 최대 네 개, 그중 수집을 최대 두 개 동시에 실행합니다. 큐 제한은 즉시 실행 가능한 메시지를 기준으로 하므로 지연 예약이 다른 작업을 막지 않습니다. PostgreSQL 작업·호스트 레코드가 워커 간 컬렉션 쿨다운, 요청 간격과 HTTP 429 backoff를 유지합니다. 실행과 재시도 예약은 태스크로 처리합니다. 시작 시와 매분 수행하는 복구 검사는 만료된 작업과 호스트 lease를 각각 최대 100개 확인하고 실행 가능한 수집을 최대 50개 예약합니다. 검사 자체는 원격 컬렉션을 가져오지 않습니다. 트랜잭션 안에서 생성한 작업은 커밋 후 복구 검사가 예약합니다.
업그레이드 전에 구버전 노드를 모두 중지하고 모든 노드에 호환되는 태스크 정의를 배포하세요. 기존 대기 작업은 복구되며 처리 중 작업은 15분 heartbeat lease가 만료된 뒤 회수됩니다. 종료 시 대기를 중단하고 실행을 마친 시도를 대기 상태로 돌립니다. 강제 종료 시에는 lease 만료를 기다립니다. HTTP 429는 기록된 시각에 재시도하고 다른 수집 오류는 실패로 종료합니다. 기존 수집 환경 변수와 로컬 액터의 인증된 가져오기 동작은 유지됩니다.
투표 알림 태스크
섹션 제목: “투표 알림 태스크”투표 만료 알림도 전용 Fedify 태스크 큐를 사용합니다. 웹 노드는 예약하고, 워커와 통합 노드는 현재 만료 시각과 로컬 수신자를 다시 읽어 실행합니다. 만료 변경과 중복 메시지로 알림이 일찍 생성되거나 중복 집계되지 않습니다. 먼 만료 시각은 최대 7일씩 나누어 예약하며, 실제 실행은 만료보다 늦을 수 있습니다.
시작 시점과 매분 복구 스캔은 알림이 누락된 만료 투표를 최대 100개씩 예약합니다. 앞선 작업이 대기하거나 실패해도 순회 위치를 이어가므로 큰 backlog도 복구합니다. 기존 투표, 워커 중단, commit 후 예약 실패, 재시도 소진을 복구하며 스캔 자체는 알림을 생성하지 않습니다. 지연 메시지는 즉시 실행 큐의 부하 제한에 포함되지 않습니다. 일시적인 오류는 지수 backoff로 최대 3회 시도하고, 이후에도 복구 스캔이 재예약할 수 있습니다. 종료 시 진행 중인 트랜잭션을 마치며 중단된 작업은 복구 가능합니다. 업그레이드 전 기존 노드를 중지하고 모든 노드에 호환되는 태스크 정의를 배포하세요.
공유 데이터베이스에는 지연 wakeup과 재시도를 포함해 대기 투표 메시지를 최대 100개 저장하며, 새 예약은 투표당 하나의 wakeup으로 합칩니다. 반복 참여와 만료 변경도 합쳐지고, 만료가 앞당겨지면 기존 wakeup을 앞당깁니다. 만료가 늦춰지면 wakeup 실행 시 현재 만료 시각을 다시 읽습니다. 상한에서는 더 이른 wakeup이 가장 늦은 메시지를 대체합니다. 보류되거나 밀려난 투표는 만료 후 복구됩니다. 부하가 몰려 입장 잠금을 1초 안에 얻지 못해도 예약을 보류할 수 있습니다. 이 상한은 워커별이 아니라 모든 노드에 걸쳐 적용되며, 실행 중 handler나 알림 수가 아닌 저장된 작업 수를 제한합니다.
지연 예약은 워커마다 타이머를 할당하는 대신 큐의 5초 주기 확인을 사용합니다. 복구는 공유 ready 메시지가 200개이면 여전히 일시 중지합니다. 100개 상한은 복구 한 페이지 크기이며 처리량 보장은 아닙니다. 업그레이드 전에 모든 기존 생산자를 중지하세요. 기존 메시지도 상한에 포함되지만 이미 100개를 넘은 backlog는 점차 줄어듭니다. 새 예약으로 그 backlog가 더 커지지는 않습니다.
hollo.poll-notifications.queue 로거는 상한에 따른 보류, wakeup 대체와 잠금
시간 초과를 warning 수준으로 원인별·프로세스별 매분 최대 한 번 기록합니다.
아래 SQL로 저장 부하를 확인할 수 있습니다. enqueue 카운터에는 합쳐지거나
보류된 시도도 포함되므로 저장 메시지 수를 나타내지 않습니다.
SELECT count(*) AS queued, count(*) FILTER (WHERE created + delay <= CURRENT_TIMESTAMP) AS ready, count(*) FILTER (WHERE created + delay > CURRENT_TIMESTAMP) AS delayed, count(*) FILTER ( WHERE message->>'type' = 'task' AND message->>'taskName' = 'hollo.poll-notification.v1' ) AS pending_pollsFROM hollo_task_message_v1;Docker Compose 설정
섹션 제목: “Docker Compose 설정”다음은 별도의 웹 및 워커 노드를 실행하기 위한 compose.yaml 예제입니다:
services: db: image: postgres:17-alpine restart: unless-stopped environment: POSTGRES_USER: hollo POSTGRES_PASSWORD: password POSTGRES_DB: hollo volumes: - ./data/postgres:/var/lib/postgresql/data
web: image: ghcr.io/dahlia/hollo:latest restart: unless-stopped depends_on: - db ports: - "3000:3000" environment: - NODE_TYPE=web - DATABASE_URL=postgresql://hollo:password@db/hollo - SECRET_KEY=${SECRET_KEY} - DRIVE_DISK=fs - FS_STORAGE_PATH=/data/storage - STORAGE_URL_BASE=https://hollo.example.com/assets - BEHIND_PROXY=true volumes: - ./data/storage:/data/storage
worker: image: ghcr.io/dahlia/hollo:latest restart: unless-stopped depends_on: - db environment: - NODE_TYPE=worker - DATABASE_URL=postgresql://hollo:password@db/hollo - SECRET_KEY=${SECRET_KEY} - DRIVE_DISK=fs - FS_STORAGE_PATH=/data/storage - STORAGE_URL_BASE=https://hollo.example.com/assets volumes: - ./data/storage:/data/storage워커 확장
섹션 제목: “워커 확장”여러 워커 노드를 실행하려면 워커 서비스를 더 추가하세요:
services: # ... db 및 web 서비스 ...
worker-1: image: ghcr.io/dahlia/hollo:latest restart: unless-stopped depends_on: - db environment: - NODE_TYPE=worker # ... 기타 환경 변수 ...
worker-2: image: ghcr.io/dahlia/hollo:latest restart: unless-stopped depends_on: - db environment: - NODE_TYPE=worker # ... 기타 환경 변수 ...PostgreSQL의 LISTEN/NOTIFY는 각 메시지가 하나의 워커에서만
처리되도록 보장합니다.
수동 설치
섹션 제목: “수동 설치”수동 설치의 경우, 다른 NODE_TYPE 값을 사용하여 별도의 프로세스를 실행할 수 있습니다.
웹 노드
섹션 제목: “웹 노드”NODE_TYPE=web pnpm prod구성된 포트에서 웹 서버만 시작합니다.
워커 노드
섹션 제목: “워커 노드”NODE_TYPE=worker pnpm prod# 또는pnpm worker웹 서버 없이 Fedify 메시지 큐와 임포트 워커만 시작합니다.
Systemd 예제
섹션 제목: “Systemd 예제”systemd를 사용하는 경우, 별도의 서비스 파일을 만드세요:
/etc/systemd/system/hollo-web.service
섹션 제목: “/etc/systemd/system/hollo-web.service”[Unit]Description=Hollo Web ServerAfter=network.target postgresql.service
[Service]Type=simpleUser=holloWorkingDirectory=/opt/holloEnvironment="NODE_TYPE=web"EnvironmentFile=/opt/hollo/.envExecStart=/usr/bin/pnpm prodRestart=on-failure
[Install]WantedBy=multi-user.target/etc/systemd/system/hollo-worker.service
섹션 제목: “/etc/systemd/system/hollo-worker.service”[Unit]Description=Hollo WorkerAfter=network.target postgresql.service
[Service]Type=simpleUser=holloWorkingDirectory=/opt/holloEnvironment="NODE_TYPE=worker"EnvironmentFile=/opt/hollo/.envExecStart=/usr/bin/pnpm workerRestart=on-failure
[Install]WantedBy=multi-user.target그런 다음 두 서비스를 활성화하고 시작하세요:
sudo systemctl enable hollo-web hollo-workersudo systemctl start hollo-web hollo-worker모니터링
섹션 제목: “모니터링”워커 상태 확인
섹션 제목: “워커 상태 확인”Docker Compose의 경우:
# 웹 노드 로그 보기docker compose logs -f web
# 워커 노드 로그 보기docker compose logs -f workersystemd의 경우:
# 웹 노드 로그 보기sudo journalctl -u hollo-web -f
# 워커 노드 로그 보기sudo journalctl -u hollo-worker -f워커 로그 메시지
섹션 제목: “워커 로그 메시지”워커 노드가 시작되면 다음과 같이 표시됩니다:
Worker started (Fedify activity/task queues + Job recovery)워커가 정상적으로 작동하는지 확인하려면 액티비티 처리, 가져오기, 정리, 원격 답글 가져오기에 대한 메시지를 확인하세요.
문제 해결
섹션 제목: “문제 해결”웹 노드는 작동하지만 액티비티가 처리되지 않음
섹션 제목: “웹 노드는 작동하지만 액티비티가 처리되지 않음”문제: 웹 UI에 접근할 수 있지만 들어오는 액티비티(팔로우, 좋아요, 포스트)가 처리되지 않습니다.
해결: NODE_TYPE=worker로 최소 하나의 워커 노드가 실행 중인지 확인하세요.
워커 노드가 시작되지 않음
섹션 제목: “워커 노드가 시작되지 않음”문제: 워커 노드가 오류와 함께 종료됩니다.
해결: 다음을 확인하세요:
DATABASE_URL이 올바르고 데이터베이스에 접근 가능한지- 데이터베이스에 최신 마이그레이션이 적용되었는지
- 스토리지 설정(
DRIVE_DISK,FS_STORAGE_PATH등)이 올바른지
액티비티 처리가 느림
섹션 제목: “액티비티 처리가 느림”문제: 연합 액티비티 처리에 지연이 있습니다.
해결: 메시지를 병렬로 처리하기 위해 워커 노드를 더 추가하세요. PostgreSQL의 메시지 큐가 사용 가능한 모든 워커에 작업을 분산합니다.
스토리지 접근 오류
섹션 제목: “스토리지 접근 오류”문제: 워커 노드가 업로드된 파일에 접근할 수 없습니다.
해결: 다음을 확인하세요:
- 모든 노드(웹 및 워커)가 동일한 스토리지에 접근할 수 있는지
- 파일시스템 스토리지의 경우: 스토리지 볼륨이 모든 노드에 마운트되어 있는지
- S3 스토리지의 경우: 모든 노드가 동일한 S3 자격 증명을 가지고 있는지
성능 고려사항
섹션 제목: “성능 고려사항”- 웹 노드: 가볍고 최소한의 리소스로 실행 가능(512MB-1GB RAM)
- 워커 노드: 특히 높은 연합 활동 중에 리소스 집약적(1GB-2GB RAM 권장)
- 데이터베이스: 모든 노드가 공유; 충분한 리소스 확보 필요 (바쁜 인스턴스의 경우 2GB+ RAM 권장)
- 동시성: 각 워커는 별도 큐에서 ActivityPub 메시지 최대 10개와 애플리케이션 작업 최대 4개를 동시에 처리합니다. 원격 답글 수집은 이 4개 작업 슬롯 중 최대 2개를 사용합니다. 제한은 워커 프로세스별로 적용되므로 워커를 추가하면 전체 동시 처리량이 늘어납니다.
모범 사례
섹션 제목: “모범 사례”- 성능 문제가 발생할 때까지
NODE_TYPE=all(기본값)로 시작하세요 - 리소스 사용량을 모니터링하여 분리가 필요한 시점을 결정하세요
NODE_TYPE=web사용 시 최소 하나의 전용 워커 노드를 실행하세요- 웹 노드 앞에 리버스 프록시(nginx, Caddy)를 사용하여 로드 밸런싱하세요
- 모든 노드에서 스토리지에 접근 가능하도록 유지하세요(공유 볼륨 또는 S3)
- 오류 및 성능 문제를 위해 모든 노드의 로그를 모니터링하세요