diff --git a/.gitignore b/.gitignore index cdf5f0e..c51a748 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ lerna-debug.log* apm_query_api_spec_v2.yaml # …but keep README.md files tracked !README.md +!backend/OPERATIONS.md # Keep project-specific ignores inside subdirectories (.gitignore within backend/, frontend/, etc.) # Keep project-specific ignores inside subdirectories (.gitignore within backend/, frontend/, etc.) -reports \ No newline at end of file +reports diff --git a/README.md b/README.md new file mode 100644 index 0000000..39ee012 --- /dev/null +++ b/README.md @@ -0,0 +1,91 @@ +# Panopticon Backend + +NestJS 기반 APM 백엔드로, 수집된 로그·스팬을 Elasticsearch에 적재하고 조회/알림/롤업을 제공합니다. Kafka를 통해 들어오는 실시간 이벤트를 처리하고, 대시보드용 HTTP API와 WebSocket 알림 채널을 함께 제공합니다. + +## 아키텍처 한눈에 보기 +- **stream-processor**: Kafka `apm.logs`/`apm.spans` 토픽 소비 → 검증 후 ES 데이터 스트림(`logs-apm`, `traces-apm`)에 `_bulk` 색인. ERROR 로그는 별도 토픽(`apm.logs.error`)으로 포워딩. +- **query-api**: 서비스/엔드포인트 메트릭, 스팬/로그 검색, 단일 트레이스 조회용 읽기 전용 HTTP API. 롤업(1분 버킷) 데이터와 Redis 캐시를 활용해 긴 구간 조회를 가속. +- **error-stream**: `apm.logs.error`를 소비해 WebSocket으로 프런트엔드에 실시간 에러 알림 송신. +- **aggregator**: 닫힌 분(minute) 단위로 `traces-apm`을 집계해 롤업 데이터 스트림(`metrics-apm`)을 채우는 워커. +- **shared**: 공통 DTO, 저장소, Kafka/ES 설정, Redis 캐시 유틸. + +## 주요 기능 +- APM 로그/스팬 ingest 및 `_bulk` 색인 최적화(배치/바이트/타이머 기준 플러시, 병렬 플러시 한도). +- 서비스·엔드포인트 메트릭(QPS, p95/p90/p50, 에러율)과 트레이스/스팬/로그 검색 API 제공. +- 롤업 파이프라인(1분 버킷) 및 롤업+RAW 자동 병합 조회, Redis 기반 단기 캐시. +- Kafka → WebSocket 에러 스트림 브리지로 실시간 에러 알림. + +## 디렉터리 구조 +``` +backend/ + src/ + query-api/ # 읽기 전용 HTTP API + stream-processor/ # Kafka 컨슈머(로그/스팬) + 샘플 프로듀서 + error-stream/ # 에러 로그 WebSocket 브리지 + aggregator/ # 롤업 워커 + shared/ # 공통 DTO/서비스/설정 +``` + +## 빠른 시작 +> 모든 명령은 `backend` 디렉터리에서 실행합니다. + +```bash +cd backend +npm ci + +# 로컬 개발 (env는 .env/.env.local 사용) +npm run start:query-api # HTTP API (포트 3001) +npm run start:stream-processor # Kafka 컨슈머 +npm run start:error-stream # WebSocket + Kafka +npm run start:aggregator # 롤업 워커 +``` + +### 필수/주요 환경 변수 +- OpenSearch/ES: `ELASTICSEARCH_NODE`, `OPENSEARCH_USERNAME`, `OPENSEARCH_PASSWORD`, `OPENSEARCH_REJECT_UNAUTHORIZED`, `USE_ISM` +- Kafka: `KAFKA_BROKERS` (`KAFKA_BROKERS_LOCAL`), `KAFKA_SSL`, `KAFKA_SASL_MECHANISM`, `KAFKA_SASL_USERNAME/PASSWORD`, `KAFKA_AWS_REGION` +- APM 토픽: `KAFKA_APM_LOG_TOPIC`, `KAFKA_APM_SPAN_TOPIC`, `KAFKA_APM_LOG_ERROR_TOPIC` +- Redis 캐시: `REDIS_HOST` (캐시 비활성화 시 생략), `METRICS_CACHE_PREFIX`, `METRICS_CACHE_TTL_SECONDS` +- 롤업: `ROLLUP_ENABLED`, `ROLLUP_THRESHOLD_MINUTES`, `ROLLUP_BUCKET_MINUTES`, `ROLLUP_CACHE_TTL_SECONDS` + +## API 개요 (주요 엔드포인트) +- **트레이스** + - `GET /query/traces/:traceId` : 단일 트레이스의 스팬/로그 전체 반환(서비스/환경 필터 지원) +- **스팬 검색** + - `GET /query/spans` : 서비스/환경/이름/지연/상태/트레이스 ID 기준 검색, 페이지네이션/정렬 지원 +- **로그 검색** + - `GET /query/logs` : 서비스/환경/레벨/트레이스·스팬 ID/메시지로 검색, 최신순 기본 15분 +- **서비스 메트릭** + - `GET /query/services/:serviceName/metrics` : QPS, p95/p90/p50, 에러율 시계열. 긴 구간은 롤업+RAW 병합, Redis 캐시 활용 +- **서비스 개요** + - `GET /query/services` : 시간 구간 내 서비스별 요청수/p95/에러율 랭킹 +- **엔드포인트 메트릭/트레이스** + - `GET /query/services/:serviceName/endpoints` : 엔드포인트별 요청수/p95/에러율 상위 N개 + - `GET /query/services/:serviceName/endpoints/:endpointName/traces` : 최근 에러/느린 트레이스 샘플 +- **WebSocket 에러 알림** + - 경로: `ws://:3010/ws/error-logs` (환경 변수로 변경 가능) + - 이벤트: `error-log` (Kafka `apm.logs.error`의 validated DTO) + +## 롤업 파이프라인 +- Aggregator가 닫힌 1분 구간을 계획(MinuteWindowPlanner) → 서비스/환경별 percentiles 및 에러율 집계 → `metrics-apm` 데이터 스트림에 `_bulk create` 저장. +- Query API는 조회 구간이 `ROLLUP_THRESHOLD_MINUTES` 이상이면 과거 구간을 롤업으로 채우고, 최신 구간은 RAW 집계로 결합해 응답. + +## 운영 팁 +- Kafka/ES/Redis는 `infra/docker-compose.yml`로 로컬 부트스트랩 가능. +- `_bulk` 설정은 `BULK_BATCH_SIZE`, `BULK_BATCH_BYTES_MB`, `BULK_MAX_PARALLEL_FLUSHES` 등으로 조정해 클러스터 부하에 맞출 수 있습니다. +- Throughput 모니터링: `STREAM_THROUGHPUT_*` 환경 변수를 설정하면 컨슈머 처리량 로그를 샘플링해 남깁니다. +- 보안: OpenSearch ISM 또는 Elasticsearch ILM/템플릿을 자동 생성하지만, 프로덕션에서는 최소 권한 계정과 TLS 설정을 사용하세요. + +## 샘플 이벤트 발행 +```bash +npm run test:sample:log # Kafka에 샘플 로그 전송 +npm run test:sample:span # Kafka에 샘플 스팬 전송 +``` + +## 운영 가이드 링크 +- 백엔드 빌드/배포/환경 변수 세부 가이드는 [backend/OPERATIONS.md](backend/OPERATIONS.md)를 참고하세요. + +## 이 아키텍처의 강점 +- **책임 분리 + 스케일링**: ingest(stream-processor) / 롤업(aggregator) / 조회(query-api) / 실시간 알림(error-stream)을 각각 컨테이너로 분리해 장애 격리, 트래픽 패턴별 독립 확장이 가능. +- **운영 튜너블**: 환경 변수만으로 ILM/ISM, Kafka SSL/SASL, `_bulk` 버퍼, 롤업/캐시 전략을 즉시 조정해 인프라 부하에 맞출 수 있음. +- **성능 보호**: 1분 롤업 + Redis 캐시로 긴 구간 조회를 가볍게 하고, ThroughputTracker/배치 플러시로 컨슈머 병목을 방지. +- **데이터 신뢰성**: DTO 검증, ISM/ILM 자동 생성, idempotent 롤업 ID로 중복/충돌을 줄이고, 에러 로그는 별도 토픽으로 분리해 손실 위험을 낮춤. diff --git a/backend/OPERATIONS.md b/backend/OPERATIONS.md new file mode 100644 index 0000000..be5da83 --- /dev/null +++ b/backend/OPERATIONS.md @@ -0,0 +1,65 @@ +# Backend Operations Guide + +Panopticon 백엔드를 빌드/배포/운영하기 위한 실무 가이드입니다. 각 기능은 별도 이미지/프로세스로 분리되어 있어 장애 격리와 독립 스케일링이 가능합니다. + +## 컴포넌트와 Docker 타깃 +| 역할 | Docker target | 설명 | +| --- | --- | --- | +| Query API | `query-api` | 서비스/엔드포인트 메트릭, 스팬/로그 검색, 트레이스 조회 | +| Stream Processor | `stream-processor` | Kafka `apm.logs`/`apm.spans` 소비 → ES 데이터 스트림에 `_bulk` 색인, ERROR 로그는 별도 토픽으로 복제 | +| Error Stream | `error-stream` | `apm.logs.error` 소비 → WebSocket(`ws://host:3010/ws/error-logs`) 브로드캐스트 | +| Aggregator | `aggregator` | 닫힌 1분 구간을 롤업 집계해 `metrics-apm` 데이터 스트림에 저장 | + +## 로컬 실행/빌드 +모든 명령은 `backend`에서 실행합니다. + +```bash +npm ci +npm run start:query-api # 포트 3001 +npm run start:stream-processor # Kafka 컨슈머 +npm run start:error-stream # WebSocket + Kafka +npm run start:aggregator # 롤업 워커 + +# 프로덕션 빌드 +npm run build + +# Docker 이미지 빌드 예시 +docker build -f backend/Dockerfile -t panopticon-query-api --target query-api backend +docker build -f backend/Dockerfile -t panopticon-stream-processor --target stream-processor backend +docker build -f backend/Dockerfile -t panopticon-error-stream --target error-stream backend +docker build -f backend/Dockerfile -t panopticon-aggregator --target aggregator backend +``` + +ECR 푸시 예시: +```bash +aws ecr get-login-password --region | docker login --username AWS --password-stdin .dkr.ecr..amazonaws.com +docker tag panopticon-query-api:latest .dkr.ecr..amazonaws.com/panopticon-query-api:latest +docker push .dkr.ecr..amazonaws.com/panopticon-query-api:latest +``` + +## 서비스별 핵심 환경 변수 +- 공통: `ELASTICSEARCH_NODE`, `OPENSEARCH_USERNAME/PASSWORD`, `OPENSEARCH_REJECT_UNAUTHORIZED`, `USE_ISM`, `KAFKA_BROKERS`(또는 `KAFKA_BROKERS_LOCAL`), `KAFKA_SSL`, `KAFKA_SASL_*` +- Query API: `PORT`, `ROLLUP_ENABLED`, `ROLLUP_THRESHOLD_MINUTES`, `ROLLUP_BUCKET_MINUTES`, `ROLLUP_CACHE_TTL_SECONDS`, `REDIS_HOST`(캐시 활성화) +- Stream Processor: `KAFKA_APM_LOG_TOPIC`, `KAFKA_APM_SPAN_TOPIC`, `_bulk` 튜닝(`BULK_BATCH_SIZE`, `BULK_BATCH_BYTES_MB`, `BULK_FLUSH_INTERVAL_MS`, `BULK_MAX_PARALLEL_FLUSHES`), 처리량 로그(`STREAM_THROUGHPUT_*`) +- Error Stream: `KAFKA_APM_LOG_ERROR_TOPIC`, `ERROR_STREAM_PORT`, `ERROR_STREAM_WS_ORIGINS`, `ERROR_STREAM_WS_PATH` +- Aggregator: `ROLLUP_AGGREGATOR_ENABLED`, `ROLLUP_BUCKET_SECONDS`, `ROLLUP_POLL_INTERVAL_MS`, `ROLLUP_INITIAL_LOOKBACK_MINUTES`, `ROLLUP_INDEX_PREFIX`, `ROLLUP_CHECKPOINT_INDEX` + +## 운영/성능 튜닝 팁 +- **Bulk 색인**: `_bulk` 버퍼 크기와 동시 플러시(`BULK_MAX_PARALLEL_FLUSHES`)를 클러스터 상태에 맞게 조정합니다. +- **Kafka 소비량 모니터링**: `STREAM_THROUGHPUT_*`로 샘플 처리량 로그를 남겨 병목을 조기에 파악합니다. +- **롤업 조회 전략**: 긴 구간 조회는 롤업 버킷(`metrics-apm`)을 우선 사용하고 최신 구간만 RAW를 읽습니다. 캐시 TTL을 상황에 맞게 늘리거나 줄이세요. +- **보안**: TLS/SSL·SASL(AWS MSK IAM 포함)을 환경 변수로 켜고, ISM/ILM/템플릿은 부팅 시 자동 생성되지만 프로덕션에서는 최소 권한 계정으로 접속하세요. +- **WebSocket 알림**: 허용 Origin은 `ERROR_STREAM_WS_ORIGINS`로 제한하고, 에러 토픽 소비가 실패하면 로그로 확인 후 Kafka 설정을 점검합니다. + +## 배포 체크리스트 +- ECR에 최신 이미지 푸시 여부 확인 (`query-api`, `stream-processor`, `error-stream`, `aggregator` 각각). +- ECS/Compose에 필수 환경 변수 입력: ES, Kafka, Redis, 롤업/캐시/버퍼 설정. +- Kafka 토픽 권한 및 SASL/SSL 설정 검증. +- 롤업/캐시 기능 플래그(`ROLLUP_ENABLED`, `ROLLUP_AGGREGATOR_ENABLED`, `ROLLUP_CACHE_TTL_SECONDS`) 원하는 상태인지 확인. +- WebSocket 경로/Origin, 헬스체크 엔드포인트(`/query/health`, `/health`)를 로드밸런서에 등록. + +## 트러블슈팅 힌트 +- **컨슈머 지연**: `_bulk` 설정 과도, Kafka fetch 옵션(`KAFKA_FETCH_*`) 조정 필요 여부 확인. +- **롤업 누락**: Aggregator 로그에서 체크포인트/집계/저장 단계 에러 확인, `ROLLUP_CHECKPOINT_INDEX` 접근 권한 점검. +- **캐시 미동작**: `REDIS_HOST` 설정 여부, TLS 옵션(`REDIS_USE_TLS`, `REDIS_REJECT_UNAUTHORIZED`) 확인. +- **에러 알림 미수신**: `apm.logs.error` 토픽 유입 여부와 Error Stream WebSocket 접속 로그 확인. 프런트 CORS/Origin 설정 점검. diff --git a/backend/README.md b/backend/README.md deleted file mode 100644 index 3861e47..0000000 --- a/backend/README.md +++ /dev/null @@ -1,108 +0,0 @@ -## 🐳 Backend Images for CI/CD - -NestJS 백엔드는 다음 두 이미지로 분리해 ECS에 개별 배포할 수 있습니다. - -``` -backend/src -├── query-api # HTTP 요청을 받아 OpenSearch에서 읽기 전용 응답 제공 -├── stream-processor # MSK(Kafka)에서 소비한 로그·스팬을 정제 후 저장 -├── error-stream # apm.logs.error 토픽을 WebSocket으로 중계해 실시간 알림 제공 -└── shared # DTO/Repository/인프라 연결 등 공통 모듈 -``` - -| 이미지 | Docker target | 역할 | -| ------ | ------------- | ---- | -| `panopticon-query-api` | `query-api` | 브라우저 요청을 받아 OpenSearch를 조회하는 읽기 전용 API | -| `panopticon-stream-processor` | `stream-processor` | MSK(Kafka) 스트림을 소비해 로그/스팬을 정제 후 OpenSearch에 적재 | -| `panopticon-error-stream` | `error-stream` | `apm.logs.error` 토픽을 구독해 WebSocket 으로 프런트엔드(NEXT.js)에 실시간 전송 | -| `panopticon-aggregator` | `aggregator` | `metrics-apm` 롤업 인덱스를 채우는 1분 버킷 집계 전용 워커 | - -### Build & Push - -```bash -# Query API -docker build -f backend/Dockerfile -t panopticon-query-api --target query-api backend - -# Stream Processor -docker build -f backend/Dockerfile -t panopticon-stream-processor --target stream-processor backend - -# Error Stream (Kafka → WebSocket) -docker build -f backend/Dockerfile -t panopticon-error-stream --target error-stream backend - -# Aggregator (Roll-up 워커) -docker build -f backend/Dockerfile -t panopticon-aggregator --target aggregator backend - -# (선택) ECR 로그인 및 푸시 -aws ecr get-login-password --region | docker login --username AWS --password-stdin .dkr.ecr..amazonaws.com -docker tag panopticon-query-api:latest .dkr.ecr..amazonaws.com/panopticon-query-api:latest -docker tag panopticon-stream-processor:latest .dkr.ecr..amazonaws.com/panopticon-stream-processor:latest -docker push .dkr.ecr..amazonaws.com/panopticon-query-api:latest -docker push .dkr.ecr..amazonaws.com/panopticon-stream-processor:latest -``` - -ECS 태스크 정의에서는 각 이미지를 별도 컨테이너로 등록하고, MSK/OpenSearch 등 매니지드 엔드포인트를 환경 변수로 주입하면 됩니다. 로컬 개발 시에는 `infra/docker-compose.yml`을 이용해 동일한 이미지를 Compose 빌드 타깃으로 실행할 수 있습니다. - -### NPM Scripts - -- `npm run build:query-api` / `npm run build:stream-processor`: 각 서버만 컴파일 -- `npm run build:error-stream`: WebSocket 기반 에러 스트림 서버 컴파일 -- `npm run build:aggregator`: 롤업 워커(1분 집계)만 컴파일 -- `npm run start:prod`: `dist/query-api/query-api/main.js` 실행 (읽기 API) -- `npm run start:stream-processor:prod`: `dist/stream-processor/stream-processor/main.js` 실행 (Kafka 컨슈머) -- `npm run start:error-stream:prod`: `dist/error-stream/main.js` 실행 (Kafka→WebSocket 브리지) -- `npm run start:aggregator:prod`: `dist/aggregator/main.js` 실행 (roll-up 워커) - -### Error Stream 환경 변수 - -| 변수 | 설명 | -| --- | --- | -| `KAFKA_APM_LOG_ERROR_TOPIC` | 기본 `apm.logs.error`. MSK 토픽 이름 | -| `ERROR_STREAM_KAFKA_CLIENT_ID` / `ERROR_STREAM_KAFKA_GROUP_ID` | MSK 클러스터 연결용 Kafka client/group 식별자 | -| `ERROR_STREAM_PORT` | WebSocket 서버 포트 (기본 3010) | -| `ERROR_STREAM_WS_ORIGINS` | 허용할 Origin 목록. 콤마로 구분 (기본 모든 Origin 허용) | -| `ERROR_STREAM_WS_PATH` | WebSocket 엔드포인트 경로 (기본 `/ws/error-logs`) | -- `npm run test:app-log` / `npm run test:http-log`: 로컬에서 샘플 Kafka 메시지 전송 (필요 시 `KAFKA_BROKERS_LOCAL=localhost:9092` 등으로 브로커 주소를 덮어쓰세요) - -### Stream Processor 성능 로깅 - -Kafka 컨슈머 처리량을 주기적으로 파악하고 싶다면 다음 환경 변수를 설정하세요. 값이 없으면 기본 설정(가벼운 샘플링)으로 동작합니다. - -| 변수 | 기본값 | 설명 | -| --- | --- | --- | -| `STREAM_THROUGHPUT_BATCH_SIZE` | `5000` | 누적 처리 건수가 이 값 이상 증가했을 때만 처리량 로그를 남깁니다. 0 이하로 설정하면 기능이 꺼집니다. | -| `STREAM_THROUGHPUT_MIN_INTERVAL_MS` | `10000` | 처리량 로그 사이의 최소 간격(ms). 너무 잦은 로깅을 방지합니다. | -| `STREAM_THROUGHPUT_TARGET_COUNT` | _(옵션)_ | 총 N건 처리 완료까지의 예상 소요 시간을 로그에 함께 표시합니다. | - -### Bulk 색인 버퍼 옵션 - -`apm.logs`/`apm.spans` 컨슈머는 Elasticsearch `_bulk` API로 배치 색인을 수행합니다. 아래 환경 변수를 통해 버퍼 크기와 플러시 동시성을 조정할 수 있습니다. - -| 변수 | 기본값 | 설명 | -| --- | --- | --- | -| `BULK_BATCH_SIZE` | `500` | 버퍼에 일정 건수 이상 쌓이면 즉시 flush 합니다. | -| `BULK_BATCH_BYTES_MB` | `5` | 문서 크기 합계가 지정한 MB를 넘기면 즉시 flush 합니다. | -| `BULK_FLUSH_INTERVAL_MS` | `1000` | 위 조건을 만족하지 않아도 해당 시간이 지나면 주기적으로 flush 합니다. | -| `BULK_MAX_PARALLEL_FLUSHES` | `1` | 동시에 실행할 bulk 요청 개수. 클러스터 부하에 맞게 1~4 사이에서 조정하세요. | - -### Aggregator & Query API 롤업 설정 - -`rollup_metrics_spec.md`에 정의된 대로 1분 버킷 롤업을 도입했습니다. `panopticon-aggregator` 컨테이너가 `metrics-apm` 데이터 스트림을 채우고, Query API는 긴 구간(기본 5분 이상)을 조회할 때 자동으로 롤업 데이터를 읽어 raw 집계와 결합합니다. - -- Aggregator 환경 변수는 `backend/src/aggregator/README.md`에 정리되어 있습니다. 필요한 최소 값은 `ELASTICSEARCH_*` 연결 정보와 `ROLLUP_AGGREGATOR_ENABLED` 정도이며, 나머지는 기본값(1분 버킷, 15초 폴링 등)을 따릅니다. -- Query API는 다음 변수를 통해 롤업 전략을 제어합니다. - -| 변수 | 기본값 | 설명 | -| --- | --- | --- | -| `ROLLUP_ENABLED` | `true` | `false`이면 항상 raw 집계만 사용합니다. | -| `ROLLUP_THRESHOLD_MINUTES` | `5` | 조회 구간 길이가 이 값 이상이면 `to - threshold` 이전 범위를 롤업 데이터로 채웁니다. | -| `ROLLUP_BUCKET_MINUTES` | `1` | 롤업 데이터가 사용하는 버킷 크기. 분 단위로 정렬/정규화할 때 사용합니다. | -| `ROLLUP_CACHE_TTL_SECONDS` | `60` | Redis에 저장되는 롤업 결과 TTL. 큰 구간 조회 시 반복 요청을 가볍게 합니다. | -| `ROLLUP_CACHE_PREFIX` | `apm:metrics-rollup` | 롤업 결과 캐시 키 접두사. raw 캐시(`METRICS_CACHE_PREFIX`)와 분리합니다. | -| `ROLLUP_MAX_QUERY_BUCKETS` | `43200` | 한 번의 롤업 조회에서 허용할 최대 버킷 수(기본 30일=43,200분). 과도한 범위를 방지합니다. | - -> ⚠️ 롤업 데이터는 1분 버킷 기준으로 정렬되므로 from/to가 분 단위에 맞지 않아도 자동으로 버킷 경계에 맞춰 조회합니다. 최대 1분 이내의 오차가 있을 수 있다는 점을 염두에 두고 UX를 설계하세요. - -### Query API 성능 프로파일링 - -서비스 메트릭 엔드포인트(`GET /services/{serviceName}/metrics`)가 Elasticsearch 집계를 수행하는데 걸린 시간을 확인하려면 `SERVICE_METRICS_PROFILE=true`를 설정하면 됩니다. -활성화 시 `metrics-profile`(중간 단계)와 `metrics-total`(전체 소요시간) 로그가 콘솔에 출력됩니다.