S3 Express One Zone 성능 최적화 모범 사례

S3 Express One Zone 성능 최적화 모범 사례 (Best practices to optimize S3 Express One Zone performance)

Amazon S3 Express One Zone에서 객체를 업로드·검색하는 애플리케이션을 만들 때는 성능을 최적화하도록 모범 사례 지침을 따르는 게 좋아요. S3 Express One Zone 스토리지 클래스를 사용하려면 S3 디렉터리 버킷을 만들어야 해요. S3 Express One Zone 스토리지 클래스는 S3 일반 목적 버킷에서는 지원되지 않아요.

다른 모든 Amazon S3 스토리지 클래스와 S3 일반 목적 버킷의 성능 지침은 "Best practices design patterns: optimizing Amazon S3 performance"를 참고해요.

S3 Express One Zone 스토리지 클래스와 디렉터리 버킷으로 고규모 워크로드에서 최적의 성능과 확장성을 얻으려면 디렉터리 버킷이 일반 목적 버킷과 어떻게 다르게 동작하는지 이해하는 것이 중요해요. 그런 다음 애플리케이션을 디렉터리 버킷의 동작 방식에 맞추기 위한 모범 사례를 제공해 드릴게요.

출처: 문서

본문

디렉터리 버킷이 동작하는 방식

Amazon S3 Express One Zone 스토리지 클래스는 디렉터리 버킷당 초당 최대 2,000,000건의 GET과 최대 200,000건의 PUT 트랜잭션(TPS)을 지원할 수 있어요. S3 Express One Zone에서 데이터는 가용 영역의 S3 디렉터리 버킷에 저장돼요. 디렉터리 버킷의 객체는 파일 시스템과 비슷한 계층적 네임스페이스 안에서 접근할 수 있는데, 이는 평면 네임스페이스를 갖는 S3 일반 목적 버킷과 대조적이에요. 일반 목적 버킷과 달리 디렉터리 버킷은 키를 프리픽스(prefix)가 아니라 디렉터리로 계층적으로 정리해요. 프리픽스는 객체 키 이름의 시작 부분에 있는 문자열이에요. 일반 목적 버킷에서 데이터를 정리하고 평면 객체 스토리지 아키텍처를 관리하려면 프리픽스를 사용할 수 있어요. 자세한 내용은 "프리픽스를 사용해 객체 정리하기"를 참고해요.

디렉터리 버킷에서 객체는 유일하게 지원되는 구분자인 슬래시(/)를 사용해 계층적 네임스페이스로 정리돼요. dir1/dir2/file1.txt 같은 키로 객체를 업로드하면 dir1/과 dir2/ 디렉터리가 Amazon S3에 의해 자동으로 생성·관리돼요. 디렉터리는 PutObject 또는 CreateMultiPartUpload 연산 중에 생성되고, DeleteObject 또는 AbortMultiPartUpload 연산 후 비어 있게 되면 자동으로 제거돼요. 디렉터리의 객체와 하위 디렉터리 수에는 상한이 없어요.

디렉터리 버킷에 객체를 업로드할 때 생성되는 디렉터리는 HTTP 503(Slow Down) 오류의 가능성을 줄이기 위해 즉시 확장될 수 있어요. 이 자동 확장 덕분에 애플리케이션은 필요에 따라 디렉터리 안과 디렉터리 사이에서 읽기·쓰기 요청을 병렬화할 수 있어요. S3 Express One Zone에서 개별 디렉터리는 디렉터리 버킷의 최대 요청 속도를 지원하도록 설계돼요. 시스템이 균등한 부하 분산을 위해 객체를 자동으로 분산하므로 최적의 성능을 위해 키 프리픽스에 무작위성(entropy)을 둘 필요가 없어요. 하지만 그 결과로 디렉터리 버킷에서는 키가 사전식(lexicographic) 순서로 저장되지 않아요. 이는 사전식으로 더 가까운 키가 같은 서버에 함께 배치될 가능성이 더 높은 S3 일반 목적 버킷과 대조적이에요.

디렉터리 버킷 연산과 디렉터리 상호작용 예제에 대한 자세한 내용은 "디렉터리 버킷 연산·디렉터리 상호작용 예제"를 참고해요.

