AWS 기술 블로그

AWS 분석 서비스를 활용하여 SaaS 미터링 시스템 구축하기

SaaS(Software-as-a-Service)는 클라우드 환경에서 소프트웨어를 제공하는 방식으로, 사용자는 소프트웨어를 설치하거나 유지 관리할 필요 없이 인터넷을 통해 접근할 수 있습니다. 이러한 SaaS 비즈니스 모델은 특히 사용량 기반 과금 체계를 통해 효율적인 비용 관리와 투명한 요금 청구를 가능하게 합니다. 그러나 SaaS 제공자는 여러 고객을 대상으로 서비스를 제공하기 때문에, 각 고객의 사용량을 정확히 측정하고 이에 따라 요금을 산정하는 것이 매우 중요합니다.

SaaS 미터링 시스템은 이러한 요구를 충족시키기 위해 설계된 시스템으로, API 호출 수, 데이터 전송량, 사용자 활동 등 다양한 지표를 기반으로 사용량을 측정합니다. 정확한 사용량 측정은 고객에게 공정한 요금을 청구할 수 있게 하며, 이는 고객 만족도와 신뢰도를 높이는 데 중요한 역할을 합니다.

이 블로그에서는 API 호출 수 기반의 SaaS 미터링 시스템을 구축하는 방법을 단계별로 설명합니다. API 호출 수를 기준으로 한 사용량 측정은 모든 요청이 API를 통과하는 특성을 활용해 서비스의 실제 사용량을 정확히 포착할 수 있습니다. 또한 내부 시스템의 복잡성과 무관하게 일관된 방식으로 측정할 수 있으며, 서비스가 확장되거나 변경되어도 측정 방식을 수정할 필요가 없어 유지보수가 용이합니다. 이러한 장점들은 SaaS 제공자가 효율적이고 정확한 미터링 시스템을 구축하여 비즈니스 성장과 고객 만족도 향상을 달성하는 데 기여합니다.

1. 솔루션 개요

이번 블로그 포스팅에서 소개할 솔루션은 Amazon API Gateway, Amazon Data Firehose, Amazon S3, 그리고 Amazon Athena를 사용하여 효율적이고 확장 가능한 SaaS 미터링 시스템을 구축합니다.

이 솔루션의 아키텍처는 다음과 같은 방식으로 동작합니다:

  • 사용자 인증: Amazon Cognito를 통해 사용자 인증 및 권한 관리가 이루어집니다. Amazon Cognito는 다양한 인증 방식을 지원하고 안전한 사용자 로그인을 보장하며, 사용자 역할에 따라 애플리케이션의 기능 또는 데이터에 대한 접근을 세밀하게 제어할 수 있어 보안을 강화합니다.
  • API 관리 및 로깅: API Gateway는 모든 API 호출을 중앙에서 관리하고 로깅합니다. 이는 미터링 시스템의 핵심 기반으로, 기존 시스템과 미터링 로직을 분리하여 유지보수성을 높이고 일관된 측정을 가능하게 합니다.
  • 비즈니스 로직 처리: 이 솔루션에서는 사용량을 측정할 API를 AWS Lambda 함수에서 구현한 예제를 이용해서 SaaS 미터링 시스템의 동작 원리를 설명하고자 합니다.
  • 로그 데이터 수집 및 저장: API Gateway의 액세스 로그는 Data Firehose를 통해 실시간으로 S3에 저장됩니다. 이 방식은 대규모 로그 데이터를 효율적으로 처리하고 저장하며, 비용 효율적인 장기 보관과 대규모 데이터 분석을 가능케 합니다.
  • 데이터 분석: Athena를 사용하여 S3에 저장된 로그 데이터를 분석합니다. Athena의 서버리스 쿼리 서비스를 통해 별도의 인프라 관리 없이 SQL로 데이터를 쉽게 분석할 수 있습니다.
  • 데이터 최적화: 주기적으로 실행되는 Lambda 함수가 S3에 저장된 작은 파일들을 병합하여 Parquet 형식의 대용량 파일로 변환합니다. 이는 Athena 쿼리 성능을 크게 향상시킵니다.

