Initialize RTGS prototype repository

3센터 A-A-A RTGS 실시간총액결제 프로토타입 최초 버전관리 시작.
- backend: Kotlin/Gradle 멀티모듈(sequencer, common, 채널/센터 모듈)
- frontend: Vite + TS
- infra: docker-compose, prometheus/grafana, ELK 네이티브 스크립트
- loadtest(k6), sim(장애/순서/정합성 시나리오), docs(PoC 보고서/복원력방안)
This commit is contained in:
rtgs
2026-07-15 15:37:32 +09:00
commit 58ca23b5d9
140 changed files with 11832 additions and 0 deletions

74
infra/docker-compose.yml Normal file
View File

@@ -0,0 +1,74 @@
name: rtgs
# 로컬 경량 스택 (1센터 DC1). acs와 포트/프로젝트명(-p rtgs 자동: name=rtgs) 분리.
# 서비스(Sequencer/Chanel/Dior/Hermes/Prada)는 호스트에서 gradlew bootRun으로 구동하며
# 아래 인프라(localhost:9092/5433/27017)에 접속한다.
services:
postgres:
image: postgres:16
container_name: rtgs-postgres
environment:
POSTGRES_DB: rtgs
POSTGRES_USER: rtgs
POSTGRES_PASSWORD: rtgs
ports:
- "5433:5432" # acs=5432 회피
volumes:
- pgdata:/var/lib/postgresql/data
- ./init/postgres-init.sql:/docker-entrypoint-initdb.d/01-init.sql:ro
healthcheck:
test: ["CMD-SHELL", "pg_isready -U rtgs -d rtgs"]
interval: 5s
timeout: 3s
retries: 20
mongo:
image: mongo:7
container_name: rtgs-mongo
ports:
- "27017:27017"
volumes:
- mongodata:/data/db
kafka:
image: apache/kafka:3.8.1
container_name: rtgs-kafka
ports:
- "9092:9092"
environment:
# --- KRaft 단일 노드(broker+controller) ---
KAFKA_NODE_ID: "1"
KAFKA_PROCESS_ROLES: "broker,controller"
KAFKA_CONTROLLER_QUORUM_VOTERS: "1@localhost:9093"
# HOST(호스트의 gradle bootRun) / DOCKER(컨테이너 내 kafka-ui) 이중 리스너
KAFKA_LISTENERS: "HOST://:9092,DOCKER://:29092,CONTROLLER://:9093"
KAFKA_ADVERTISED_LISTENERS: "HOST://localhost:9092,DOCKER://kafka:29092"
KAFKA_CONTROLLER_LISTENER_NAMES: "CONTROLLER"
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: "CONTROLLER:PLAINTEXT,HOST:PLAINTEXT,DOCKER:PLAINTEXT"
KAFKA_INTER_BROKER_LISTENER_NAME: "DOCKER"
# --- 단일 노드 복제/ISR ---
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: "1"
KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: "1"
KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: "1"
KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS: "0"
# --- 저널 전역 순서 보장: 기본 파티션 1개(단일 파티션이라야 전역 순서 성립) ---
KAFKA_NUM_PARTITIONS: "1"
KAFKA_AUTO_CREATE_TOPICS_ENABLE: "true"
# 선택: Kafka 관리 UI (docker compose --profile ui up)
kafka-ui:
image: provectuslabs/kafka-ui:latest
container_name: rtgs-kafka-ui
profiles: ["ui"]
ports:
- "8095:8080"
environment:
KAFKA_CLUSTERS_0_NAME: rtgs
KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS: kafka:29092
depends_on:
- kafka
volumes:
pgdata:
mongodata:

View File