모범 사례

디렉터리 버킷 성능을 최적화하고 워크로드가 시간이 지나도 확장되도록 도와주는 모범 사례를 따라 봐요.

많은 항목(객체 또는 하위 디렉터리)을 포함하는 디렉터리 사용

디렉터리 버킷은 모든 워크로드에 기본적으로 높은 성능을 제공해요. 특정 연산에서 성능을 더 최적화하려면 더 많은 항목(객체 또는 하위 디렉터리)을 디렉터리로 통합하면 지연 시간이 낮아지고 요청 속도가 높아져요:

  • PutObject, DeleteObject, CreateMultiPartUpload, AbortMultiPartUpload 같은 변경(mutating) API 연산은 수천 개의 항목을 포함하는 더 적고 밀집된 디렉터리로 구현할 때, 많은 수의 더 작은 디렉터리를 사용할 때보다 최적의 성능을 내요.
  • ListObjectsV2 연산은 결과 페이지를 채우기 위해 탐색해야 하는 디렉터리가 더 적을 때 더 잘 동작해요.
프리픽스에 무작위성(entropy)을 사용하지 않기

Amazon S3 연산에서 entropy는 저장 파티션 전체에 워크로드를 균등하게 분산하는 데 도움이 되는 프리픽스 명명의 무작위성을 말해요. 하지만 디렉터리 버킷은 내부적으로 부하 분산을 관리하므로 최상의 성능을 위해 프리픽스에 entropy를 사용하지 않는 것이 좋아요. 디렉터리 버킷의 경우 entropy는 이미 생성된 디렉터리를 재사용하지 않아 요청이 더 느려질 수 있기 때문이에요.

$HASH/directory/object 같은 키 패턴은 많은 중간 디렉터리를 만들 수 있어요. 다음 예제에서 부모가 서로 다르므로 모든 job-1은 서로 다른 디렉터리예요. 디렉터리가 희소해지고 변경·목록 요청이 더 느려져요. 이 예제에는 각각 하나의 항목만 있는 12개의 중간 디렉터리가 있어요.

s3://my-bucket/0cc175b9c0f1b6a831c399e269772661/job-1/file1
  
s3://my-bucket/92eb5ffee6ae2fec3ad71c777531578f/job-1/file2
  
s3://my-bucket/4a8a08f09d37b73795649038408b5f33/job-1/file3
  
s3://my-bucket/8277e0910d750195b448797616e091ad/job-1/file4
  
s3://my-bucket/e1671797c52e15f763380b45e841ec32/job-1/file5
  
s3://my-bucket/8fa14cdd754f91cc6554c9e71929cce7/job-1/file6

대신 성능을 위해 $HASH 구성 요소를 제거하고 job-1이 단일 디렉터리가 되게 해 디렉터리의 밀도를 높일 수 있어요. 다음 예제에서 6개의 항목을 가진 단일 중간 디렉터리는 앞선 예제보다 더 나은 성능으로 이어질 수 있어요.

s3://my-bucket/job-1/file1
  
s3://my-bucket/job-1/file2
  
s3://my-bucket/job-1/file3
  
s3://my-bucket/job-1/file4
  
s3://my-bucket/job-1/file5
  
s3://my-bucket/job-1/file6

이 성능 이점은 객체 키가 처음 생성될 때 키 이름에 디렉터리가 포함되면 해당 객체에 대해 디렉터리가 자동으로 생성되기 때문이에요. 같은 디렉터리로의 이후 객체 업로드는 디렉터리를 생성할 필요가 없어서 기존 디렉터리에 대한 객체 업로드의 지연 시간을 줄여 줘요.

ListObjectsV2 호출 중 객체를 논리적으로 그룹화할 필요가 없다면 키의 부분을 구분하는 데 구분자 / 대신 다른 구분자를 사용

/ 구분자는 디렉터리 버킷에서 특별하게 처리되므로 의도를 가지고 사용해야 해요. 디렉터리 버킷은 객체를 사전식 순서로 정렬하지 않지만, 디렉터리 안의 객체는 ListObjectsV2 출력에서 여전히 함께 그룹화돼요. 이 기능이 필요하지 않다면 /를 다른 문자로 대체해 중간 디렉터리 생성을 유발하지 않도록 할 수 있어요.