이 아키텍처에서 API Gateway는 모든 API 호출을 중앙에서 관리하고 로깅할 수 있어 미터링 시스템의 기반으로 선택되었습니다. 이를 통해 기존 시스템과 미터링 로직을 분리하여 유지보수성을 높이고, 서비스 확장 시에도 일관된 측정이 가능합니다. 또한 API Gateway의 로그를 CloudWatch Logs 대신 Data Firehose로 직접 전송하는 방식을 채택하여, 대규모 로그 데이터를 효율적으로 처리하고 저장하였습니다. Data Firehose를 통해 로그 데이터는 실시간으로 S3에 저장되며, 이는 비용 효율적인 장기 보관과 대규모 데이터 분석을 가능케 합니다.

이 솔루션은 완전히 자동화되어 있어, SaaS 제공자는 실시간 사용량 모니터링과 정확한 요금 청구를 위한 데이터를 지속적으로 확보할 수 있습니다. 이러한 접근 방식은 확장성, 비용 효율성, 그리고 데이터 기반의 의사결정을 지원하는 강력한 SaaS 미터링 솔루션을 제공합니다.

2. 단계 요약

  • 단계 1: 배포 준비
  • 단계 2: CDK 스택 배포
  • 단계 3: 테스트

사전 준비 사항

솔루션 배포를 위해 AWS Cloud Development Kit (AWS CDK)를 사용합니다. 실습을 진행하기 위해 아래와 같이 준비합니다.

  • AWS 계정
  • AWS CLI: AWS CLI 사용을 위한 credential 설정
    aws configure --profile [your-profile]
    AWS Access Key ID [None]: xxxxxx
    AWS Secret Access Key [None]:yyyyyyyyyy
    Default region name [None]: us-east-1
    Default output format [None]: json
    • region name은 이 솔루션을 배포할 AWS Region으로 설정합니다.
  • AWS CDK
  • jq: JSON 처리 Linux Uitility

단계 1: 배포 준비

  1. Github 리포지토리에서 솔루션 코드를 다운로드 받습니다.
    git clone --depth=1 https://github.com/aws-samples/saas-metering-system-on-aws.git
    cd saas-metering-system-on-aws
  2. CDK로 솔루션을 배포할 수 있도록 Python 가상 환경을 구성하고, 필요한 Python 패키지를 설치합니다.
    python3 -m venv .venv
    source .venv/bin/activate
    pip install -r requirements.txt
  3. CDK 스택 배포에 필요한 환경 변수를 설정합니다.
    export CDK_DEFAULT_ACCOUNT=$(aws sts get-caller-identity --query Account --output text)
    export CDK_DEFAULT_REGION=$(aws configure get region)
  4. CDK 스택을 배포하는데, 필요한 설정 값을 저장하기 위해서 아래와 같이 cdk.context.json 파일이 생성된 것을 확인합니다.
    {
      "firehose": {
        "stream_name": "random-gen",
        "buffer_size_in_mbs": 128,
        "buffer_interval_in_seconds": 300,
        "s3_bucket": "apigw-access-log-to-firehose-{region}-{account-id}",
        "s3_output_folder": "json-data",
        "prefix": "json-data/year=!{timestamp:yyyy}/month=!{timestamp:MM}/day=!{timestamp:dd}/hour=!{timestamp:HH}/",
        "error_output_prefix": "error/year=!{timestamp:yyyy}/month=!{timestamp:MM}/day=!{timestamp:dd}/hour=!{timestamp:HH}/!{firehose:error-output-type}"
      },
      "athena_work_group_name": "SaaSMeteringDemo",
      "merge_small_files_lambda_env": {
        "OLD_DATABASE": "mydatabase",
        "OLD_TABLE_NAME": "restapi_access_log_json",
        "NEW_DATABASE": "mydatabase",
        "NEW_TABLE_NAME": "restapi_access_log_parquet",
        "NEW_TABLE_S3_FOLDER_NAME": "parquet-data",
        "COLUMN_NAMES": "requestId,ip,user,requestTime,httpMethod,resourcePath,status,protocol,responseLength"
      }
    }
  5. Data Firehose 설정
    • stream_name: 데이터가 전송될 Data Firehose 스트림의 이름입니다.
    • buffer_size_in_mbsbuffer_interval_in_seconds: 데이터를 S3에 전송하기 전에 버퍼링할 크기와 시간 간격을 설정합니다.
    • s3_bucket: (선택 사항) 액세스 로그를 저장할 S3 버킷의 이름을 지정합니다. s3_bucket 을 설정하지 않는 경우, apigw-access-log-to-firehose-{region}-{account-id} 라는 S3 버킷을 자동으로 생성합니다. (예: apigw-access-log-to-firehose-us-east-1-123456789012)
    • prefixerror_output_prefix: S3에 저장될 데이터의 경로 및 오류 로그 경로를 지정합니다.
  6. Athena 설정
    • athena_work_group_name: Athena에서 사용할 작업 그룹의 이름을 지정합니다. 이는 쿼리 실행 및 리소스 할당을 관리하는 데 사용됩니다.
  7. Lambda 환경 변수 설정
    • merge_small_files_lambda_env: AWS Lambda 함수에서 사용할 환경 변수를 정의합니다. 이는 작은 크기의 JSON 파일들을 대규모 Parquet 파일로 병합하는 작업에 사용됩니다.
    • OLD_DATABASENEW_DATABASE: 데이터베이스 이름을 지정하며, 이는 Athena에서 쿼리할 테이블을 정의하는 데 사용됩니다.
    • COLUMN_NAMES: 테이블의 컬럼 이름을 지정하여 데이터 스키마를 정의합니다.

