Amazon Web Services 한국 블로그

엣지 컴퓨팅을 위한 Lambda@Edge 활용 모범 사례

이 글은 전 세계에 배포 중인 애플리케이션을 처리하는 데 있어, Lambda@Edge의 사용을 최적화하는 데 도움이 될 수 있는 모범 사례 시리즈 중 첫 번째 게시물입니다. 여기에서 다룰 주제로는 사용 사례에 따라 가장 적합한 Lambda@Edge 설계를 생성하는 방법, Lambda@Edge를 CI/CD 파이프라인에 통합하는 방법, 그리고 솔루션이 유효하게 실행되어 비즈니스 요건을 해결하는지 확인할 수 있는 방법 등이 있습니다.

첫 번째 글에서는 AWS 고객이 Lambda@Edge 솔루션을 이미 구현한 경우에는 일반 사용 사례를 몇 가지 알려드리면서, Lambda@Edge 함수를 호출해야 할 시점을 선택하는 방법에 대해 설명하고 마지막으로 성능과 비용 효율성을 최적화할 수 있는 방법을 추천해 드리겠습니다.

Lambda@Edge는 전 세계 AWS 로케이션에서 서버를 프로비저닝 또는 관리할 필요 없이 Node.js 함수를 실행할 수 있는 서비스입니다. 이 서비스 기능을 이용하면 더욱 풍부한 개인 맞춤형 콘텐츠를 더욱 낮은 지연 시간으로 고객에게 전송할 수 있습니다. 콘텐츠를 사용자 지정하는 함수는 원하는 목적에 따라 다른 시점에 실행할 수 있습니다.

예를 들어 최종 사용자가 콘텐츠를 요청할 때, 혹은 CloudFront가 오리진 서버에게 요청할 때 CloudFront에서 함수를 실행하도록 지정할 수 있습니다. Node.js 코드를 Lambda@Edge에 업로드한 이후부터는 Lambda@Edge 서비스가 높은 가용성을 유지하면서 코드를 복제하거나, 라우팅하거나, 확장/축소하는 데 필요한 모든 것을 관리합니다. 따라서 사용자와 가까운 AWS 로케이션에서 함수를 실행할 수 있습니다. 비용은 사용한 컴퓨팅 시간에 대해서만 지불하면 됩니다.

Lambda@Edge 사용 사례

오늘날 비즈니스 사용 사례에 따라 고객이 이미 구현하고 있는 솔루션은 많습니다. 하지만 Lambda@Edge를 사용하는 이점은 다음과 같이 4가지 카테고리로 나눌 수 있습니다.

1. 콘텐츠 배포 성능: Lambda@Edge 사용에 따른 가장 큰 이점은 콘텐츠가 원본(Origin)에서 반환될 때 캐싱될 가능성을 높이거나, 혹은 이미 캐싱된 콘텐츠의 유용성을 높여 캐시 히트 비율을 개선하는 것입니다. 이렇게 캐시 히트 비율이 개선되면 캐시 미스로 인한 지연 시간이 사라져 애플리케이션 성능이 향상됩니다. 다음은 Lambda@Edge를 사용해 캐시 히트 비율을 개선할 수 있는 몇 가지 예입니다.

  • 응답에서 캐시 제어 헤더를 추가하거나 수정합니다.
  • 오리진에서 3xx 응답 시 리디렉션을 따르도록 구현하여 최종 사용자 응답 지연 시간을 줄입니다.
  • 쿼리 문자열 또는 사용자 에이전트 정규화를 사용하여 요청 변수를 줄입니다.
  • 요청 헤더, 쿠키 또는 쿼리 문자열의 속성에 따라 동적으로 다른 오리진으로 라우팅합니다.

2. 동적 콘텐츠 생성: Lambda@Edge에서는 요청 또는 응답 속성에 따라 사용자 지정 콘텐츠를 동적으로 생성할 수 있습니다. 예를 들면 다음과 같은 작업이 가능합니다.

  • 요청 속성에 따른 이미지 크기를 조정합니다.
  • Mustache 같이 로직이 없는 템플릿을 기준으로 페이지를 렌더링합니다.
  • A/B 테스트를 합니다.
  • 만료되었거나 시간이 지난 리소스에 대한 요청은 모두 302/301 리디렉션 응답을 생성합니다.