예를 들어 다음 키가 YYYY/MM/DD/HH/ 프리픽스 패턴에 있다고 가정해 봐요.

s3://my-bucket/2024/04/00/01/file1
  
s3://my-bucket/2024/04/00/02/file2
  
s3://my-bucket/2024/04/00/03/file3
  
s3://my-bucket/2024/04/01/01/file4
  
s3://my-bucket/2024/04/01/02/file5
  
s3://my-bucket/2024/04/01/03/file6

ListObjectsV2 결과에서 시간이나 일 단위로 객체를 그룹화할 필요는 없지만 월 단위로는 그룹화해야 한다면, 다음 YYYY/MM/DD-HH- 키 패턴이 디렉터리를 현저히 줄이고 ListObjectsV2 연산의 성능을 더 좋게 만들어 줘요.

s3://my-bucket/2024/04/00-01-file1
  
s3://my-bucket/2024/04/00-01-file2
  
s3://my-bucket/2024/04/00-01-file3
  
s3://my-bucket/2024/04/01-02-file4
  
s3://my-bucket/2024/04/01-02-file5
  
s3://my-bucket/2024/04/01-02-file6

가능하면 구분자 있는 목록(delimited list) 연산 사용

구분자가 없는 ListObjectsV2 요청은 모든 디렉터리에 대해 깊이 우선(depth-first) 재귀 탐색을 수행해요. 구분자가 있는 ListObjectsV2 요청은 prefix 파라미터로 지정된 디렉터리의 항목만 검색해서 요청 지연 시간을 줄이고 초당 총 키 수를 늘려 줘요. 디렉터리 버킷에서는 가능하면 구분자 있는 목록 연산을 사용해요. 구분자 있는 목록은 디렉터리가 더 적게 방문되는 결과를 만들어 초당 더 많은 키와 더 낮은 요청 지연 시간으로 이어져요.

예를 들어 디렉터리 버킷에 다음 디렉터리와 객체가 있다고 가정해 봐요:

s3://my-bucket/2024/04/12-01-file1
  
s3://my-bucket/2024/04/12-01-file2
  
...
  
s3://my-bucket/2024/05/12-01-file1
  
s3://my-bucket/2024/05/12-01-file2
  
...
  
s3://my-bucket/2024/06/12-01-file1
  
s3://my-bucket/2024/06/12-01-file2
  
...
  
s3://my-bucket/2024/07/12-01-file1
  
s3://my-bucket/2024/07/12-01-file2
  
...

애플리케이션의 로직이 허용한다면 더 나은 ListObjectsV2 성능을 위해 구분자 있는 목록으로 하위 디렉터리와 객체를 나열해요. 예를 들어 구분자 있는 목록 연산에 대해 다음 명령을 실행할 수 있어요:

aws s3api list-objects-v2 --bucket my-bucket --prefix '2024/' --delimiter '/'

출력은 하위 디렉터리 목록이에요.

{
    "CommonPrefixes": [
        {
            "Prefix": "2024/04/"
        },
        {
            "Prefix": "2024/05/"
        },
        {
            "Prefix": "2024/06/"
        },
        {
            "Prefix": "2024/07/"
        }
    ]
}

더 나은 성능으로 각 하위 디렉터리를 나열하려면 다음과 같은 명령을 실행할 수 있어요:

명령:

aws s3api list-objects-v2 --bucket my-bucket --prefix '2024/04' --delimiter '/'

출력:

{
    "Contents": [
        {
            "Key": "2024/04/12-01-file1"
        },
        {
            "Key": "2024/04/12-01-file2"
        }
    ]
}

S3 Express One Zone 스토리지를 컴퓨팅 리소스와 함께 배치