단계 2: CDK 스택 배포

cdk deploy 커맨드 라인을 이용하여 CDK 스택을 배포합니다.

cdk deploy --require-approval never --all

단계 3: 테스트

  1. Cognito 사용자 풀 생성 확인
    2. CDK 스택 배포 절차에서 Amazon Cognito 사용자 풀(user pools)이 생성되었습니다. 사용자 풀은 애플리케이션의 사용자 인증 및 관리를 위한 디렉터리 역할을 하며, 사용자가 애플리케이션에 로그인하고 인증을 받을 수 있도록 지원합니다. 이 사용자 풀에 사용자를 추가할 예정입니다. 사용자 추가를 위해 사용자 풀 id를 USER_POOL_CLIENT_ID 환경 변수로 설정합니다.

    USER_POOL_CLIENT_ID=$(aws cloudformation describe-stacks --stack-name RandomGenApiGw | \
    jq -r '.Stacks[0].Outputs[] | select(.OutputKey == "UserPoolClientId") | .OutputValue')
  2. Cognito 사용자 추가
    aws cli를 이용하여 Cognito 사용자를 추가합니다. username과 password는 원하는 값으로 입력합니다.

    aws cognito-idp sign-up \
      --client-id ${USER_POOL_CLIENT_ID} \
      --username "user-email-id@domain.com" \
      --password "user-password"
  3. 사용자 생성 확인
    로그인을 수행하여 사용자가 정상적으로 추가되었는지 확인합니다. 먼저 다음의 명령을 실행하며 Amazon Cognito에 생성된 User Pool ID를 확인합니다.

    USER_POOL_ID=$(aws cloudformation describe-stacks --stack-name RandomGenApiGw | jq -r '.Stacks[0].Outputs | map(select(.OutputKey == "UserPoolId")) | .[0].OutputValue')

    username 값은 이전 단계에서 등록한 사용자 이름으로 입력하여 사용자가 정상적으로 추가되었는지 확인합니다.

    aws cognito-idp admin-confirm-sign-up \
      --user-pool-id ${USER_POOL_ID} \
      --username "user-email-id@domain.com"

    Amazon Cognito 콘솔 화면에서 UserPoolForApiGateway 이름으로 생성된 사용자 풀의 상세 정보를 확인하면, 이전 단계에서 생성한 사용자 정보가 확인됩니다.

  4. 사용자 로그인
    등록한 사용자 정보를 이용하여 로그인을 통해 JWT 토큰을 발급받습니다. 이 토큰은 이후 API 요청 시 인증 목적으로 사용됩니다. 발급받은 JWT 토큰에는 ID 토큰, Refresh 토큰, Access 토큰을 포함하고 있습니다. username와 password 값은 이전 단계에서 사용자 등록 시 설정한 이름, 패스워드 값으로 입력합니다.

    aws cognito-idp initiate-auth \
      --auth-flow USER_PASSWORD_AUTH \
      --auth-parameters USERNAME="user-email-id@domain.com",PASSWORD="user-password" \
      --client-id ${USER_POOL_CLIENT_ID}

    API 요청 시 ID 토큰을 전달할 것이기 때문에 ID 토큰을 환경 변수로 저장합니다.

    MY_ID_TOKEN=$(aws cognito-idp initiate-auth --auth-flow USER_PASSWORD_AUTH \
    --auth-parameters USERNAME="user-email-id@domain.com",PASSWORD="user-password" \
    --client-id ${USER_POOL_CLIENT_ID} | jq -r '.AuthenticationResult.IdToken')
  5. API 요청
    API 요청 테스트를 위해, API Gateway 콘솔에서 2. CDK 스택 배포 절차를 통해 생성된 random-string라는 이름의 API를 확인하고, API 요청을 위해 API의 ID 값을 메모합니다.아래와 같이 API 요청을 수행합니다. 아래 명령에서 your-user-pool-client-id 는 앞서 메모한 User Pool Client ID 값으로 교체하고, {your-api-gateway-id}을 바로 전 단계에서 메모한 API의 ID 값으로 수정합니다. ["swxrAuc"]와 같이 API 요청에 대한 응답 코드가 출력됩니다.

    REGION=$(aws configure get region)
    
    MY_ID_TOKEN=$(aws cognito-idp initiate-auth --auth-flow USER_PASSWORD_AUTH \
    --auth-parameters USERNAME="user-email-id@domain.com",PASSWORD="user-password" \
    --client-id your-user-pool-client-id | jq -r '.AuthenticationResult.IdToken')
    
    curl -X GET "https://{your-api-gateway-id}.execute-api.$REGION.amazonaws.com/prod/random/strings?len=7" \
    --header "Authorization: ${MY_ID_TOKEN}"
  6. 대량의 API 요청 스크립트 실행
    API 요청이 정상적으로 처리되는 것을 확인했으니, 대량의 API 요청을 수행하는 스크립트를 실행하여 로그를 생성합니다. {your-api-gateway-id}는 앞 단계에서 메모한 API의 ID 값으로 수정하여 API 요청을 수행합니다.

    
    pip install "requests==2.28.1"
    python tests/run_test.py --execution-id {your-api-gateway-execution-id} \
                                       --region-name ${REGION} \
                                       --auth-token ${MY_ID_TOKEN} \
                                       --max-count 10

    API 호출이 정상적으로 완료되면 아래와 같은 결과가 출력됩니다.

    200 ["yDJtcFjevljFa", "hdutMGLAfPvUf"] https://0ahgq23uc8.execute-api.ap-northeast-2.amazonaws.com/prod/random/strings?chars=letters&len=13&num=2
  7. Access Log 생성 확인API 호출로 인해 생성된 API Gateway의 Access Log는 Data Firehose에 의해 S3 버킷에 저장됩니다. 이를 확인하기 위해 5~10분 뒤에 S3 콘솔로 이동합니다. 그리고 Access Log 저장을 위해 CDK를 사용해 미리 생성한 apigw-access-log-to-firehose 로 시작하는 이름의 S3 버킷으로 이동합니다. Access Log는 year, month, day, hour 단위로 구조화된 폴더에 저장되는 것을 확인할 수 있습니다.로그 파일을 열어보면 아래와 같은 format의 Access Log가 확인됩니다.

    {"requestId": "f0a72781-3a69-4bd8-bef0-b4c2a95cbf3a", "ip": "15.165.43.146", "user": "-", "requestTime": 1675146135164, "httpMethod": "GET", "resourcePath": "/random/strings", "status": 200, "protocol": "HTTP/1.1", "responseLength": 11}

    그리고 다음 단계에서 Access Log를 저장한 S3 버킷에 접근하기 위해 apigw-access-log-to-firehose 로 시작하는 S3 버킷 이름을 메모합니다.

  8. Athena 테이블 생성 및 쿼리Athena 콘솔로 이동하고, Launch query editor를 클릭하여 Query editor로 이동합니다. 쿼리 수행을 위한 workgroup으로 SaaSMeteringDemo을 선택하고, Query editor에서 아래 쿼리를 실행합니다. 쿼리가 정상적으로 실행되면 Query successful 메시지가 출력됩니다.

    CREATE DATABASE IF NOT EXISTS mydatabase

    이제 테이블을 생성합니다. 아래의 테이블 생성 쿼리에서 LOCATION의 apigw-access-log-to-firehose-xxxxx 값은 앞 단계에서 메모한 S3 버킷 이름으로 대체합니다. 그리고 쿼리를 실행합니다.

    CREATE EXTERNAL TABLE mydatabase.restapi_access_log_json (
      `requestId` string,
      `ip` string,
      `user` string,
      `requestTime` timestamp,
      `httpMethod` string,
      `resourcePath` string,
      `status` string,
      `protocol` string,
      `responseLength` integer)
    PARTITIONED BY (
      `year` int,
      `month` int,
      `day` int,
      `hour` int)
    ROW FORMAT SERDE
      'org.openx.data.jsonserde.JsonSerDe'
    STORED AS INPUTFORMAT
      'org.apache.hadoop.mapred.TextInputFormat'
    OUTPUTFORMAT
      'org.apache.hadoop.hive.ql.io.IgnoreKeyTextOutputFormat'
    LOCATION
      's3://apigw-access-log-to-firehose-xxxxx/json-data'

    테이블 생성 쿼리가 정상적으로 실행되었다면, Database를 이전에 생성한 mydatabase로 선택하고 restapi_access_log_json 테이블이 제대로 생성되었는지 확인합니다.

    S3에 파티션된 데이터를 테이블로 로드하는 쿼리를 실행합니다.

    MSCK REPAIR TABLE mydatabase.restapi_access_log_json;

    생성된 파티션을 확인하려면 다음의 쿼리를 실행하여 확인 가능합니다.

    SHOW PARTITIONS mydatabase.restapi_access_log_json;

    API 호출 로그를 확인하기 위해, 다음의 쿼리를 실행합니다.

    SELECT * FROM mydatabase.restapi_access_log_json;

    쿼리를 실행하면 API 호출 로그가 조회됩니다.

  9. (선택 사항) Athena CTAS 쿼리를 이용하여 API 호출 로그 통합실시간으로 생성되는 데이터를 Data Firehose를 활용해 S3에 저장하면, 작은 크기의 여러 파일로 나뉘어 저장됩니다. Athena를 활용해 이 데이터를 쿼리할 때 더 나은 성능을 위해 여러 파일들을 하나의 큰 파일로 합치는 것이 권장됩니다. 파일 포맷은 JSON 보다는 Parquet, ORC, AVRO 와 같은 컬럼 데이터 포맷 사용이 성능 관점에서 좋습니다.아래의 쿼리를 수행하여 API 호출 로그 파일을 하나로 합치고, Parquet 포맷으로 저장할 수 있습니다.
    CREATE EXTERNAL TABLE mydatabase.restapi_access_log_parquet (
      `requestId` string,
      `ip` string,
      `user` string,
      `requestTime` timestamp,
      `httpMethod` string,
      `resourcePath` string,
      `status` string,
      `protocol` string,
      `responseLength` integer)
    PARTITIONED BY (
     `year` int,
     `month` int,
     `day` int,
     `hour` int)
    ROW FORMAT SERDE
     'org.apache.hadoop.hive.ql.io.parquet.serde.ParquetHiveSerDe'
    STORED AS INPUTFORMAT
     'org.apache.hadoop.hive.ql.io.parquet.MapredParquetInputFormat'
    OUTPUTFORMAT
     'org.apache.hadoop.hive.ql.io.parquet.MapredParquetOutputFormat'
    LOCATION
     's3://apigw-access-log-to-firehose-xxxxx/parquet-data'

    이전 CDK 배포 단계에서 위 쿼리를 수행하는 MergeSmallFilesWithAthenaCTAS 라는 이름의 Lambda 함수와 이 함수를 주기적으로 실행하는 Amazon EventBridge 규칙을 미리 생성했습니다. MergeSmallFilesWithAthenaCTAS Lambda 함수는 Amazon EventBridge에 의해 1시간마다 자동으로 실행됩니다.

    MergeSmallFilesWithAthenaCTAS Lambda 함수 실행을 테스트하고 싶은 경우, Test Event를 생성하여 테스트할 수 있습니다.