@@ -0,0 +1,63 @@
{
"uid": "rtgs-overview",
"title": "RTGS Overview",
"tags": ["rtgs"],
"timezone": "browser",
"schemaVersion": 39,
"version": 1,
"refresh": "5s",
"time": { "from": "now-15m", "to": "now" },
"templating": { "list": [] },
"annotations": { "list": [] },
"panels": [
{
"id": 1, "type": "timeseries", "title": "접수 TPS (Chanel /pay)",
"datasource": { "type": "prometheus", "uid": "rtgs-prom" },
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 0 },
"fieldConfig": { "defaults": { "unit": "reqps" }, "overrides": [] },
"targets": [ { "refId": "A", "datasource": { "type": "prometheus", "uid": "rtgs-prom" }, "expr": "sum(rate(http_server_requests_seconds_count{uri=\"/pay/customer\"}[1m]))", "legendFormat": "접수/초" } ]
},
{
"id": 2, "type": "timeseries", "title": "접수 지연 p95 (s)",
"datasource": { "type": "prometheus", "uid": "rtgs-prom" },
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 0 },
"fieldConfig": { "defaults": { "unit": "s" }, "overrides": [] },
"targets": [ { "refId": "A", "datasource": { "type": "prometheus", "uid": "rtgs-prom" }, "expr": "histogram_quantile(0.95, sum by (le) (rate(http_server_requests_seconds_bucket{uri=\"/pay/customer\"}[5m])))", "legendFormat": "p95" } ]
},
{
"id": 3, "type": "timeseries", "title": "정산 처리율 (Hermes, status별)",
"datasource": { "type": "prometheus", "uid": "rtgs-prom" },
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 8 },
"fieldConfig": { "defaults": { "unit": "ops" }, "overrides": [] },
"targets": [ { "refId": "A", "datasource": { "type": "prometheus", "uid": "rtgs-prom" }, "expr": "sum by (status) (rate(rtgs_settlements_total[1m]))", "legendFormat": "{{status}}" } ]
},
{
"id": 4, "type": "timeseries", "title": "정산 지연 p95 (Hermes)",
"datasource": { "type": "prometheus", "uid": "rtgs-prom" },
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 8 },
"fieldConfig": { "defaults": { "unit": "s" }, "overrides": [] },
"targets": [ { "refId": "A", "datasource": { "type": "prometheus", "uid": "rtgs-prom" }, "expr": "histogram_quantile(0.95, sum by (le) (rate(rtgs_settle_seconds_bucket[5m])))", "legendFormat": "p95" } ]
},
{
"id": 5, "type": "timeseries", "title": "Kafka Consumer Lag",
"datasource": { "type": "prometheus", "uid": "rtgs-prom" },
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 16 },
"fieldConfig": { "defaults": { "unit": "short" }, "overrides": [] },
"targets": [ { "refId": "A", "datasource": { "type": "prometheus", "uid": "rtgs-prom" }, "expr": "sum by (service) (kafka_consumer_fetch_manager_records_lag)", "legendFormat": "{{service}}" } ]
},
{
"id": 6, "type": "timeseries", "title": "JVM Heap 사용 (service별)",
"datasource": { "type": "prometheus", "uid": "rtgs-prom" },
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 16 },
"fieldConfig": { "defaults": { "unit": "bytes" }, "overrides": [] },
"targets": [ { "refId": "A", "datasource": { "type": "prometheus", "uid": "rtgs-prom" }, "expr": "sum by (service) (jvm_memory_used_bytes{area=\"heap\"})", "legendFormat": "{{service}}" } ]
},
{
"id": 7, "type": "stat", "title": "완결 누계 (Prada, status별)",
"datasource": { "type": "prometheus", "uid": "rtgs-prom" },
"gridPos": { "h": 6, "w": 24, "x": 0, "y": 24 },
"fieldConfig": { "defaults": { "unit": "short" }, "overrides": [] },
"targets": [ { "refId": "A", "datasource": { "type": "prometheus", "uid": "rtgs-prom" }, "expr": "sum by (status) (rtgs_finality_total)", "legendFormat": "{{status}}" } ]
}
]
}

View File