S3 Express One Zone에서 각 디렉터리 버킷은 버킷을 만들 때 선택한 단일 가용 영역에 위치해요. 컴퓨팅 워크로드나 리소스에 지역적으로 가까운 가용 영역에 새 디렉터리 버킷을 만들어 시작할 수 있어요. 그러면 매우 낮은 지연 시간의 읽기·쓰기를 바로 시작할 수 있어요. 디렉터리 버킷은 컴퓨팅과 스토리지 사이의 지연 시간을 줄이기 위해 AWS 리전에서 가용 영역을 선택할 수 있는 S3 버킷 유형이에요.

가용 영역을 가로질러 디렉터리 버킷에 접근하면 지연 시간이 약간 늘어날 수 있어요. 성능을 최적화하기 위해 가능하면 같은 가용 영역에 있는 Amazon Elastic Container Service, Amazon Elastic Kubernetes Service, Amazon Elastic Compute Cloud 인스턴스에서 디렉터리 버킷에 접근할 것을 권장해요.

1MB가 넘는 객체에서 높은 처리량을 얻기 위해 동시 연결 사용

디렉터리 버킷에 여러 동시 요청을 보내 요청을 별도의 연결로 분산해 사용 가능한 대역폭을 최대화하면 최상의 성능을 얻을 수 있어요. 일반 목적 버킷과 마찬가지로 S3 Express One Zone은 디렉터리 버킷으로 만드는 연결 수에 제한이 없어요. 개별 디렉터리는 같은 디렉터리에 많은 수의 동시 쓰기가 일어날 때 성능을 수평으로 자동 확장할 수 있어요.

디렉터리 버킷에 대한 개별 TCP 연결은 초당 업로드·다운로드할 수 있는 바이트 수에 고정된 상한이 있어요. 객체가 커지면 요청 시간은 트랜잭션 처리보다는 바이트 스트리밍이 지배하게 돼요. 더 큰 객체의 업로드·다운로드를 병렬화하기 위해 여러 연결을 사용하면 엔드투엔드 지연 시간을 줄일 수 있어요. Java 2.x SDK를 사용한다면 멀티파트 업로드 API 연산과 바이트 범위 가져오기 같은 성능 개선을 활용해 데이터에 병렬로 접근하는 S3 Transfer Manager를 사용하는 것을 고려해 봐요.

게이트웨이 VPC 엔드포인트 사용

게이트웨이 엔드포인트는 VPC에 인터넷 게이트웨이나 NAT 장치 없이도 VPC에서 디렉터리 버킷으로의 직접 연결을 제공해요. 패킷이 네트워크에서 보내는 시간을 줄이려면 디렉터리 버킷용 게이트웨이 VPC 엔드포인트로 VPC를 구성해야 해요. 자세한 내용은 "디렉터리 버킷 네트워킹 (Networking for directory buckets)"을 참고해요.

세션 인증을 사용하고 유효한 동안 세션 토큰 재사용

디렉터리 버킷은 성능에 민감한 API 연산의 지연 시간을 줄이기 위해 세션 토큰 인증 메커니즘을 제공해요. CreateSession을 한 번 호출해 세션 토큰을 받으면 그 토큰은 이후 5분 동안 모든 요청에 유효해요. API 호출에서 가장 낮은 지연 시간을 얻으려면 세션 토큰을 획득하고 그 토큰의 수명 전체 동안 재사용한 뒤 갱신하는 것이 좋아요.

AWS SDK를 사용하면 SDK가 세션 만료 시 서비스 중단을 피하기 위해 세션 토큰 갱신을 자동으로 처리해요. CreateSession API 연산에 대한 요청을 시작·관리하려면 AWS SDK를 사용할 것을 권장해요.

CreateSession에 대한 자세한 내용은 "CreateSession으로 존 엔드포인트 API 연산 인가하기"를 참고해요.

CRT 기반 클라이언트 사용

AWS Common Runtime(CRT)은 AWS SDK의 기반 역할을 하도록 만들어진 C로 작성된 모듈형·고성능·효율적인 라이브러리 집합이에요. CRT는 개선된 처리량, 향상된 연결 관리, 더 빠른 시작 시간을 제공해요. CRT는 Go를 제외한 모든 AWS SDK에서 사용할 수 있어요.

