대용량 인게임 거래소 이상 거래 패턴 실시간 AI 탐지와 트랜잭션 자동 동결 설계
대용량 인게임 거래소에서 시세 조작과 계정 탈취성 거래를 실시간으로 감지하는 AI 파이프라인을 설계합니다. Kafka·Flink 기반 위험도 계산, 원자적 트랜잭션 자동 동결, 오탐 해제와 감사 로그까지 실전 기준으로 정리합니다.

핵심 요약
대용량 인게임 거래소의 이상 거래 탐지는 거래 이벤트를 Kafka로 수집하고 Apache Flink의 이벤트 시간 윈도우에서 속도, 가격 편차, 계정 관계, 결제 흐름을 계산한 뒤 AI 모델과 정책 엔진으로 위험도를 판정하는 방식이 안정적입니다. 위험 거래는 모델이 직접 취소하지 않고 거래 DB의 원자적 상태 전이와 아이템·재화 보류를 통해 HOLD 또는 FREEZE 상태로 전환해야 중복 처리와 우회 거래를 막을 수 있습니다.
핵심은 세 가지입니다.
- Kafka와 Flink로 대규모 거래 이벤트를 실시간 처리한다.
- 규칙 기반 탐지와 머신러닝 점수를 결합해 오탐과 미탐을 함께 관리한다.
- PostgreSQL 트랜잭션과 멱등성 키를 사용해 거래 승인과 자동 동결의 정합성을 보장한다.
대용량 인게임 거래소 이상 거래 탐지가 어려운 이유
거래소의 정상 거래량은 이벤트, 신규 아이템 출시, 서버 통합, 인플레이션에 따라 크게 변합니다. 단순히 분당 거래 횟수가 많다는 이유만으로 계정을 차단하면 정상적인 대량 구매자와 길드 활동을 공격자로 잘못 분류할 수 있습니다.
반대로 공격자는 하나의 계정에서만 이상 행동을 하지 않습니다. 다수의 부계정이 특정 계정으로 재화를 모으거나 짧은 시간 동안 시세보다 비정상적으로 높은 가격에 거래한 뒤 환전하는 형태를 사용합니다. 따라서 단일 이벤트가 아니라 시간 흐름과 계정 간 관계를 함께 분석해야 합니다.
| 탐지 대상 | 대표 신호 | 단순 임계값의 한계 |
|---|---|---|
| 시세 조작 | 기준가 대비 거래 가격 편차, 반복 매수·매도 | 신규 아이템은 정상 가격 변동도 큼 |
| 재화 세탁 | 다수 계정에서 한 계정으로 반복 송금 | 길드 창고와 정상 지원 거래가 섞임 |
| 봇 거래 | 일정한 주기, 비정상적으로 높은 처리량 | 자동화된 정상 상점 운영도 존재함 |
| 계정 탈취 | 신규 기기, 지역 급변, 고가 아이템 이동 | VPN과 출장 사용자를 오탐할 수 있음 |
| 담합 거래 | 동일 IP·기기·친구 그래프의 반복 거래 | 가족이나 PC방 환경과 구분해야 함 |
실시간 AI 이상 거래 탐지 아키텍처는 어떻게 구성할까?
권장 구조는 거래의 진실을 보관하는 데이터베이스와 분석용 스트림을 분리하는 것입니다. PostgreSQL은 주문과 원장 상태의 기준 저장소로 사용하고 Kafka는 이벤트 전달을 담당하며 Flink는 짧은 시간 범위의 특징량을 계산합니다. 장기 분석과 운영 대시보드는 ClickHouse나 별도 분석 저장소로 분리할 수 있습니다.
flowchart LR
A[거래 요청] --> B[거래 API]
B --> C[정책 게이트와 위험도 캐시]
C --> D{허용 여부}
D -->|ALLOW| E[원장 기록과 정산]
D -->|HOLD 또는 FREEZE| F[아이템·재화 보류]
E --> G[Transactional Outbox]
F --> G
G --> H[Kafka 거래 이벤트]
H --> I[Flink 이벤트 시간 윈도우]
I --> J[특징량 계산]
J --> K[AI 모델과 정책 엔진]
K --> L[위험도 캐시·사례 큐·감사 로그]
L --> C
실시간 경로에서는 모델 호출이 느려질 때 거래 API 전체가 멈추지 않도록 제한 시간과 기본 동작을 정의해야 합니다. 예를 들어 고가 거래에서 위험도 서비스를 100ms 안에 응답하지 못하면 즉시 정산하지 않고 HOLD로 전환하는 보수적 정책을 선택할 수 있습니다. 반면 저가 거래는 기존 캐시 점수와 규칙만으로 처리해 지연을 줄일 수 있습니다.
거래 이벤트 스키마를 먼저 고정한다
분석 파이프라인은 이벤트 이름보다 스키마 안정성이 중요합니다. 이벤트에는 반드시 고유한 event_id, 거래 요청의 idempotency_key, 발생 시각과 서버 수신 시각을 함께 포함합니다.
{
"event_id": "evt_01JXYZ",
"event_type": "MARKET_TRADE_CREATED",
"occurred_at": "2026-08-24T12:00:01.235Z",
"received_at": "2026-08-24T12:00:01.248Z",
"trade_id": "trade_88421",
"buyer_id": "account_102",
"seller_id": "account_907",
"item_id": "item_dragon_sword",
"quantity": 1,
"unit_price": 850000,
"currency": "GOLD",
"device_id_hash": "device_hash",
"ip_prefix": "203.0.113.0/24",
"idempotency_key": "client-request-7f2c"
}
개인정보는 탐지에 필요한 범위만 남겨야 합니다. 원본 IP를 분석 저장소에 장기간 보관하는 대신 짧은 보존 기간의 접근 통제된 저장소에 두고 스트림 특징량에는 지역 코드나 /24 네트워크 대역처럼 목적에 맞게 축약한 값을 사용하는 방식이 일반적입니다.
실시간 거래 특징량은 무엇을 계산해야 하는가?
특징량은 최근 거래의 속도, 가격, 관계, 계정 상태를 여러 시간 범위로 계산합니다. 하나의 윈도우만 사용하면 공격자가 탐지 구간을 피할 수 있으므로 1분, 5분, 1시간, 24시간 범위를 함께 유지하는 편이 좋습니다.
| 범주 | 특징량 예시 | 계산 방식 |
|---|---|---|
| 속도 | 계정별 거래 수, 순수익, 취소 횟수 | Tumbling 또는 Sliding Window |
| 가격 | 기준가 대비 편차, 동일 아이템 가격 분산 | 이동 중앙값과 MAD |
| 관계 | 신규 상대 수, 공통 기기 수, 자금 흐름 집중도 | 계정 그래프 집계 |
| 행동 | 로그인 후 첫 거래까지 시간, 주기성 | 세션·시계열 특징량 |
| 자산 | 보유량 변화, 고가 아이템 이동량 | 원장 델타 집계 |
| 신뢰도 | 계정 연령, 인증 수준, 제재 이력 | 계정 프로필 결합 |
가격 기준은 평균보다 이동 중앙값을 권장합니다. 공격자가 고가 거래를 반복하면 평균 자체가 오염될 수 있기 때문입니다. 분산이 일정하지 않은 아이템에는 다음과 같이 중앙값 절대 편차를 사용할 수 있습니다.
현재 거래 가격 p와 기준 가격 m의 편차는 MAD가 0일 때를 별도로 처리해야 합니다. 실무에서는 MAD + epsilon을 분모로 사용하고 거래량이 너무 적은 아이템은 글로벌 등급별 기준가로 폴백합니다.