@@ -0,0 +1,9 @@
apiVersion: 1
providers:
- name: rtgs
type: file
disableDeletion: false
allowUiUpdates: true
options:
path: C:\ai-dev\workspace\rtgs\infra\grafana\dashboards
foldersFromFilesStructure: false

View File

@@ -0,0 +1,9 @@
apiVersion: 1
datasources:
- name: Prometheus
uid: rtgs-prom
type: prometheus
access: proxy
url: http://localhost:9090
isDefault: true
editable: true

View File

@@ -0,0 +1,19 @@
@echo off
rem ===================================================================
rem 3센터 삼중화용 빈 DB 3개 생성 (rtgs_dc1/dc2/dc3). 멱등.
rem 스키마·시드는 각 센터 LouisVuitton 기동 시 Flyway가 담당(V1~V4).
rem 전제: PostgreSQL(:5433)이 이미 기동(start-infra-native 또는 run-dc1).
rem ===================================================================
call C:\ai-dev\scripts\env.cmd
set "PGBIN=%DEV_HOME%\apps\postgresql-16.4\bin"
for %%D in (rtgs_dc1 rtgs_dc2 rtgs_dc3) do (
"%PGBIN%\psql.exe" -h localhost -p 5433 -U rtgs -d postgres -tAc "SELECT 1 FROM pg_database WHERE datname='%%D'" | findstr "1" >nul
if errorlevel 1 (
echo [init-3c] creating empty db %%D ...
"%PGBIN%\createdb.exe" -h localhost -p 5433 -U rtgs %%D
) else (
echo [init-3c] db %%D already exists.
)
)
echo [init-3c] done. 각 센터 LouisVuitton이 기동 시 Flyway로 스키마 생성.

View File

@@ -0,0 +1,11 @@
-- =====================================================================
-- [LEGACY] 이 파일은 더 이상 스키마 원본이 아닙니다.
--
-- 2026-07-10부터 DB 스키마/시드는 **Flyway**가 관리합니다(단일 출처).
-- 원본: backend/louisvuitton/src/main/resources/db/migration/V1__baseline.sql
-- 이후 변경: 같은 폴더에 V2__*.sql, V3__*.sql ... 로 추가(수기 ALTER 금지)
-- 적용: LouisVuitton(:8099) 기동 시 Flyway가 자동 적용
-- (기존 운영 DB는 baseline 처리되어 무변경, 신규 빈 DB는 V1부터 생성)
--
-- 인프라 스크립트(start-infra-native.cmd)는 빈 DB만 생성합니다.
-- =====================================================================

23
infra/prometheus.yml Normal file
View File

@@ -0,0 +1,23 @@
# RTGS Prometheus 스크랩 설정 — 7개 서비스의 /actuator/prometheus 수집(B4).
global:
scrape_interval: 5s
evaluation_interval: 15s
scrape_configs:
- job_name: rtgs
metrics_path: /actuator/prometheus
static_configs:
- targets: ['localhost:8090']
labels: { service: sequencer, center: DC1 }
- targets: ['localhost:8091']
labels: { service: chanel, center: DC1 }
- targets: ['localhost:8092']
labels: { service: dior, center: DC1 }
- targets: ['localhost:8093']
labels: { service: hermes, center: DC1 }
- targets: ['localhost:8094']
labels: { service: prada, center: DC1 }
- targets: ['localhost:8095']
labels: { service: gucci, center: DC1 }
- targets: ['localhost:8099']
labels: { service: louisvuitton, center: DC1 }

View File