사용하는 SDK에 CRT를 구성하는 방법은 "AWS Common Runtime(CRT) libraries", "Accelerate Amazon S3 throughput with the AWS Common Runtime", "Introducing CRT-based S3 client and the S3 Transfer Manager in the AWS SDK for Java 2.x", "Using S3CrtClient for Amazon S3 operations", "Configure AWS CRT-based HTTP clients"를 참고해요.

최신 버전의 AWS SDK 사용

AWS SDK는 Amazon S3 성능을 최적화하기 위한 권장 지침 중 상당수에 내장 지원을 제공해요. SDK는 애플리케이션 안에서 Amazon S3를 활용하기 위한 더 간단한 API를 제공하고 최신 모범 사례를 따르도록 정기적으로 업데이트돼요. 예를 들어 SDK는 HTTP 503 오류 후 요청을 자동으로 재시도하고 느린 연결 응답을 처리해요.

Java 2.x SDK를 사용한다면 바이트 범위 요청을 적절히 사용해 초당 수천 건의 요청을 달성하도록 연결을 자동으로 수평 확장하는 S3 Transfer Manager를 사용하는 것을 고려해 봐요. 바이트 범위 요청은 같은 객체 안의 서로 다른 바이트 범위를 가져오기 위해 S3에 대한 동시 연결을 사용할 수 있으므로 성능을 향상시킬 수 있어요. 이는 단일 전체 객체 요청보다 더 높은 총 처리량을 달성하는 데 도움이 돼요. 따라서 최신 성능 최적화 기능을 얻으려면 최신 버전의 AWS SDK를 사용하는 것이 중요해요.

성능 문제 해결 (Performance troubleshooting)

대기 시간에 민감한 애플리케이션에 재시도 요청을 설정하고 있나요?

S3 Express One Zone은 추가 튜닝 없이도 일관된 수준의 고성능을 제공하도록 특별히 설계됐어요. 하지만 공격적인 타임아웃 값과 재시도를 설정하면 일관된 지연 시간과 성능을 유지하는 데 더욱 도움이 될 수 있어요. AWS SDK에는 특정 애플리케이션의 허용 오차에 맞게 조정할 수 있는 구성 가능한 타임아웃·재시도 값이 있어요.

AWS Common Runtime(CRT) 라이브러리와 최적의 Amazon EC2 인스턴스 유형을 사용하고 있나요?

많은 수의 읽기·쓰기 연산을 수행하는 애플리케이션은 그러지 않은 애플리케이션보다 더 많은 메모리나 컴퓨팅 용량이 필요할 가능성이 높아요. 성능이 많이 필요한 워크로드를 위해 Amazon Elastic Compute Cloud(Amazon EC2) 인스턴스를 시작할 때 애플리케이션에 필요한 양의 리소스를 가진 인스턴스 유형을 선택해요. S3 Express One Zone 고성능 스토리지는 더 많은 시스템 메모리와 더 강력한 CPU·GPU를 가진 더 크고 새로운 인스턴스 유형과 이상적으로 짝을 이루며, 이들은 고성능 스토리지를 활용할 수 있어요. 또한 읽기·쓰기 요청을 병렬로 더 잘 가속할 수 있는 CRT 지원 AWS SDK의 최신 버전을 사용할 것을 권장해요.

세션 기반 인증에 AWS SDK를 사용하고 있나요?

Amazon S3에서 HTTP REST API 요청을 사용할 때도 AWS SDK의 일부인 것과 같은 모범 사례를 따라 성능을 최적화할 수 있어요. 하지만 S3 Express One Zone이 사용하는 세션 기반 인가·인증 메커니즘에서는 CreateSession과 그 관리형 세션 토큰을 관리하기 위해 AWS SDK를 사용할 것을 강력히 권장해요. AWS SDK는 CreateSession API 연산을 사용해 사용자 대신 토큰을 자동으로 생성·갱신해요. CreateSession을 사용하면 각 요청을 인가하기 위해 AWS Identity and Access Management(IAM)로 가는 요청당 왕복 지연 시간을 절약할 수 있어요.

디렉터리 버킷 연산·디렉터리 상호작용 예제 (Directory bucket operation and directory interaction examples)

다음은 디렉터리 버킷이 어떻게 동작하는지 보여 주는 세 가지 예제예요.