AI 모델과 규칙 기반 탐지를 어떻게 결합할까?
머신러닝 모델 하나로 모든 차단 결정을 맡기면 설명 가능성과 운영 안정성이 떨어집니다. 다음과 같이 세 계층으로 분리하면 모델을 교체해도 정책과 거래 원장은 안정적으로 유지됩니다.
- 규칙 엔진: 명백한 중복 요청, 이미 동결된 계정, 존재하지 않는 아이템처럼 즉시 판정 가능한 조건을 처리합니다.
- AI 스코어러: 여러 특징량을 입력받아
0에서1사이의 위험도를 반환합니다. - 정책 엔진: 위험도, 거래 금액, 계정 신뢰도, 최근 제재 이력을 조합해
ALLOW,HOLD,FREEZE를 결정합니다.
실시간 모델은 LightGBM이나 XGBoost를 ONNX로 내보내 추론 서비스에 탑재할 수 있습니다. 모델 출력은 확률처럼 보이더라도 실제 차단 확률과 동일하지 않을 수 있으므로 운영 데이터로 임계값을 보정하고 정밀도·재현율·거짓 양성률을 함께 모니터링해야 합니다.
예시 위험도 함수는 다음과 같이 정의할 수 있습니다.
초기 운영에서는 다음과 같은 정책을 둘 수 있습니다. 수치는 게임 경제와 정상 거래 분포를 검증한 뒤 조정해야 합니다.
| 위험도 | 기본 결정 | 처리 |
|---|---|---|
0.00~0.39 | ALLOW | 즉시 정산하고 이벤트 발행 |
0.40~0.74 | HOLD | 보류 원장에 기록하고 추가 특징량 수집 |
0.75~1.00 | FREEZE | 거래 정산 중단, 관련 계정과 자산 잠금 |
중요한 점은 모델이 계정 전체를 자동 영구 정지하지 않는 것입니다. 모델은 조사 대상과 거래 보호 수준을 제안하고 정책 엔진은 범위와 만료 시간을 결정해야 합니다. 그래야 오탐 발생 시 복구 범위를 제한할 수 있습니다.
트랜잭션 자동 동결은 어떻게 구현할까?
동결은 단순히 accounts.status = 'FROZEN'을 업데이트하는 기능이 아닙니다. 이미 생성된 주문, 보류 중인 아이템, 재화 원장, 출금 요청이 서로 다른 상태를 가지면 동결 이후에도 자산이 빠져나갈 수 있습니다.
거래 상태는 다음처럼 명시적인 상태 머신으로 관리합니다.
| 상태 | 의미 | 허용되는 다음 상태 |
|---|---|---|
CREATED | 거래 요청 생성 | AUTHORIZED, CANCELED |
AUTHORIZED | 구매자 재화와 판매자 아이템 검증 완료 | SETTLED, HOLD, FROZEN |
HOLD | 추가 분석을 기다리는 상태 | SETTLED, FROZEN, CANCELED |
SETTLED | 양쪽 원장에 정산 완료 | REVERSED |
FROZEN | 정산과 출금이 차단된 상태 | RELEASED, CANCELED |
RELEASED | 조사 결과 해제 | SETTLED, CANCELED |
고위험 거래의 동결 처리는 다음 조건을 한 번의 DB 트랜잭션에서 처리해야 합니다.
- 주문 상태가 현재 상태에서 예상한 상태인지 확인한다.
- 구매자 재화와 판매자 아이템에 보류 표시를 설정한다.
- 필요한 경우 관련 계정의 거래·출금 기능을 잠근다.
freeze_case와audit_log를 기록한다.- 후속 알림과 사례 큐 등록을 Outbox 이벤트로 남긴다.
PostgreSQL 기준의 핵심 처리 예시는 다음과 같습니다.
BEGIN;
SELECT status, version
FROM trade_orders
WHERE trade_id = $1
FOR UPDATE;
SELECT account_id, trade_status, withdrawal_status
FROM accounts
WHERE account_id IN ($2, $3)
ORDER BY account_id
FOR UPDATE;
UPDATE trade_orders
SET status = 'FROZEN',
freeze_reason = $4,
version = version + 1,
updated_at = now()
WHERE trade_id = $1
AND status IN ('AUTHORIZED', 'HOLD');
UPDATE asset_holds
SET status = 'FROZEN', updated_at = now()
WHERE trade_id = $1
AND status = 'HELD';
INSERT INTO freeze_cases
(case_id, trade_id, risk_score, reason_code, expires_at)
VALUES
($5, $1, $6, $4, now() + interval '24 hours');
INSERT INTO outbox_events
(event_id, aggregate_id, event_type, payload, created_at)
VALUES
($7, $1, 'TRADE_FROZEN', $8, now());
COMMIT;
UPDATE 결과가 1행이 아니면 이미 다른 요청이 처리한 것이므로 중복 동결로 간주하고 현재 상태를 다시 조회합니다. API 요청에는 반드시 idempotency_key를 사용하고 trade_id와 함께 유니크 인덱스를 설정해야 재시도나 네트워크 중복으로 이중 정산이 발생하지 않습니다.
Kafka와 Outbox를 함께 사용할 때 주의할 점
데이터베이스 커밋과 Kafka 발행을 각각 따로 수행하면 다음과 같은 문제가 생깁니다.
- DB 커밋 후 프로세스가 종료되어 Kafka 이벤트가 누락된다.
- Kafka 발행 후 DB 롤백이 일어나 분석 결과와 실제 원장이 달라진다.
- 소비자 재시도로 동결 이벤트가 여러 번 적용된다.
따라서 거래 상태 변경과 Outbox 레코드 삽입을 같은 DB 트랜잭션에서 처리합니다. 별도 Publisher가 Outbox를 읽어 Kafka에 발행하고 소비자는 event_id를 기준으로 중복을 제거합니다. Kafka의 전달 보장은 유용하지만 최종 정합성은 소비자의 멱등 처리와 원장 상태 검증으로 확보해야 합니다.
Flink에서는 occurred_at을 이벤트 시간으로 사용하고 Watermark를 설정합니다. 늦게 도착한 이벤트는 허용 지연 시간 안에서 집계에 반영하고 그 이후에는 보정 이벤트를 별도로 발행합니다. 서버 수신 시각만 사용하면 네트워크 지연 때문에 정상 거래가 잘못된 윈도우에 들어갈 수 있습니다.
구현 절차: 실시간 이상 거래 탐지와 자동 동결을 단계별로 만들기
1단계. 정산 원장과 상태 머신을 먼저 만든다
거래 API, 아이템 보관함, 재화 원장을 하나의 진실 공급원으로 정의합니다. CREATED부터 SETTLED까지의 전이를 명시하고 동결 가능한 상태와 이미 정산된 상태의 보상·회수 정책을 구분합니다.
이 단계에서 동결 기능을 분석 시스템보다 먼저 검증해야 합니다. AI가 정확해도 동결 상태가 출금 API에서 무시되면 보안 효과가 없습니다.
2단계. 이벤트와 실시간 특징량을 추가한다
거래 생성, 승인, 정산, 취소, 출금, 로그인, 기기 변경 이벤트를 동일한 event_id 규칙으로 발행합니다. Flink 작업은 계정별·아이템별·관계별 특징량을 계산하고 Redis에는 짧은 TTL의 최신 위험도와 카운터만 저장합니다.
특징량 계산과 원장 처리를 같은 트랜잭션으로 묶으려 하지 않는 것이 좋습니다. 원장은 강한 일관성이 필요하지만 스트림 특징량은 지연과 재처리를 허용하는 분석 데이터이기 때문입니다.
3단계. AI 점수와 정책 엔진을 연결한다
오프라인 학습 데이터에는 정상 거래와 실제 조사 결과를 분리해 넣습니다. 시간 기준으로 학습·검증 데이터를 나눠 미래 이벤트가 과거 학습 데이터에 섞이는 데이터 누수를 막습니다.
온라인에서는 최신 특징량을 모델에 전달하고 결과를 정책 엔진에 넘깁니다. 정책 엔진은 거래 금액, 계정 인증 수준, 점수 신뢰도, 현재 동결 상태를 함께 확인해 최종 결정을 반환합니다.
4단계. 동결과 해제의 운영 절차를 만든다
동결에는 reason_code, 모델 버전, 특징량 스냅샷, 정책 버전, 만료 시간을 남깁니다. 운영자는 사례 큐에서 관련 거래와 자금 흐름을 확인한 뒤 해제, 부분 회수, 영구 제재 중 하나를 선택합니다.
자동 동결은 기본적으로 만료되는 임시 보호 조치로 설계하는 편이 안전합니다. 단, 계정 탈취나 확정적인 중복 결제처럼 즉시 조치가 필요한 유형은 별도의 높은 등급 정책으로 분리할 수 있습니다.