3. 보안: Lambda@Edge는 사용자 지정 인증 및 권한 부여를 처리할 때도 사용할 수 있습니다. 다음은 몇 가지 사용 사례입니다.

  • 액세스 제어가 적용되는 사용자 지정 오리진에 대한 요청에 서명을 추가합니다.
  • JWT/MD5/SHA 토큰 해시를 사용하는 등 최종 사용자 토큰 인증을 구성합니다.
  • 봇 탐지를 설정합니다.
  • HSTS 또는 CSP 보안 헤더를 추가합니다.

4. 원본(Origin) 독립성: 일부 시나리오에서는 오리진에 요청 및 응답 로직이 추가로 필요합니다. 이때 오리진 서버에서 실행되는 코드로 로직을 구현하는 대신 CloudFront에서 Lambda@Edge 함수를 실행하여 더욱 원활한 솔루션을 구현할 수 있습니다. 예를 들어 로직을 구현하면 다음과 같은 작업이 가능합니다.

  • .html 파일 확장자를 제거한 고유 URL(Pretty URL)을 생성합니다.
  • 오리진 요청에 대한 인증 및 권한 부여를 관리합니다.
  • 오리진 디렉터리 구조에 맞게 URL 또는 요청을 조작합니다.
  • 사용자 지정 로드 밸런싱 및 장애 조치 로직을 구현합니다.

작업에 따른 올바른 트리거 선택

다음과 같이 4가지 CloudFront 이벤트 발생 시 Lambda@Edge 함수를 호출하여 실행할 수 있습니다.

요청 응답
오리진 요청이 오리진으로 전송되기 전에 캐치 미스가 발생할 경우 실행 응답이 오리진에서 수신된 후 캐치 미스가 발생할 경우 실행
최종 사용자 CloudFront의 캐시가 확인되기 전에 모든 요청에서 실행 오리진 또는 캐시에서 응답이 수신된 후 모든 요청에서 실행

종합적인 지침은 개발자 안내서에 설명되어 있으므로 여기에서 목적에 따라 Lambda@Edg 트리거를 선택하는 방법에 대해 자세하게 알 수 있습니다. 그 밖에도 아래 질문은 사용할 Lambda@Edge 트리거를 결정하는 데 도움이 될 수 있습니다.

  • 캐시 미스 발생 시 함수를 실행하시겠습니까? 오리진 트리거를 사용하십시오.
  • 모든 요청에서 함수를 실행하시겠습니까? 최종 사용자 트리거를 사용하십시오.
  • 캐시 키(URL, 쿠키, 헤더, 쿼리 문자열)를 수정하시겠습니까? 최종 사용자 요청 트리거를 사용하십시오.
  • 결과를 캐싱하지 않고 응답을 수정하시겠습니까? 최종 사용자 응답 트리거를 사용하십시오.
  • 오리진을 동적으로 선택하시겠습니까? 오리진 요청 트리거를 사용하십시오.
  • 오리진 URL을 다시 작성하시겠습니까? 오리진 요청 트리거를 사용하십시오.
  • 캐싱되지 않는 응답을 생성하시겠습니까? 최종 사용자 요청 트리거를 사용하십시오.
  • 캐싱되기 전에 응답을 수정하시겠습니까? 오리진 응답 트리거를 사용하십시오.
  • 캐싱 가능한 응답을 생성하시겠습니까? 오리진 요청 트리거를 사용하십시오.

비용 효율성 최적화 방법

Lambda@Edge는 다음과 같이 두 가지 요인에 따라 요금이 청구됩니다.

  1. 요청 수. 현재(본 블로그 포스트 시점) 요금은 1M 요청당 0.60 USD입니다.
  2. 함수 실행 시간. 현재(본 블로그 포스트 시점) 요금은 사용된 GB-초당 0.00005001 USD입니다. 예를 들어 Lambda@Edge 함수가 실행될 때마다 128MB의 메모리를 할당한다면 실행 시간 요금은 사용된 128MB-초당 0.00000625125 USD가 됩니다. 단, Lambda@Edge 함수는 50ms 단위로 측정됩니다.

현재 요금과 몇 가지 Lambda@Edge 요금 예는 요금 안내서를 참조하십시오. Lambda@Edge 사용 요금을 줄이려면 아래 권장 사항에 따라 필요할 때만 함수를 호출하고 리소스 할당 방식을 최적화하십시오.