단계4: 리소스 정리하기

테스트를 완료하였다면, 불필요한 비용 발생을 막기 위해 사용한 자원을 삭제합니다. 리소스 삭제 작업은 CDK를 실행한 터미널 환경으로 돌아가 아래의 명령어를 실행하여 수행합니다.

cdk destroy --force --all

3. 맺음말

이상으로 Amazon API Gateway, Data Firehose, S3, 그리고 Athena를 활용한 SaaS 미터링 시스템 구축 방법에 대해 살펴보았습니다. 이 솔루션은 AWS의 관리형 서비스들을 기반으로 하여, 운영 부담을 최소화하면서도 확장 가능하고 신뢰할 수 있는 미터링 시스템을 구현할 수 있게 해줍니다. API Gateway를 통한 중앙화된 로그 수집, Data Firehose를 통한 실시간 데이터 전송과 S3 저장, 그리고 Athena를 이용한 서버리스 쿼리 기능은 대규모 API 호출 기반의 SaaS 서비스에 특히 유용한 솔루션을 제공합니다.

그러나 다양한 환경과 요구사항에 대한 추가적인 고민도 필요합니다. ECS나 EKS와 같은 컨테이너 기반 환경에서는 사이드카 패턴이나 서비스 메시를 활용한 접근 방식을 고려해볼 수 있으며, API 호출 외에도 리소스 사용량, 사용자 활동, 비즈니스 로직 기반의 커스텀 메트릭 등 다양한 미터링 방법을 탐색해볼 필요가 있습니다.

SaaS 비즈니스를 운영하거나 계획 중인 분들에게 이 솔루션이 좋은 참고가 되길 바랍니다. 본 블로그에서 소개한 방식은 시작점이 될 수 있으며, 여기에 다양한 AWS 서비스와 아키텍처 패턴을 결합하여 각 비즈니스의 특성과 요구사항에 가장 적합한 미터링 시스템을 구축할 수 있을 것입니다. 이를 통해 정확한 사용량 측정, 투명한 요금 청구, 그리고 데이터 기반의 서비스 개선이 가능해지며, 궁극적으로 비즈니스의 성장과 고객 만족도 향상을 동시에 달성할 수 있을 것입니다.

4. 참고자료

Sungmin Kim

Sungmin Kim

김성민님은 AWS의 솔루션즈 아키텍트 입니다. Startup 고객들과 협력하여 비즈니스 성과를 실현하는데 도움을 드리고 있습니다.

Jinah Kim

Jinah Kim

김진아 솔루션즈 아키텍트는 스타트업 고객이 효율적이고 안정적인 서비스를 운영할 수 있도록 아키텍처 설계 가이드를 드리고 기술을 지원하는 역할을 수행하고 있습니다.