들어가며: 실시간 가격이 중요한 이유

대규모 이커머스에서 '실시간 가격'은 단순한 기능 이상의 의미를 갖습니다. 블랙프라이데이 같은 대규모 트래픽 이벤트에서 가격이 1시간 동안 동기화되지 않으면, 고객은 장바구니에서 다른 가격을 보고 '카트 쇼크(Cart Shock)'를 경험하게 됩니다. 이는 단순한 버그가 아니라, 아키텍처의 근본적인 지연(Latency) 문제입니다.

삼성닷컴(Samsung.com)은 스마트폰, TV, 가전 등 수많은 변형(Variant)과 지역별 가격을 관리해야 하는 복잡한 환경에서 이 문제를 해결해야 했습니다. 이번 포스트에서는 그들이 어떻게 레거시 데이터 집계(DA: Data Aggregation) 아키텍처를 해체하고, AWS Lambda Response Streaming과 Amazon CloudFront를 활용한 상태 비저장(Stateless) 스트리밍 아키텍처로 전환했는지 상세히 살펴보겠습니다.

현업 적용 포인트: 단순히 '캐시를 지우는' 수준이 아니라, 중간 저장 계층 자체를 없애는 근본적인 해결책을 제시합니다. 국내 SI 환경에서 흔히 발생하는 '배치 동기화 지연' 문제를 해결하는 좋은 참고 사례가 될 거예요.

AWS Lambda serverless function diagram showing response streaming for real-time pricing

문제: 데이터 집계(DA)의 함정

레거시 시스템은 백엔드 포 프론트엔드(BFF) 서비스 형태로 DA 레이어를 두고, 시간당 한 번씩 크론(Cron) 작업을 실행해 전체 상품 카탈로그의 가격을 미리 계산(precompute)하여 캐시에 저장했습니다.

두 가지 심각한 실패

  1. 조합의 폭발(Permutation Explosion)

    • 30개 상품 × (변형 × 할인 × 추가옵션) = 페이지당 수천 개의 레코드
    • 새로운 상품 변형이 추가될 때마다 캐시가 기하급수적으로 증가
    • 미리 계산된 대부분의 조합은 실제로 요청되지 않음 (낭비)
  2. 동기화 지연(Synchronization Lag)

    • 크론 작업이 1시간에 한 번만 실행되므로, 플래시 세일 같은 가격 변동이 즉시 반영되지 않음
    • 고객은 다음 동기화 시간까지 이전 가격을 보게 됨
    • 결제 단계에서 가격이 달라져 신뢰도 하락

레거시 아키텍처 다이어그램

[고객 브라우저] → CloudFront CDN → [DA 레이어 (캐시)] → [가격 엔진]
                        ↑ 1시간 지연    ↑ 크론 워커 (매시간)

이 구조는 '권위자(Authority, 가격 엔진)''고객' 사이에 명확한 '비동기화 계층(Desynchronization Layer)' 을 만들어 냈습니다.

CloudFront edge caching architecture with Lambda streaming for ecommerce IT Technology Image

해결책: 상태 비저장 스트리밍 아키텍처

삼성팀은 AWS TAM(Technical Account Manager)과 협력하여 중간 저장 계층을 완전히 제거하는 새로운 아키텍처를 설계했습니다. 바로 Bulk Arbitration Engine이라는 상태 비저장(Stateless) 오케스트레이션 레이어입니다.

핵심: Lambda Response Streaming

Lambda Response Streaming은 기존의 버퍼링된 응답 방식과 달리, 데이터가 준비되는 대로 즉시 클라이언트에 스트리밍합니다. 이를 통해 30개 SKU를 병렬로 조회하고, 결과가 도착하는 즉시 화면에 렌더링할 수 있습니다.

구현 3단계

Step 1: 스트리밍 핸들러 구현

// Node.js Lambda 핸들러 - awslambda.streamifyResponse() 사용
const { pipeline } = require('stream/promises');
const { Transform } = require('stream');
const { createGzip } = require('zlib');

exports.handler = awslambda.streamifyResponse(async (event, responseStream, context) => {
    // 1. 요청에서 SKU 목록 추출
    const skus = parseCompressedQueryString(event.queryStringParameters.g);
    
    // 2. NDJSON 변환 스트림 생성 (Newline-Delimited JSON)
    const ndjsonTransform = new Transform({
        objectMode: true,
        transform(chunk, encoding, callback) {
            this.push(JSON.stringify(chunk) + '\n');
            callback();
        }
    });

    // 3. GZIP 압축 스트림 (Z_BEST_SPEED = Level 1)
    const gzipStream = createGzip({ level: 1 });

    // 4. 모든 SKU를 병렬로 조회하고, 결과를 스트림으로 전달
    const pricingPromises = skus.map(sku => fetchPricingForSKU(sku));
    const results = await Promise.allSettled(pricingPromises);
    
    // 5. 파이프라인 구성: 결과 → NDJSON 변환 → GZIP 압축 → 응답 스트림
    await pipeline(
        Readable.from(results.filter(r => r.status === 'fulfilled').map(r => r.value)),
        ndjsonTransform,
        gzipStream,
        responseStream
    );
});