운영에서 측정해야 할 지표
탐지 모델의 정확도만 보면 실제 서비스 품질을 놓치기 쉽습니다. 다음 지표를 거래량과 금액 기준으로 함께 관찰합니다.
| 지표 | 의미 | 운영 판단 |
|---|---|---|
| 탐지 지연 시간 | 이벤트 발생부터 정책 결정까지 걸린 시간 | p95·p99 추적 |
| 거짓 양성률 | 정상 거래를 HOLD·FREEZE한 비율 | 고객 불편과 직결 |
| 거짓 음성률 | 사후 확인된 이상 거래 중 놓친 비율 | 경제 피해와 직결 |
| 보호 금액 | 동결로 보호한 재화·아이템 가치 | 정책 효과 측정 |
| 해제 시간 | 동결부터 운영 판단까지 걸린 시간 | CS 품질 측정 |
| 재처리율 | 장애 후 이벤트 재처리 비율 | 파이프라인 안정성 확인 |
모델 버전이 바뀔 때는 전체 트래픽에 즉시 적용하지 말고 Shadow Mode로 신규 점수만 기록하거나 일부 아이템군에 Canary 적용합니다. 모델 변경으로 특정 거래 유형의 거짓 양성률이 급증하면 정책 엔진에서 해당 버전을 즉시 비활성화할 수 있어야 합니다.
자주 묻는 질문(FAQ)
AI 모델이 거래를 직접 취소하게 해도 되는가?
권장하지 않습니다. 모델은 위험도와 근거를 제공하고 정책 엔진이 ALLOW, HOLD, FREEZE를 결정해야 합니다. 최종 상태 변경은 멱등성과 감사 로그를 갖춘 거래 서비스가 수행해야 합니다.
Redis에 저장된 위험도 캐시가 만료되면 거래를 어떻게 처리하는가?
거래 금액과 위험 등급에 따라 기본 동작을 나눕니다. 고액 또는 출금 연계 거래는 캐시가 없으면 HOLD로 보내고 저액 거래는 규칙 기반 점수와 계정 신뢰도만으로 제한적으로 승인할 수 있습니다.
이미 정산된 거래도 자동 동결할 수 있는가?
정산 완료 거래를 소급해 FROZEN으로 바꾸기보다 원장 반전, 자산 회수, 출금 보류를 별도 상태로 관리해야 합니다. 그래야 원장 감사 추적과 고객 보상 처리를 분리할 수 있습니다.
정리
대용량 인게임 거래소의 이상 거래 문제는 AI 모델만 추가해서 해결되지 않습니다. Kafka와 Flink로 거래 흐름을 실시간 분석하고 규칙·모델·관계 그래프를 결합해 위험도를 계산한 뒤 원장 DB의 상태 머신과 Outbox를 통해 동결을 적용해야 합니다.
가장 먼저 구현할 범위는 멱등성 키 → 거래 상태 머신 → 아이템·재화 보류 → 감사 로그입니다. 그 위에 실시간 특징량과 AI 모델을 단계적으로 추가하면 탐지 정확도와 운영 복구성을 함께 확보할 수 있습니다.