@@ -0,0 +1,27 @@
@echo off
rem ===================================================================
rem RTGS 로그관리 ELK 네이티브 기동 (Docker 없이). 각각 새 창으로 뜬다.
rem Elasticsearch :9200 (데이터 home\es-data)
rem Logstash :5000 (서비스 Logback TCP 수신 -> ES rtgs-logs-*)
rem Kibana :5601 (로그 조회 UI) -> http://localhost:5601 (Discover > RTGS Logs)
rem 주의: ELK는 메모리를 많이 쓴다(ES/Logstash 각 ~1GB). 서비스+인프라와 동시 구동 시 여유 확인.
rem ===================================================================
call C:\ai-dev\scripts\env.cmd
set "ES=%DEV_HOME%\apps\elasticsearch-8.15.2"
set "LS=%DEV_HOME%\apps\logstash-8.15.2"
set "KB=%DEV_HOME%\apps\kibana-8.15.2"
echo [elk] starting Elasticsearch :9200 (new window)...
start "RTGS Elasticsearch :9200" cmd /k "set ES_JAVA_HOME=%ES%\jdk&& %ES%\bin\elasticsearch.bat"
echo [elk] waiting for ES to come up...
timeout /t 40 /nobreak >nul
echo [elk] starting Logstash :5000 (new window)...
start "RTGS Logstash :5000" cmd /k "set LS_JAVA_HOME=%LS%\jdk&& %LS%\bin\logstash.bat -f %DEV_HOME%\home\logstash\rtgs.conf --path.data %DEV_HOME%\home\logstash\data"
echo [elk] starting Kibana :5601 (new window; ~1 min to be ready)...
start "RTGS Kibana :5601" cmd /k "%KB%\bin\kibana.bat"
echo.
echo [elk] launched. Kibana: http://localhost:5601 (Discover ^> RTGS Logs, index rtgs-logs-*)

20
infra/start-grafana.cmd Normal file
View File

@@ -0,0 +1,20 @@
@echo off
rem ===================================================================
rem Grafana (관측성 B4 시각화) — Prometheus(:9090) 위에 대시보드.
rem 포터블: apps\grafana-13.1.0, 데이터 home\grafana-data.
rem 프로비저닝: infra\grafana\provisioning (Prometheus 데이터소스 + RTGS 대시보드 자동).
rem UI: http://localhost:3000 (익명 Admin 허용 — 로그인 없이 조회/편집)
rem ===================================================================
set "GF=C:\ai-dev\apps\grafana-13.1.0"
set "GF_PATHS_HOME=%GF%"
set "GF_PATHS_DATA=C:\ai-dev\home\grafana-data"
set "GF_PATHS_LOGS=C:\ai-dev\home\grafana-data\log"
set "GF_PATHS_PROVISIONING=C:\ai-dev\workspace\rtgs\infra\grafana\provisioning"
set "GF_SERVER_HTTP_PORT=3000"
set "GF_AUTH_ANONYMOUS_ENABLED=true"
set "GF_AUTH_ANONYMOUS_ORG_ROLE=Admin"
set "GF_USERS_DEFAULT_THEME=light"
if not exist "%GF_PATHS_DATA%" mkdir "%GF_PATHS_DATA%"
echo [grafana] starting on :3000 (datasource=Prometheus:9090, dashboard=RTGS Overview)...
start "Grafana :3000" "%GF%\bin\grafana.exe" server --homepath "%GF%"
echo [grafana] UI: http://localhost:3000 (Dashboards ^> RTGS Overview)

View File