// 병렬 요청을 위한 헬퍼 함수
async function fetchPricingForSKU(sku) {
    const response = await fetch(`https://pricing-engine.internal/${sku}`, {
        // 커넥션 풀링 활용
        agent: new https.Agent({ keepAlive: true, maxSockets: 30 })
    });
    return response.json();
}

Step 2: GET 요청으로 압축

CloudFront에서 캐싱하려면 GET 요청이 필수입니다. 삼성팀은 복잡한 요청 데이터를 압축된 쿼리 스트링 형식으로 변환했습니다.

// 압축된 쿼리 스트링 예시
// 원본 JSON (3~4KB) → 압축 (약 800바이트)
// 형식: g=group1(p=SKU-A:1:p=SKU-B:2)...

Step 3: CloudFront 캐시 정책 설정

  • 기본 TTL: 5분 (신선도와 캐시 효율성 균형)
  • 쿼리 스트링을 캐시 키에 포함 (서로 다른 SKU 조합 = 별도 캐시 항목)
  • 헤더 허용 목록을 통해 맞춤형 가격 변형 지원
  • GZIP 압축 자동 활성화

Network latency optimization phases from baseline to production with HTTP/2 and VPC peering System Abstract Visual

성능 최적화 결과: 4단계 진화

단계설명P90 지연시간개선 배수
Phase 1 (Baseline)글로벌 VPN, 버퍼링 응답, 압축 없음4,500ms1x
Phase 2 (VPC)VPC 피어링 + Provisioned Concurrency1,000ms4.5x
Phase 3 (HTTP/2)HTTP/2 멀티플렉싱 + GZIP 압축218ms20x
Phase 4 (Production)CloudFront 엣지 캐싱 (95% Cache Hit)50ms90x

핵심 인사이트

  • Phase 2 → Phase 3의 개선 폭이 가장 큽니다. HTTP/2 멀티플렉싱으로 30개 병렬 요청의 TCP 연결을 재사용하고, GZIP 압축으로 응답 크기를 76% 줄인 덕분입니다.
  • Phase 4에서 95% 캐시 히트는 단 5%의 요청만 Lambda를 호출한다는 뜻입니다. 블랙프라이데이 같은 피크 시간에도 비용과 지연시간을 최소화할 수 있었습니다.

이 기술의 한계 및 주의사항

  1. Lambda 실행 시간 제한: 30개 SKU 제한은 Lambda 실행 시간을 5초 이내로 유지하기 위한 설계입니다. 50개 이상의 SKU가 필요하면 클라이언트에서 여러 배치로 분할해야 합니다.
  2. 부분 실패 처리: 스트리밍 아키텍처는 일부 SKU 조회가 실패해도 나머지 결과는 계속 전달되므로, 클라이언트에서 부분 실패를 적절히 UI로 표현해야 합니다.
  3. GET 요청 길이 제한: 압축된 쿼리 스트링이라도 URI 길이 제한(보통 8KB)을 초과하지 않도록 주의해야 합니다.
  4. 보안: SKU 정보는 공개 데이터지만, 가격 엔진의 비즈니스 로직은 VPC 내부에서 안전하게 보호해야 합니다. TLS 1.3, VPC 엔드포인트, CloudTrail 감사 로그를 필수로 적용하세요.

다음 단계 학습 방향

  • Lambda Response Streaming 공식 문서를 읽고 Node.js 외에 Python, Java에서의 구현 방법을 익혀보세요.
  • CloudFront Cache Policy의 세부 설정(Query String 포함, 헤더 허용 목록)을 실습해보세요.
  • NDJSON(Newline-Delimited JSON) 포맷을 다른 API에서도 적용해볼 수 있습니다. 예를 들어, 실시간 검색 결과나 소셜 미디어 피드에 활용할 수 있습니다.

함께 보면 좋은 글

근거자료: 이 글은 AWS Architecture Blog의 원문을 기반으로 분석 및 재구성했습니다.

본 콘텐츠는 신뢰할 수 있는 출처를 바탕으로 AI 도구를 활용하여 초안이 작성되었으며, 편집자의 검토를 거쳐 발행되었습니다. 전문가의 조언을 대체하지 않습니다.