주요 컨텐츠로 이동
공학

결제 버튼을 누른 후 수 밀리초 동안 일어나는 일

Model Serving 경로 최적화, Lakebase, 오토스케일링을 결합하여 수십 밀리초 내에 거래를 분석하는 Databricks App의 가이드라인으로, 모든 레이어의 코드와 벤치마크를 제공합니다.

작성자: Harsha Pasala , Subhadip Chanda

  • 저지연 추론을 위한 Model Serving 경로 최적화와 온라인 피처 및 프로필 조회를 위한 Lakebase Postgres를 활용하여, 실시간으로 신용카드 사기 거래를 탐지하는 샘플 Databricks App(FastAPI + React)입니다.
  • 빠른 추론만으로는 충분하지 않습니다. 이 앱은 경로 최적화된 Model Serving과 Lakebase를 결합하고, 부하가 걸린 상황에서도 지연 시간을 안정적으로 유지하는 커넥션 풀링, OAuth 토큰 로테이션, 오토스케일링 패턴을 적용했습니다.
  • 5,000개의 요청 전체에서 경로 최적화된 엔드포인트는 엔드투엔드 기준 p50에서 27ms, p95에서 37ms의 응답 시간을 기록했으며, Lakebase 피처 조회 중앙값 8.9ms 및 100%의 성공률을 달성하여 결제 지연 시간 허용 한도를 여유롭게 충족했습니다.

계산대 앞에 서서 카드를 단말기에 대는 순간을 생각해 보세요. 0.5초도 안 되는 짧은 시간 동안 작은 로딩 아이콘이 빙글빙글 돌더니 '승인 완료' 메시지가 뜹니다. 혹은 승인이 거절되기도 하죠.

그 짧은 시간 동안, 이 결제 요청이 실제 본인의 패턴과 일치하는지, 아니면 6개월 전 데이터 유출 사고로 카드 번호를 도용한 타인의 소행인지 판단하는 과정이 일어납니다. 시스템은 소비 패턴, 일일 한도, 해외 결제 허용 여부 등 사용자에 대한 정보를 파악해야 합니다. 그리고 이 모든 과정을 사용자가 전혀 눈치채지 못할 정도로 빠르게 처리해야 하죠.

이번 블로그 글에서는 Databricks에서 이러한 "판단 시스템"을 구축할 때 어떤 모습이 되는지 살펴보겠습니다. 두 가지 플랫폼 기능을 결합한 샘플 애플리케이션인 retail-app(FastAPI 백엔드, React 프론트엔드로 구성되어 Databricks App으로 배포됨)을 살펴보겠습니다.

  • 경로 최적화(route optimization)가 적용된 Model Serving: 배포된 모델로 향하는 더 빠른 네트워크 경로를 제공합니다.
  • Lakebase: 모델이 예측 시점에 필요로 하는 프로필 및 피처 데이터를 위한 관리형 Postgres로, 데이터베이스가 새로운 병목 구간이 되지 않고 수요에 따라 확장되도록 자동 확장(autoscaling) 기능을 제공합니다.

전체 리포지토리는 GitHub에 공개되어 있으므로, 포크(fork)하여 워크스페이스에 배포한 후 직접 '결제' 버튼을 눌러 테스트해 볼 수 있습니다.

흐름: 트랜잭션 발생 시 실제로 일어나는 일

코드를 살펴보기 전에 단일 결제가 처리되는 과정을 간단히 설명해 드리겠습니다. 두 가지 검사가 순서대로 실행됩니다. 먼저 모델이 결제 건의 점수를 매기고(scoring), 그 다음 앱이 사용자의 프로필 규칙을 확인합니다. 둘 중 하나라도 통과하지 못하면 트랜잭션이 거절됩니다.

image2.png