예제 1: 디렉터리 버킷에 대한 S3 PutObject 요청이 디렉터리와 상호작용하는 방식

  1. 빈 버킷에서 PUT(<bucket>, "documents/reports/quarterly.txt") 연산이 실행되면 버킷 루트 안에 documents/ 디렉터리가 생성되고, documents/ 안에 reports/ 디렉터리가 생성되며, reports/ 안에 quarterly.txt 객체가 생성돼요. 이 연산에서는 객체에 더해 두 개의 디렉터리가 생성됐어요.
  2. 그런 다음 PUT(<bucket>, "documents/logs/application.txt") 연산이 실행되면 documents/ 디렉터리는 이미 존재하고, documents/ 안의 logs/ 디렉터리는 존재하지 않아 생성되며, logs/ 안에 application.txt 객체가 생성돼요. 이 연산에서는 객체에 더해 디렉터리 하나만 생성됐어요.
  3. 마지막으로 PUT(<bucket>, "documents/readme.txt") 연산이 실행되면 루트 안의 documents/ 디렉터리는 이미 존재하고 readme.txt 객체가 생성돼요. 이 연산에서는 디렉터리가 생성되지 않아요.

예제 2: 디렉터리 버킷에 대한 S3 ListObjectsV2 요청이 디렉터리와 상호작용하는 방식

구분자를 지정하지 않은 S3 ListObjectsV2 요청의 경우 버킷이 깊이 우선 방식으로 탐색돼요. 출력은 일관된 순서로 반환돼요. 이 순서는 요청 사이에도 동일하게 유지되지만, 순서가 사전식은 아니에요. 앞선 예제에서 만든 버킷과 디렉터리의 경우:

  1. LIST(<bucket>)이 실행되면 documents/ 디렉터리에 들어가고 탐색이 시작돼요.
  2. logs/ 하위 디렉터리에 들어가고 탐색이 시작돼요.
  3. logs/ 안에서 application.txt 객체를 찾아요.
  4. logs/ 안에 더 이상 항목이 없어요. List 연산이 logs/에서 빠져나와 다시 documents/에 들어가요.
  5. documents/ 디렉터리 탐색이 계속되고 readme.txt 객체를 찾아요.
  6. documents/ 디렉터리 탐색이 계속되고 reports/ 하위 디렉터리에 들어가 탐색이 시작돼요.
  7. reports/ 안에서 quarterly.txt 객체를 찾아요.
  8. reports/ 안에 더 이상 항목이 없어요. List 연산이 reports/에서 빠져나와 다시 documents/에 들어가요.
  9. documents/ 안에 더 이상 항목이 없어 List 연산이 반환돼요.

이 예제에서 logs/는 readme.txt 앞에 정렬되고 readme.txt는 reports/ 앞에 정렬돼요.

예제 3: 디렉터리 버킷에 대한 S3 DeleteObject 요청이 디렉터리와 상호작용하는 방식

  1. 같은 버킷에서 DELETE(<bucket>, "documents/reports/quarterly.txt") 연산이 실행되면 quarterly.txt 객체가 삭제되고 reports/ 디렉터리가 비어 즉시 삭제돼요. documents/ 디렉터리는 안에 logs/ 디렉터리와 readme.txt 객체가 모두 있어 비어 있지 않으므로 삭제되지 않아요. 이 연산에서는 객체 하나와 디렉터리 하나가 삭제됐어요.
  2. DELETE(<bucket>, "documents/readme.txt") 연산이 실행되면 readme.txt 객체가 삭제돼요. documents/는 안에 logs/ 디렉터리가 있어 여전히 비어 있지 않으므로 삭제되지 않아요. 이 연산에서는 디렉터리는 삭제되지 않고 객체만 삭제돼요.
  3. 마지막으로 DELETE(<bucket>, "documents/logs/application.txt") 연산이 실행되면 application.txt가 삭제되고 logs/가 비어 즉시 삭제돼요. 그러면 documents/도 비어 즉시 삭제돼요. 이 연산에서는 디렉터리 두 개와 객체 하나가 삭제돼요. 이제 버킷이 비었어요.

더 알아보기 (Learn more)