@@ -0,0 +1,45 @@
@echo off
rem ===================================================================
rem RTGS 네이티브 인프라 기동 (Docker 대체 - 이 PC는 가상화 미지원).
rem PostgreSQL(:5433) + Kafka(:9092, KRaft 단일노드)
rem 문서저장(원전문/조회사본)도 PostgreSQL 사용(MongoDB는 EDR 파일락으로 제외).
rem 멱등: 이미 초기화/기동돼 있으면 건너뜀.
rem ===================================================================
call C:\ai-dev\scripts\env.cmd
set "JAVA_HOME=%JDK21_HOME%"
set "PGBIN=%DEV_HOME%\apps\postgresql-16.4\bin"
set "PGDATA=%DEV_HOME%\home\pgsql-data"
set "KAFKA=%DEV_HOME%\apps\kafka-3.8.1"
set "KDATA=%DEV_HOME%\home\kafka"
set "KAFKA_HEAP_OPTS=-Xmx1G -Xms1G"
rem ---------- PostgreSQL ----------
if not exist "%PGDATA%\PG_VERSION" (
echo [infra] initdb PostgreSQL cluster...
"%PGBIN%\initdb.exe" -D "%PGDATA%" -U rtgs -E UTF8 --locale=C -A trust
)
"%PGBIN%\pg_ctl.exe" status -D "%PGDATA%" >nul 2>&1
if errorlevel 1 (
echo [infra] starting PostgreSQL :5433...
"%PGBIN%\pg_ctl.exe" -D "%PGDATA%" -o "-p 5433" -l "%PGDATA%\server.log" -w start
) else (
echo [infra] PostgreSQL already running.
)
rem create db if missing (스키마/시드는 Flyway가 담당 — LouisVuitton 기동 시 V1__baseline 부터 적용)
"%PGBIN%\psql.exe" -h localhost -p 5433 -U rtgs -d postgres -tAc "SELECT 1 FROM pg_database WHERE datname='rtgs'" | findstr "1" >nul
if errorlevel 1 (
echo [infra] creating empty db rtgs [schema by Flyway]...
"%PGBIN%\createdb.exe" -h localhost -p 5433 -U rtgs rtgs
)
rem ---------- Kafka (KRaft) ----------
if not exist "%KDATA%\kraft-logs\meta.properties" (
echo [infra] formatting Kafka KRaft storage...
for /f "delims=" %%i in ('%KAFKA%\bin\windows\kafka-storage.bat random-uuid') do set "CID=%%i"
"%KAFKA%\bin\windows\kafka-storage.bat" format -t %CID% -c "%KDATA%\server.properties"
)
echo [infra] starting Kafka :9092 (new window; keep it open)...
start "RTGS Kafka :9092" cmd /k "%KAFKA%\bin\windows\kafka-server-start.bat %KDATA%\server.properties"
echo.
echo [infra] PostgreSQL :5433 (db rtgs), Kafka :9092 up. (MongoDB not used - docs in PostgreSQL)

View File

@@ -0,0 +1,12 @@
@echo off
rem ===================================================================
rem Prometheus (관측성 B4) — RTGS 7개 서비스 /actuator/prometheus 스크랩.
rem 포터블: apps\prometheus-3.13.0, 데이터는 home\prometheus-data.
rem UI: http://localhost:9090 (Status > Targets 로 수집 상태 확인)
rem ===================================================================
set "PROM=C:\ai-dev\apps\prometheus-3.13.0"
set "PDATA=C:\ai-dev\home\prometheus-data"
if not exist "%PDATA%" mkdir "%PDATA%"
echo [prometheus] starting on :9090 (scrape rtgs 7 services)...
start "Prometheus :9090" "%PROM%\prometheus.exe" --config.file="%~dp0prometheus.yml" --storage.tsdb.path="%PDATA%" --web.listen-address=0.0.0.0:9090
echo [prometheus] UI: http://localhost:9090 (Status ^> Targets)

View File

@@ -0,0 +1,13 @@
@echo off
rem RTGS 네이티브 인프라 종료. (Kafka 창은 자동 종료 시도; 안 되면 창을 닫으세요)
call C:\ai-dev\scripts\env.cmd
set "PGBIN=%DEV_HOME%\apps\postgresql-16.4\bin"
set "PGDATA=%DEV_HOME%\home\pgsql-data"
echo [infra] stopping Kafka (kafka-server-stop.bat uses removed 'wmic', so kill via PowerShell)...
powershell -NoProfile -Command "Get-CimInstance Win32_Process | Where-Object { $_.CommandLine -like '*kafka*server.properties*' -and $_.Name -eq 'java.exe' } | ForEach-Object { Stop-Process -Id $_.ProcessId -Force }"
echo [infra] stopping PostgreSQL...
"%PGBIN%\pg_ctl.exe" -D "%PGDATA%" stop -m fast
echo [infra] done.