결과에 관계없이 모델의 지연 시간(latency) 수치를 확보하기 위해 모델이 먼저 실행됩니다. 그런 다음 프로필 조회(일일 지출 한도, 해외 결제 여부, 거주 국가) 결과가 몇 가지 조건문(if-statements)으로 전달됩니다. 응답에는 각 단계별 소요 시간이 포함되어 있어 몇 밀리초(ms)가 어디에서 소비되었는지 정확히 확인할 수 있습니다.

프로필 업데이트(일일 한도 변경, 해외 결제 여부 전환)는 별도의 작업입니다. 데이터베이스에 변경 사항을 저장하면 다음 결제부터 즉시 반영됩니다. 재배포나 캐시 무효화(cache invalidation) 과정이 필요 없습니다.

경로 최적화: 네트워크 경로가 중요한 이유

모델이 Databricks Model Serving 뒤에 배포되면 애플리케이션과 추론(inference) 컨테이너 사이에 네트워크 홉(hop)이 발생합니다. 배치(batch) 워크로드의 경우 요청당 몇 밀리초가 더 걸려도 무방하지만, 결제 환경에서는 이 짧은 시간이 모든 것을 좌우합니다.

경로 최적화(Route optimization)는 이 네트워크 경로를 단축합니다. 엔드포인트에서 경로 최적화를 활성화하면 Databricks Model Serving이 추론 요청을 위한 네트워크 경로를 개선하여 클라이언트와 모델 간에 더 빠르고 직접적인 통신이 가능해집니다. 이 최적화된 라우팅은 최적화되지 않은 엔드포인트에 비해 초당 쿼리 수(QPS)를 높이고, 애플리케이션에 더 안정적이고 낮은 지연 시간을 제공합니다.

엔드포인트를 생성할 때 이 기능을 활성화하며, 개인 액세스 토큰이 아닌 OAuth를 사용하여 데이터 평면(data-plane) 흐름을 통해 쿼리합니다. 동일한 컴퓨팅 자원으로 더 낮은 지연 시간과 더 높은 처리량(throughput)을 얻을 수 있으며, 이는 실시간 이상 거래 탐지(fraud-scoring) 사용 사례에 정확히 필요한 부분입니다.

샘플 앱에서 엔드포인트의 이름은 fraud-detection-lakebase입니다. 다음은 이를 호출하는 상수와 함수입니다.

주목할 만한 몇 가지 사항은 다음과 같습니다.

  • **serving_endpoints_data_plane.query**: 이는 경로 최적화가 사용하는 데이터 평면(data-plane) 쿼리 경로입니다. Databricks SDK가 백그라운드에서 OAuth 토큰 교환을 처리합니다.
  • **asyncio.to_thread**: SDK의 쿼리 메서드는 동기식(synchronous)입니다. 이를 to_thread으로 감싸면 모델이 실행되는 동안 FastAPI 이벤트 루프가 차단되지 않고 원활하게 유지됩니다.
  • **dataframe_records**: 페이로드는 딕셔너리 목록(행당 하나)입니다. 이상 거래 탐지의 경우 한 번에 하나의 트랜잭션을 전송합니다.

모델 자체는 fraud_probability, fraud_flag 및 (가장 중요한) 자체 내부 소요 시간인 lookup_ms(모델 컨테이너 내부의 피처 조회에 걸린 시간), inference_ms(CatBoost 예측), total_ms를 반환합니다. 백엔드는 프론트엔드가 지연 시간 폭포수(waterfall) 차트를 표시할 수 있도록 이 값들을 매핑하여 전달합니다.

따라서 UI에서 지연 시간 세부 정보(예: "모델 추론: 45ms" 하위에 "피처 조회: 8ms"가 중첩되어 표시됨)를 볼 때, 이는 각 레이어에서 측정되어 단일 응답으로 결합된 수치입니다.

설정에 대한 자세한 내용은 다음을 참고하세요. 경로 최적화 · 경로 최적화된 엔드포인트 쿼리하기.

Lakebase: 모델에 필요한 데이터를 위한 Postgres