첫째, 가장 명확한 CloudFront 동작일 때만 Lambda@Edge를 호출하여 함수 호출을 최적화하십시오. 예를 들어 Lambda@Edge가 최종 사용자에게 권한을 부여하는 용도로 사용되는 이번 솔루션에서는 프라이빗 콘텐츠일 때만 Lambda@Edge 함수가 호출됩니다. 이러한 사용 사례에서는 CloudFront 캐시 동작에서 “private/*” 경로 패턴을 지정하여 오리진의 프라이빗 콘텐츠를 식별할 수 있습니다.

둘째, 적합한 트리거를 선택하십시오. 일부 Lambda@Edge 로직은 최종 사용자에 대한 HTTP 응답에서 HSTS 헤더를 추가할 때처럼 오리진 또는 최종 사용자 트리거를 사용해 구현할 수 있습니다. 이러한 시나리오에서는 최종 사용자 트리거보다는 오리진 트리거를 선택하여 Lambda@Edge 호출을 최적화하고 CloudFront 캐시를 이용하는 것이 좋습니다.

마지막으로, 함수 리소스 할당을 최적화하십시오. 최종 사용자 트리거의 리소스 할당은 128MB로 제한되어 있는 반면 오리진 트리거일 때는 최대 3008MB까지 할당할 수 있습니다. 메모리 크기를 늘리면 사용할 수 있는 CPU까지 동일하게 증가하기 때문에 함수의 실행 시간을 최적화하는 데 매우 중요할 수 있습니다. 따라서 로직에 필요한 리소스와 예산에 따라 최적의 메모리 크기 구성을 선택하여 이러한 요인들의 균형을 맞추어야 합니다.

성능 최적화 방법

Lambda@Edge 함수는 전 세계에서 CloudFront 네트워크를 통해 AWS 로케이션에서 실행됩니다. 최종 사용자 요청이 엣지 로케이션으로 전송되면 요청이 엣지에서 종료되고 Lambda@Edge가 최종 사용자와 가까운 AWS 로케이션에서 함수를 실행합니다.

Lambda@Edge를 사용할 때와 사용하지 않을 때 CloudFront 배포 사용을 비교하면 최종 사용자가 인지할 수 있는 지연 시간에 차이가 있습니다. 이러한 차이는 CloudFront 배포 구성, 트리거 유형, 엣지 로케이션, 함수 코드, 애플리케이션 로직 등 여러 가지 요인에 따라 달라집니다. 여기 몇 가지 예가 있습니다.

  • 오리진이 us-east-1 리전에 있고, Lambda 함수는 API Gateway 뒤에서 실행된다고 가정하겠습니다. 애플리케이션은 전 세계 최종 사용자에게 3xx 리디렉션을 동적으로 생성하며, 이때 첫 번째 바이트의 평균 지연 시간(FBL)은 260ms(us-east-1 리전에 대한 네트워크 FBL 160ms + 오리진 FBL 100ms)입니다. 이때 리디렉션 로직이 Lambda@Edge 함수로 이동하면 FBL이 110ms(CloudFront FBL 80ms + 최종 사용자 요청 트리거의 Lambda@Edge 호출 시간 30ms)까지 떨어집니다.
  • 캐시 히트 비율이 95%인 CloudFront를 통해 정적인 html 파일을 전송하며, Lambda@Edge는 HTTP 보안 헤더를 추가하는 데 사용한다고 가정하겠습니다. 이때 Lambda@Edge가 캐시 미스일 때만 실행되도록 구성하면 FBL이 0.5ms(5% x 오리진 응답 트리거의 호출 시간 10ms) 증가합니다.

Lambda@Edge 서비스 팀이 지속적으로 서비스 성능을 개선하고 있기 때문에 위에서 언급한 지연 시간 수치는 시간이 지나면서 좋아질 것입니다.

각 사용 사례에 따라 Lambda@Edge 구현을 최적화하여 최종 사용자 경험을 개선할 수 있습니다. 이를 위해 Lambda@Edge 함수의 실행 시간을 줄이는 동시에 서비스에서 설정한 함수 및 확장 제한을 벗어나지 않는 범위 내에서 함수를 실행할 수 있는 방법을 살펴보십시오.

첫째, 다음과 같은 방법으로 함수 실행 시간을 줄이십시오.

  • 성능에 따라 함수 코드를 최적화합니다. 예를 들어 실행 컨텍스트를 재사용하면 함수를 호출할 때마다 변수와 객체를 다시 초기화할 필요가 없습니다. 특히 Lambda@Edge 코드가 로컬에서 가져오거나, 저장하거나, 참조해야 할 외부 구성이 있다면 더욱 중요합니다. 그 밖에 정적 초기화 또는 생성자, 글로벌 정적 변수, 싱글톤을 사용하는 방법도 좋습니다.
  • 함수가 사용할 외부 네트워크 호출을 최적화합니다. 예를 들어 TCP Keep Alive를 활성화하여 이전 호출 시 설정했던 연결(HTTP, 데이터베이스 등)을 재사용하십시오. 또한 가능하다면 리소스에 대한 네트워크 호출을 Lambda@Edge 함수가 실행되는 리전과 동일한 리전에서 실행하여 네트워크 지연 시간을 줄이십시오. 이를 위한 한 가지 방법으로 DynamoDB 전역 테이블을 사용하는 것도 좋습니다. 그 밖에 함수에서 필요한 리소스만 외부에서 호출하는 것도 권장할 만합니다. 예를 들어 Aurora 또는 S3 Select의 쿼리 필터를 사용할 수 있습니다. 요청하는 외부 변수가 자주 바뀌지 않거나, 최종 사용자에 따라 다르지 않거나, 즉석에서 전파할 필요가 없다면 코드에서 상수를 사용하여 변수가 바뀔 때만 함수를 업데이트하는 것이 좋습니다.
  • 배포 패키지를 최적화합니다. 예를 들어 직접 작성할 수 있을 정도로 간단한 함수일 때는 외부 패키지에 대한 의존도를 회피할 수 있습니다. 그리고 외부 리소스를 사용해야 한다면 작은 용량의 패키지를 선택하십시오. 또한 Minify, Browserfy 등의 도구를 사용해 배포 패키지를 압축하는 것도 좋은 방법입니다.

둘째, Lambda@Edge의 함수확장 제한에 주의하고, 필요하다면 제한 상향 조정을 사전에 요청하십시오.

함수에 필요한 제한을 추정할 수 있는 방법은 다음 예를 고려하십시오. CloudFront를 사용해 초당 5,000개의 요청 수(RPS)와 90%의 캐시 히트 비율로 정적 이미지를 전송하는 동시에 오리진 응답 트리거와 함께 Lambda@Edge 함수를 사용해 HTTP 보안 헤더를 추가한다고 가정하겠습니다.

그러면 Lambda@Edge가 500RPS(10% x 5,000RPS)의 비율로 호출됩니다. 이는 간단한 함수이기 때문에 평균 실행 시간을 1ms로 추정할 수 있습니다. 안정적인 동시 실행 수를 계산하려면 10ms를 평균 실행 시간에 더한 다음 앞에서 계산한 RPS로 나누면 됩니다. 이번 예에서 계산한다면 동시 실행 수는 500RPS x (10ms + 1ms)로 55가 됩니다. 이는 계정 1개당 기본적인 동시 실행 제한 값인 1,000에 한참 미치지 못하는 수치입니다.

단, 트래픽 프로파일에서 Lambda@Edge 함수의 동시 실행 수가 몇 초 만에 수백 개까지 급증할 수 있는 경우에는 몇 가지 추가 요인을 고려해야 합니다. 동시 실행 수가 급증할 때는 Lambda@Edge가 사전에 설정된 수까지 동시에 실행되는 함수의 수를 즉시 늘리고, 늘어난 수가 트래픽 급증을 만족할 정도로 충분하지 않다면 Lambda@Edge가 계정 안전 한도에 도달할 때까지, 혹은 동시 실행 함수의 수가 늘어난 부하를 처리할 정도로 충분해질 때까지 동시 실행 함수의 수를 분당 500개씩 계속해서 늘립니다. 이렇게 자동으로 확장되기 때문에 트래픽이 급증하는 첫 1분 동안은 Lambda@Edge의 동시 실행이 사전 설정된 수로 제한됩니다. 또한 수평 확장 시에는 콜드 시작(cold starts)이 간단한 함수를 통해 최대 자릿수까지 함수 실행 시간을 늘립니다.

맺으면서

Amazon CloudFront와 Lambda@Edge를 DynamoDB 전역 테이블 같은 다른 AWS 서비스와 함께 사용하면 사용 사례에 따라 서버 없이 배포되는 고성능 웹 애플리케이션을 구축할 수 있습니다. 이번 블로그 포스트에서는 일반적인 Lambda@Edge 사용 사례 몇 가지를 알려드리고, 예산에 맞춰 Lambda@Edge의 구현 성능을 개선할 수 있는 모범 사례도 추천하였습니다. 시리즈에서 다음에 이어지는 글에서는 Lambda@Edge 함수를 간단하게 개발하여 테스트할 수 있는 방법과 Lambda@Edge를 CI/CD 파이프라인에 통합하는 방법에 대해서 설명하겠습니다.

이 글은 AWS Networking & Content Delivery 블로그에 실린 AWS Lambda@Edge Design Best Practices의 한국어 번역입니다.