Streaming Inference (Kafka × Feast × KServe)
GenD 의 Streaming Inference 는 Kafka 토픽에 흐르는 이벤트를 받아 Feast online store 에서 피처를 조회하고, KServe inference endpoint 를 호출해 추론 결과를 다시 Kafka sink 토픽 (또는 DLQ) 으로 흘려보내는 상시 가동 추론 워커 입니다.
본 가이드는 M1 마일스톤 (Epic #1084) 의 운영 가능 기능을 다룹니다. M2 (API/UI + KEDA 오토스케일 + lineage emit) 와 M3 (Flink + GPU + exactly-once) 는 후속 가이드에서 다룹니다.
핵심 결정
| 항목 | 선택 | 근거 |
|---|---|---|
| 스트리밍 엔진 | Bytewax 0.21 (Python native) | GenD 코드베이스가 Python 중심 (Dagster/FastAPI/Feast/SDK). JVM 운영 부담 회피. M3 에서 Flink 옵션 추가. |
| 모델 호출 패턴 | Pattern B — 외부 KServe HTTP 호출 | Gap 2 의 canary/blue-green/A-B traffic split 그대로 재사용. 워커는 endpoint 만 알면 됨. 모델 메모리/GPU 분리. |
| KServe 프로토콜 | HTTP v1 (TFServing-compatible) + v2 (OIP) 양쪽 지원 | M1 은 HTTP REST. gRPC 는 M3 에서 throughput 측정 후 도입. |
| Feast online store | Redis 7.2 (infra/redis-feast/) | StatefulSet 2Gi RWO + AOF + NetworkPolicy egress-deny. M2 에서 Sentinel 3-replica. |
| Kafka 클라이언트 | kafka-python (pure Python) | Alpine 호환, librdkafka 의존성 회피. M2 throughput 측정 후 confluent-kafka 전환 검토. |
| Delivery 보증 | At-least-once (idempotent producer + offset auto-commit) | M1. M3 에서 Flink 2PC sink 로 exactly-once. |
데이터 흐름
M1 scope
구현됨
apps/streaming/신규 Python 워크스페이스 (gend-pipelines 와 별개)config.py—GEND_STREAMING_*Pydantic settings (24 keys).clients/feast_online_client.py— Redis hmget + freshness TTL 검사.clients/kserve_inference_client.py— v1/v2 inference protocol + 재시도 + circuit breaker.clients/kafka_producer.py— kafka-python idempotent producer (sink + DLQ 공유).bytewax_jobs/anomaly_detection.py— 7-step dataflow (parse → Feast → predict → 분류 → sink/DLQ).lineage_emitter.py—/api/v1/lineage/streamingPOST (M1 silent — 라우터 M2).dead_letter.py— 표준 DLQ envelope (schema_version: 1).metrics.py— Prometheus 6종 (latency / errors / feast lookup / stale / dlq / processed).
infra/redis-feast/— Feast online Redis StatefulSet (2Gi RWO, USER 999, NetworkPolicy 3종).infra/streaming/bytewax-base/— Bytewax worker Deployment (replicas 1, imagegend/gend-streaming:0.1.0).apps/streaming/Dockerfile— Python 3.12-slim multi-stage, non-root UID 1000.- 회귀 가드:
apps/streaming/tests/test_anomaly_detection_local.py(8 시나리오, Bytewax in-process).apps/streaming/tests/test_kserve_inference_client.py(8 시나리오, respx mock).apps/streaming/tests/test_feast_online_client.py(5 시나리오, fakeredis).scripts/test_streaming_manifest_renders.py(17 단언, kustomize render + 보안 posture).
미구현 — M2 (3-4주)
- DB 모델
streaming_job/streaming_job_run+ Alembic migration. routers/streaming_jobs.py8개 endpoint (CRUD + start/stop + metrics + dlq + replay).- KEDA
ScaledObject(Kafka consumer lag trigger, replicas 1-N). lineage_emitter라우터 활성화 + UI lineage 그래프 노드 (kafka_topic / kserve_endpoint / streaming_job).- UI
StreamingJobsPage+StreamingJobDetailPage+StreamingJobCreateDialog+streamingStore. - Grafana dashboard
infra/grafana/dashboards/streaming.json. - Redis Sentinel 3-replica 전환 (
infra/redis-feast/overlay).
미구현 — M3 (5-6주)
- Flink 1.20 (옵션, Bytewax 와 공존) —
flink-kubernetes-operator+FlinkDeploymentCRD. - KServe gRPC (latency 단축 — M1 HTTP 대비 P99 -20% 예상).
- GPU 노드 KServe 할당.
- Exactly-once delivery (Flink 2PC sink + Kafka EOS v2).
- Edge inference (단말 KServe — 별도 Epic).
환경 의존성
| 컴포넌트 | 상태 (M1) | 비고 |
|---|---|---|
| Kafka 3-broker KRaft | 운영 (infra/kafka/) | source / sink / DLQ 토픽 사전 생성 필요 (feedback_otel_kafka_prereq) |
| Kafka Connect + Debezium | 운영 (infra/kafka/connect.yaml) | source-db.public.operating_event 토픽 자동 생성됨 |
| Redis (Feast online) | 본 PR 추가 (infra/redis-feast/) | M1 single replica. 실제 배포는 별도 ops PR. |
| Feast registry (SQL on PG) | 정의됨 (pipelines/feast/feature_store.yaml) | M1 은 수동 feast materialize 1회 (Dagster sensor 는 Gap 3) |
| KServe RawDeployment | 운영 (#1082 M1) | 워커는 GEND_STREAMING_KSERVE_ENDPOINT 로 base URL 만 주입. 실제 모델 (anomaly-lng) 등록은 별도 절차. |
gend-api /api/v1/lineage/streaming | 미존재 (M2 추가) | M1 워커는 silent fallback — 404 발생해도 로그만 남기고 계속 처리. |
Fallback 동작
- Redis 미배포 → 워커가 Feast lookup 시
feast_unreachable에러로 모든 이벤트를 DLQ 로 보냄. KServe 는 호출되지 않음. 워커 자체는 정상 동작 유지 (back-pressure 회피). - KServe endpoint 미배포 →
kserve_timeout또는 connection refused → 5xx 처리 → DLQ. circuit breaker 가 5회 연속 실패 후 30초간 fast-fail. 쿨다운 후 자동 복구. - lineage API 미배포 →
LineageEmitter.emit_static()가False반환 + debug 로그. 워커 부트는 막지 않음.
운영
로컬 (Kind)
# 1. 이미지 빌드 + Kind 로드
docker build -t gend/gend-streaming:0.1.0 -f apps/streaming/Dockerfile apps/streaming
kind load docker-image gend/gend-streaming:0.1.0 --name gend-local
# 2. Redis (Feast online) 배포
kubectl apply -k infra/redis-feast/
# 3. Bytewax worker 배포
kubectl apply -k infra/streaming/bytewax-base/
# 4. 메트릭 확인
kubectl -n gend port-forward svc/gend-streaming-anomaly 9090:9090
curl localhost:9090/metrics | grep gend_streaming
AKS prod (별도 ops PR)
# ACR push (operations 팀 절차)
docker tag gend/gend-streaming:0.1.0 genosprodacr.azurecr.io/gend-streaming:0.1.0
docker push genosprodacr.azurecr.io/gend-streaming:0.1.0
# ACR-prefixed manifest 패치 후 apply
kubectl --context aks-genos-prod apply -k infra/redis-feast/
kubectl --context aks-genos-prod apply -k infra/streaming/bytewax-base/
kubectl --context aks-genos-prod set image \
deployment/gend-streaming-anomaly \
bytewax=genosprodacr.azurecr.io/gend-streaming:0.1.0 \
-n gend
메트릭
| 메트릭 | 라벨 | 설명 |
|---|---|---|
gend_streaming_inference_latency_seconds | job, model, status | KServe 호출 latency (Histogram, 5ms-5s) |
gend_streaming_inference_errors_total | job, model, error_type | timeout / 4xx / 5xx / circuit_open / malformed_response |
gend_streaming_feast_lookup_latency_seconds | job, feature_view | Redis hmget latency |
gend_streaming_feast_stale_total | job, feature_view | TTL 초과 카운트 |
gend_streaming_dlq_total | job, reason | DLQ 진입 사유별 카운트 |
gend_streaming_processed_total | job, sink | 성공적으로 sink 된 이벤트 카운트 |
P99 latency budget: < 200ms (Feast 2-5ms + KServe 30-80ms + 워커 5ms).
보안 가드
- Non-root: Redis StatefulSet UID 999, Bytewax Deployment UID 1000 (numeric).
- readOnlyRootFilesystem: 양쪽 모두.
/tmp만 emptyDir 256Mi. - NetworkPolicy: Redis 는 ingress allow-list (gend-api / dagster / gend-streaming / prometheus) + egress DNS only.
- HMAC / JWT: M1 미적용 (M2 ContextGateway 통합 시 적용). 현재는 namespace 내부 ClusterIP 통신만.
- DLQ 가시성: 모든 실패 envelope 에
feast_lookup_result포함 — PII 가 들어갈 수 있으므로 M2 에서 PII 마스킹 통과 후 DLQ 기록.
한계 (M1 명시)
- 단일 모델 하드코딩 —
GEND_STREAMING_MODEL_NAMEenv 만 받음. 다중 모델 동시 운영은 별도 Deployment 복제. - 단일 source topic —
GEND_STREAMING_SOURCE_TOPICenv 1개. fan-in / multi-topic 은 M2. - 자동 오토스케일 없음 — replicas: 1 고정. lag 폭증 시 수동
kubectl scale. M2 KEDA. - lineage emit silent —
/api/v1/lineage/streaming라우터 부재로 emit 비활성 (GEND_STREAMING_LINEAGE_EMIT_ENABLED=false). - In-process bytewax Kafka source 미주입 —
bytewax-kafkaextras 또는bytewax.connectors.kafka의존성을 운영자가 별도로 추가해build_flow(source=...)로 주입해야 본격 가동. M1 배포 manifest 는 빈 source 로 부팅만 보장.
관련 문서
- Model Serving Overview (#1082 M1) — KServe RawDeployment + canary.
- Ontology Layer (#993) —
Equipment / Process / OperatingEvent스키마. - Architecture §5.3 Medallion — Bronze (CDC) / Silver (PII-masked) / Gold (parsed) 매핑.
- Epic #1084 — Streaming Inference Gap 4 본문.