이상 거래 탐지 모델은 트랜잭션 하나만 독립적으로 보지 않습니다. 신용카드의 앞 6자리(BIN)를 조회 키로 사용하여 고객의 과거 피처 데이터(평균 거래 금액, 해외 결제 비율, 차지백 비율, 지난 24시간 동안의 거래 빈도)를 조회합니다. 이러한 피처는 customer_features라는 이름의 Lakebase Postgres 테이블에 저장되어 있습니다.

이 테이블은 백엔드가 프로필 데이터(이름, 일일 한도, 해외 결제 여부)를 읽어오는 테이블과 동일합니다. 하나의 테이블을 두 군데에서 읽는 구조입니다. 즉, 모델 컨테이너는 추론을 위해 피처를 읽고, FastAPI 앱은 비즈니스 규칙을 위해 프로필 필드를 읽습니다.

읽기 측면에서 백엔드는 풀(pool)에서 연결을 가져와 user_id 기준의 매개변수화된 SELECT을 실행한 다음, finally 블록에서 연결을 반환합니다. 일반적인 psycopg2 방식이지만, 모든 트랜잭션에서 실행될 때는 연결을 가져오고 반환하는 규칙을 철저히 지키는 것이 중요합니다. 쿼리는 사용자 입력을 위해 매개변수화된 플레이스홀더를 사용하므로, 조회 키가 SQL에 직접 결합되지 않습니다.

쓰기 측면에서 사용자가 UI에서 일일 한도를 변경하거나 해외 결제 여부를 전환하면, 백엔드는 편집 가능한 열의 허용 목록(allowlist)을 기반으로 동적 UPDATE을 생성합니다. 해당 목록에 있는 필드만 기록되므로 클라이언트가 임의의 열 이름을 주입할 수 없습니다. 쓰기 작업은 autocommit = True과 함께 실행되므로 변경 사항이 즉시 반영됩니다. 따라서 배치 플러시(batch flush)나 캐시 무효화를 기다릴 필요 없이 바로 다음 트랜잭션에서 업데이트된 한도가 적용됩니다.

연결 풀링 및 OAuth 토큰 순환

이 앱의 모든 트랜잭션은 Lakebase에 최소 두 번 액세스합니다. 한 번은 피처 조회를 위해 모델 컨테이너 내부에서, 다른 한 번은 프로필 확인을 위해 백엔드에서 발생합니다. 매번 새로운 연결을 열면 요청할 때마다 TCP 핸드셰이크와 TLS 협상이 발생하여 호출당 20~50ms의 오버헤드가 쉽게 추가될 수 있습니다. 연결 풀(connection pool)은 몇 개의 연결을 열어둔 채로 대기 상태를 유지하므로, 대부분의 요청은 연결을 바로 가져와서 빠르게 처리할 수 있습니다.

실제 구현 모습은 다음과 같습니다. 모든 데이터베이스 작업은 동일한 가져오기/쿼리/반환 패턴을 따릅니다.

연결을 가져와 쿼리를 실행한 다음, 무언가 오류를 던지더라도 항상 반환되도록 finally 블록에서 연결을 반환합니다. 앱의 모든 읽기 및 쓰기 작업은 이와 동일한 패턴을 따릅니다.

풀 자체는 3~10개의 연결을 가진 psycopg2.pool.ThreadedConnectionPool입니다. 하지만 Lakebase는 OAuth를 통해 인증하기 때문에, 풀을 빌드하는 과정에서 흥미로운 부분이 시작됩니다. 앱은 서비스 주체(service principal) 자격 증명을 액세스 토큰으로 교환한 다음, 클라이언트 ID를 Postgres 사용자 이름으로, 토큰을 비밀번호로 사용합니다. 수명이 긴 데이터베이스 비밀번호는 필요하지 않습니다.

즉, 토큰이 만료되면 풀링된 연결에서 기존 비밀번호를 계속 사용할 수 없습니다. 다음 쿼리에서 실패하기 때문입니다. 따라서 _ensure_pool은 토큰이 변경되었는지 확인하고, 변경되었다면 새로운 풀을 빌드합니다:

대부분의 경우 빠른 경로(fast path)가 실행됩니다. 풀이 존재하고 토큰이 변경되지 않았으므로 즉시 반환됩니다. 토큰이 교체(rotate)될 때는 이중 확인 잠금(double-check locking)을 통해 두 스레드가 동시에 풀을 재빌드하지 않도록 방지하며, 기존 풀을 닫는 30초 타이머는 연결이 사라지기 전에 진행 중인 쿼리가 완료될 수 있는 시간을 제공합니다.

모델 측: 컨테이너 내부의 피처 조회(feature lookup)

(MLflow pyfunc로 배포된) 사기 탐지 모델 자체는 예측 시점에 *자체적인* Lakebase 조회를 수행합니다. 카드 BIN을 추출하고, customer_features을 쿼리하며, 피처 벡터를 구성한 다음 CatBoost 추론을 실행합니다. 각 단계의 소요 시간이 측정됩니다.

모델 컨테이너는 Lakebase에 대한 자체 ThreadedConnectionPool(fraud_model.pyLakebaseConnectionPool 클래스)을 유지하며, 백그라운드 토큰 새로 고침을 통해 장시간 실행되는 서빙 인스턴스 전체에서 풀이 유효하게 유지되도록 합니다. 이는 백엔드와 동일한 패턴(풀 + OAuth 교체)이지만, FastAPI 프로세스가 아닌 모델 컨테이너 내부에서 실행됩니다.

이러한 lookup_msinference_ms 값은 서빙 응답을 통해 백엔드를 거쳐 프론트엔드로 다시 전달됩니다. 이를 통해 엔드투엔드 가시성을 확보할 수 있습니다. 모델은 내부 소요 시간을 보고하고, 백엔드는 자체 실제 실행 시간(wall-clock) 측정값을 추가하며, 사용자는 이 모든 것을 볼 수 있습니다.

비즈니스 규칙: 프로필 확인

모델이 거래를 점수화(score)한 후, 백엔드는 Lakebase에서 고객의 프로필을 읽고 두 가지 간단한 규칙을 실행합니다. 첫째, 거래 금액을 사용자의 일일 지출 한도와 비교합니다. 청구 금액이 한도를 초과하면 거래가 거절되며, 사용자가 프로필 설정에서 한도를 늘릴 수 있다는 메시지가 표시됩니다. 둘째, 사용자가 해외 거래를 비활성화한 경우 백엔드는 거래 국가가 사용자의 거주 국가와 일치하는지 확인합니다. 일치하지 않으면 거절됩니다.

두 규칙 중 어느 하나라도 모델의 승인을 재정의(override)할 수 있습니다. 모델이 괜찮다고 판단한 거래라도 사용자가 일일 한도를 $500로 설정해 두었다면 거절될 수 있습니다. 이것은 의도된 설계입니다. 모델은 통계적 위험을 처리하고, 프로필은 사용자 선호도를 처리합니다. 둘 다 동일한 Lakebase 테이블에서 읽지만 목적이 다릅니다.

프로필 조회 및 규칙 확인에 소요된 시간은 business_logic_ms로 추적되어 모델 소요 시간과 함께 반환되므로, 비즈니스 로직이 각 거래에 얼마나 많은 오버헤드를 추가하는지 정확히 확인할 수 있습니다.

scale-to-zero를 지원하는 Lakebase 오토스케일링: 수요 처리

프로덕션 환경에서 Postgres 인스턴스는 모델 컨테이너의 피처 조회 백엔드의 프로필 읽기를 모두 처리해야 하며, 피크 시간대에는 초당 수많은 요청이 발생할 수 있습니다. Lakebase는 오토스케일링을 통해 설정된 최소/최대 범위 내에서 컴퓨팅을 조정하므로, 새벽 3시에 피크 용량에 대한 비용을 지불하지 않으면서도 정오에 쿼리가 누락되는 일을 방지할 수 있습니다.

아래 벤치마크 결과에서 p50부터 p75까지 일관되게 한 자릿수 밀리초의 조회 시간을 보이는 것은 안정적인 부하 상태에서 웜업된(warmed) Lakebase 인스턴스가 제공하는 성능을 반영합니다. p95에서의 급증(13.9ms)은 연결 풀 변동(churn)이나 일시적인 스케일업 이벤트에서 흔히 발생하며, 여전히 결제 흐름의 지연 시간 예산(latency budget) 내에 충분히 들어맞고, 오토스케일링이 사용자에게 노출되기 전에 흡수하는 전형적인 급증 현상입니다. scale-to-zero를 활성화하면 거래가 발생하지 않을 때 비용 지불도 중단됩니다.

종합: 엔드투엔드 지연 시간 분석

단일 거래에 대한 전체 지연 시간 현황은 다음과 같습니다.

측정 항목캡처 대상측정 위치
model_call_ms전체 서빙 호출에 대한 실제 실행 시간(Wall-clock time)백엔드 (router.py)
model_lookup_ms모델 컨테이너 내부의 피처 조회모델 (fraud_model.py)
model_interfere_msCatBoost 예측 시간모델 (fraud_model.py)
model_total_ms모델 컨테이너 내부의 총 소요 시간모델 (fraud_model.py)
business_logic_ms프로필 읽기 + 규칙 평가백엔드 (router.py)
backend_total_ms요청 시작부터 사기 탐지 모델 호출까지의 실제 실행 시간(Wall-clock time)백엔드 (router.py)

round_trip_ms model_total_ms 사이의 격차는 네트워크 오버헤드이며, 바로 이 부분에서 경로 최적화가 도움이 됩니다. backend_total_msmodel_call_ms 사이의 격차는 프레임워크 오버헤드(직렬화, 라우팅 등)입니다.

앱을 실행하고 거래를 제출하면 UI에 모델 추론(Model Inference), 피처 조회(Feature Lookup), 비즈니스 로직(Business Logic) 등의 주요 항목이 표시됩니다. 이를 통해 경로 최적화가 만드는 차이를 쉽게 확인하거나, 콜드 데이터베이스 연결에서 예상되는 수백 밀리초가 아닌 한 자릿수 밀리초의 지연 시간만 Lakebase 피처 조회에 추가된다는 점을 보여줄 수 있습니다.

결과: 실제로 얼마나 빠를까요?

경로가 최적화된 fraud-detection-lakebase 엔드포인트(CPU, "Small" 워크로드 크기, 단일 Azure 리전)로 5,000개의 순차적 요청(동시성 = 1, 호출 간 지연 시간 50ms)을 보내고, 모델 컨테이너 내부부터 호출자의 왕복(round-trip)까지 모든 레이어의 지연 시간을 수집했습니다. 목표는 경합 상황에서의 처리량을 부하 테스트하는 것이 아니라, 각 레이어(피처 조회, 추론, 네트워크 오버헤드)에서 밀리초 단위의 시간이 어떻게 소요되는지 요청별 지연 시간 구조를 분리하여 분석하는 것이었습니다. 이 수치는 모델 엔드포인트를 직접 호출하는 벤치마크 스크립트(scripts/benchmark.py)에서 가져온 것입니다. UI 지연 시간 분석은 전체 백엔드 경로를 통해 측정된 다른 단면(모델 추론, 피처 조회, 비즈니스 로직)을 보여줍니다.

지표측정 대상p50p75p90p95
피처 조회 (model_lookup_ms)모델 컨테이너 내부의 Lakebase 읽기8.9 ms9.8 ms11.7 ms13.9 ms
추론 (model_inference_ms)CatBoost 예측0.4 ms0.5 ms1.6 ms6.0 ms
총 모델 시간 (model_total_ms)조회 + 추론 + 컨테이너 오버헤드9.5 ms10.9 ms14.9 ms17.6 ms
엔드투엔드 왕복 시간 (round_trip_ms)호출자부터 응답까지의 전체 데이터 플레인 호출27.2 ms29.6 ms33.8 ms37.3 ms
네트워크 오버헤드 (round_trip_ms - model_total_ms)왕복 시간에서 모델 시간을 뺀 값17.4 ms18.5 ms19.8 ms21.1 ms

몇 가지 주목할 만한 점은 다음과 같습니다.

  • 엔드투엔드 왕복 시간은 중앙값에서 27ms, p95에서 37ms입니다. 호출자 → 경로 최적화된 데이터 플레인 → 모델 컨테이너 → Lakebase 조회 → CatBoost 추론 → 응답으로 이어지는 전체 과정입니다. 결제 흐름의 지연 시간 예산 내에 충분히 들어옵니다.
  • 피처 조회는 p50에서 한 자릿수 밀리초(8.9ms)에 불과합니다. Lakebase에 대한 모델의 커넥션 풀이 연결을 활성(warm) 상태로 유지하므로 대부분의 읽기 작업에서 TLS 핸드셰이크를 완전히 건너뜁니다. p95에서도 조회 시간은 14ms 미만으로 유지됩니다.
  • 추론 시간은 거의 무시할 수 있는 수준입니다. 12개 피처 벡터에 대한 CatBoost 예측은 중앙값 기준 0.4ms가 소요됩니다. 모델 소요 시간의 대부분은 예측 자체가 아니라 피처 조회에 의해 결정됩니다.
  • 네트워크 오버헤드는 약 17ms입니다. 모델 컨테이너가 보고하는 시간과 호출자가 확인하는 시간의 차이는 요청 라우팅, 직렬화, 데이터 플레인 홉과 같은 서빙 인프라에서 발생합니다. 경로 최적화 덕분에 이 값이 일관되게 유지되어 p50과 p95의 편차는 4ms에 불과합니다.

직접 시도해 보세요: 워크스페이스에 앱 배포하기

이 앱은 apx를 사용하여 Databricks App(FastAPI 백엔드, React 프론트엔드)으로 빌드되었습니다. 다음 명령어로 설치하세요:

또한 모델 서빙 엔드포인트와 Lakebase 인스턴스가 배포된 워크스페이스에 대해 Databricks CLI 인증을 구성해야 합니다.

로컬에서 실행하려면:

워크스페이스에 배포하려면:

주요 파일:

  • src/retail_app/backend/router.py: 트랜잭션 엔드포인트, 사기 탐지, 비즈니스 규칙
  • src/retail_app/backend/postgres.py: Lakebase 커넥션 풀, 프로필 CRUD
  • model_training/fraud_model.py: 피처 조회 및 CatBoost가 포함된 MLflow pyfunc
  • app.yml: 배포 구성(uvicorn 엔트리포인트, 환경 변수)

관련 문서

사기 점수 산정, 개인화, 동적 가격 책정 등 50ms 미만의 지연 시간 요구사항이 있는 실시간 사용 사례를 Databricks에서 기본적으로 구축할 수 있습니다. 모델 서빙 경로 최적화와 Lakebase는 추론 경로와 데이터 경로를 통제된 단일 플랫폼에 통합합니다. 이전에 레이크하우스 아키텍처가 결제 서비스 수준의 지연 시간을 충족할 수 없다고 생각하셨다면, 여기에 제시된 벤치마크 수치(한 자릿수 피처 조회 및 중앙값 27ms의 엔드투엔드 시간)를 다시 한번 확인해 보시기 바랍니다. 실시간 워크로드가 '구현하기 너무 어려움' 목록에 머물러 있었다면, 지금이 바로 다시 검토해 볼 때입니다. 자체 워크스페이스에 앱을 배포하여 지연 시간 폭포수(waterfall)를 직접 확인하고, Lakebase와 Databricks 모델 서빙을 경험해 보세요.

(이 글은 AI의 도움을 받아 번역되었습니다. 원문이 궁금하시다면 여기를 클릭해 주세요)

최신 게시물을 이메일로 받아보세요

블로그를 구독하고 최신 게시물을 이메일로 받아보세요.