

기계 번역으로 제공되는 번역입니다. 제공된 번역과 원본 영어의 내용이 상충하는 경우에는 영어 버전이 우선합니다.

# Amazon S3 유틸리티
<a name="sdk-utilities-s3"></a>

## Amazon S3 Transfer Managers
<a name="transfer-managers"></a>

 Amazon S3 업로드 및 다운로드 관리자는 큰 객체를 분할할 수 있으므로 객체를 여러 파트로 병렬 전송할 수 있습니다. 이렇게 하면 중단된 전송을 쉽게 재개할 수 있습니다.

### Amazon S3 Upload Manager
<a name="s3-upload-manager"></a>

 Amazon S3 Upload Manager는 파일을 더 작은 파트로 분할하고 병렬로 업로드할 수 있는지 여부를 결정합니다. 병렬 업로드 수와 업로드된 파트의 크기를 사용자 지정할 수 있습니다.

 다음 예제에서는 Amazon S3 `Uploader`를 사용하여 파일을 업로드합니다. `Uploader` 사용 방법은 `s3.PutObject()` 작업과 비슷합니다.

```
import "context"
import "github.com/aws/aws-sdk-go-v2/config"
import "github.com/aws/aws-sdk-go-v2/service/s3"
import "github.com/aws/aws-sdk-go-v2/feature/s3/manager"

// ...

cfg, err := config.LoadDefaultConfig(context.TODO())
if err != nil {
    log.Printf("error: %v", err)
    return
}

client := s3.NewFromConfig(cfg)

uploader := manager.NewUploader(client)
result, err := uploader.Upload(context.TODO(), &s3.PutObjectInput{
    Bucket: aws.String("{{amzn-s3-demo-bucket}}"),
    Key:    aws.String("{{my-object-key}}"),
    Body:   uploadFile,
})
```

#### 구성 옵션
<a name="configuration-options"></a>

 [NewUploader](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/feature/s3/manager#NewUploader)를 사용하여 `Uploader` 인스턴스를 인스턴스화할 때 여러 구성 옵션을 지정하여 객체 업로드 방법을 사용자 지정할 수 있습니다. 옵션은 `NewUploader`에 하나 이상의 인수를 제공하여 재정의됩니다. 이 옵션에는 다음이 포함됩니다.
+  `PartSize` - 업로드할 각 파트의 버퍼 크기를 바이트 단위로 지정합니다. 파트당 최소 크기는 5MiB입니다.
+  `Concurrency` - 병렬로 업로드할 파트의 수를 지정합니다.
+  `LeavePartsOnError` - Amazon S3에 성공적으로 업로드된 파트를 남길지 여부를 나타냅니다.

 `Concurrency` 값은 주어진 `Upload` 직접 호출에 대해 발생할 수 있는 동시 파트 업로드 수를 제한합니다. 이는 글로벌 클라이언트 동시성 제한이 아닙니다. `PartSize` 및 `Concurrency` 구성 값을 조정하여 최적의 구성을 찾습니다. 예를 들어, 연결 대역폭이 높은 시스템은 더 큰 파트와 더 많은 업로드를 병렬로 전송할 수 있습니다.

 예를 들어, 애플리케이션에서 `Uploader`의 `Concurrency` 설정을 `5`로 구성합니다. 애플리케이션이 서로 다른 두 개의 고루틴에서 `Upload`를 직접 호출하면 결과는 `10`개의 동시 파트 업로드(고루틴 2 \* `Concurrency` 5)입니다.

**주의**  
 애플리케이션 리소스 소진을 방지하기 위해 애플리케이션은 동시 직접 호출을 `Upload`로 제한해야 합니다.

 다음은 `Uploader` 생성 중에 기본 파트 크기를 설정하는 예제입니다.

```
uploader := manager.NewUploader(client, func(u *Uploader) {
    u.PartSize = 10 * 1024 * 1024, // 10 MiB
})
```

 `Uploader` 및 구성에 대한 자세한 내용은 AWS SDK for Go API 참조의 [업로더](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/feature/s3/manager#Uploader)를 참조하세요.

#### PutObjectInput 본문 필드(io.ReadSeeker와 io.Reader 비교)
<a name="putobjectinput-body-field-ioreadseeker-vs-ioreader"></a>

 `s3.PutObjectInput` 구조체의 `Body` 필드는 `io.Reader` 유형입니다. 그러나 이 필드를 `io.ReadSeeker` 및 `io.ReaderAt` 인터페이스를 모두 충족하는 유형으로 채우면 호스트 환경의 애플리케이션 리소스 사용률을 개선할 수 있습니다. 다음 예제에서는 두 인터페이스를 모두 충족하는 유형 `ReadSeekerAt`을 생성합니다.

```
type ReadSeekerAt interface {
    io.ReadSeeker
    io.ReaderAt
}
```

 `io.Reader` 유형의 경우 파트를 업로드하려면 먼저 리더의 바이트를 메모리에 버퍼링해야 합니다. `PartSize` 또는 `Concurrency` 값을 늘리면 `Uploader`에 필요한 메모리(RAM)가 크게 증가합니다. 필요한 메모리는 약 *`PartSize`* \* *`Concurrency`*입니다. 예를 들어 `PartSize`에 100MB를 지정하고 `Concurrency`에 10을 지정하려면 최소 1GB가 필요합니다.

 `io.Reader` 유형은 바이트를 읽기 전에 크기를 결정할 수 없으므로 `Uploader`는 업로드할 파트 수를 계산할 수 없습니다. 따라서 `PartSize`를 너무 낮게 설정하면 `Uploader`가 대용량 파일의 Amazon S3 업로드 한도인 10,000파트에 도달할 수 있습니다. 10,000개 이상의 파트를 업로드하려고 하면 업로드가 중지되고 오류가 반환됩니다.

 `ReadSeekerAt` 유형을 구현하는 `body` 값의 경우, `Uploader`는 Amazon S3로 전송하기 전에 본문 콘텐츠를 메모리에 버퍼링하지 않습니다. `Uploader`는 파일을 Amazon S3에 업로드하기 전에 예상 파트 수를 계산합니다. `PartSize`의 현재 값에 파일을 업로드하는 데 10,000개 이상의 파트가 필요한 경우 `Uploader`는 필요한 파트 수가 적도록 파트 크기 값을 늘립니다.

#### 실패한 업로드 처리
<a name="handling-failed-uploads"></a>

 Amazon S3에 대한 업로드가 실패하면 기본적으로 `Uploader`는 Amazon S3 `AbortMultipartUpload` 작업을 사용하여 업로드된 파트를 제거합니다. 이 기능은 실패한 업로드가 Amazon S3 스토리지를 사용하지 않도록 합니다.

 `Uploader`가 성공적으로 업로드된 파트를 삭제하지 않도록 `LeavePartsOnError`를 true로 설정할 수 있습니다. 이는 부분적으로 완료된 업로드를 재개하는 데 유용합니다. 업로드된 파트에서 작동하려면 실패한 업로드의 `UploadID`를 가져와야 합니다. 다음 예제에서는 `manager.MultiUploadFailure` 오류 인터페이스 유형을 사용하여 `UploadID`를 가져오는 방법을 보여줍니다.

```
result, err := uploader.Upload(context.TODO(), &s3.PutObjectInput{
    Bucket: aws.String("{{amzn-s3-demo-bucket}}"),
    Key:    aws.String("{{my-object-key}}"),
    Body:   uploadFile,
})
output, err := u.upload(input)
if err != nil {
    var mu manager.MultiUploadFailure
    if errors.As(err, &mu) {
        // Process error and its associated uploadID
        fmt.Println("Error:", mu)
        _ = mu.UploadID() // retrieve the associated UploadID
    } else {
        // Process error generically
        fmt.Println("Error:", err.Error())
    }
    return
}
```

#### 업로드당 업로더 옵션 재정의
<a name="overriding-uploader-options-per-upload"></a>

 메서드에 하나 이상의 인수를 제공하여 `Upload`를 직접 호출할 때 `Uploader` 옵션을 재정의할 수 있습니다. 이러한 재정의는 동시성에 안전한 수정 사항이며 진행 중인 업로드 또는 후속 관리자 `Upload` 직접 호출에는 영향을 주지 않습니다. 예를 들어 특정 업로드 요청에 대한 `PartSize` 구성을 재정의하려면 다음을 수행합니다.

```
params := &s3.PutObjectInput{
    Bucket: aws.String("{{amzn-s3-demo-bucket}}"),
    Key:    aws.String("{{my-key}}"),
    Body:   myBody,
}
resp, err := uploader.Upload(context.TODO(), params, func(u *manager.Uploader) {
    u.PartSize = 10 * 1024 * 1024, // 10 MiB
})
```

#### 예제
<a name="examples"></a>

##### Amazon S3로 폴더 업로드
<a name="upload-a-folder-to-s3"></a>

 다음 예제에서는 `path/filepath` 패키지를 사용하여 파일 목록을 재귀적으로 수집하고 지정된 Amazon S3 버킷에 업로드합니다. Amazon S3 객체의 키에는 파일의 상대 경로 접두사가 붙습니다.

```
package main

import (
    "context"
    "log"
    "os"
    "path/filepath"

    "github.com/aws/aws-sdk-go-v2/aws"
    "github.com/aws/aws-sdk-go-v2/config"
    "github.com/aws/aws-sdk-go-v2/feature/s3/manager"
    "github.com/aws/aws-sdk-go-v2/service/s3"
)

var (
    localPath string
    bucket    string
    prefix    string
)

func init() {
    if len(os.Args) != 4 {
        log.Fatalln("Usage:", os.Args[0], "<local path> <bucket> <prefix>")
    }
    localPath = os.Args[1]
    bucket = os.Args[2]
    prefix = os.Args[3]
}

func main() {
    walker := make(fileWalk)
    go func() {
        // Gather the files to upload by walking the path recursively 
        if err := filepath.Walk(localPath, walker.Walk); err != nil {
            log.Fatalln("Walk failed:", err)
        }
        close(walker)
    }()

    cfg, err := config.LoadDefaultConfig(context.TODO())
    if err != nil {
        log.Fatalln("error:", err)
    }
    
    // For each file found walking, upload it to Amazon S3
    uploader := manager.NewUploader(s3.NewFromConfig(cfg))
    for path := range walker {
        rel, err := filepath.Rel(localPath, path)
        if err != nil {
            log.Fatalln("Unable to get relative path:", path, err)
        }
        file, err := os.Open(path)
        if err != nil {
            log.Println("Failed opening file", path, err)
            continue
        }
        defer file.Close()
        result, err := uploader.Upload(context.TODO(), &s3.PutObjectInput{
            Bucket: &bucket,
            Key:    aws.String(filepath.Join(prefix, rel)),
            Body:   file,
        })
        if err != nil {
            log.Fatalln("Failed to upload", path, err)
        }
        log.Println("Uploaded", path, result.Location)
    }
}

type fileWalk chan string

func (f fileWalk) Walk(path string, info os.FileInfo, err error) error {
    if err != nil {
        return err
    }
    if !info.IsDir() {
        f <- path
    }
    return nil
}
```

### Download Manager
<a name="download-manager"></a>

 Amazon S3 [Downloader](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/feature/s3/manager#Downloader) Manager는 파일을 더 작은 파트로 분할하고 병렬로 다운로드할 수 있는지 여부를 결정합니다. 병렬 다운로드 수와 다운로드된 파트의 크기를 사용자 지정할 수 있습니다.

#### 예제: 파일 다운로드
<a name="example-download-a-file"></a>

 다음 예제에서는 Amazon S3 `Downloader`를 사용하여 파일을 다운로드합니다. `Downloader` 사용 방법은 [s3.GetObject](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/s3#Client.GetObject) 작업과 비슷합니다.

```
import "context"
import "github.com/aws/aws-sdk-go-v2/aws"
import "github.com/aws/aws-sdk-go-v2/config"
import "github.com/aws/aws-sdk-go-v2/service/s3"
import "github.com/aws/aws-sdk-go-v2/feature/s3/manager"

// ...

cfg, err := config.LoadDefaultConfig(context.TODO())
if err != nil {
    log.Println("error:", err)
    return
}

client := s3.NewFromConfig(cfg)

downloader := manager.NewDownloader(client)
numBytes, err := downloader.Download(context.TODO(), downloadFile, &s3.GetObjectInput{
    Bucket: aws.String("{{amzn-s3-demo-bucket}}"), 
    Key:    aws.String("{{my-key}}"),
})
```

 `downloadFile` 파라미터는 `io.WriterAt` 유형입니다. `WriterAt` 인터페이스를 사용하면 `Downloader`가 파일의 여러 파트를 병렬로 쓸 수 있습니다.

#### 구성 옵션
<a name="configuration-options-1"></a>

 `Downloader` 인스턴스를 인스턴스화할 때 구성 옵션을 지정하여 객체 다운로드 방법을 사용자 지정할 수 있습니다.
+  `PartSize` - 다운로드할 각 파트의 버퍼 크기를 바이트 단위로 지정합니다. 파트당 최소 크기는 5MB입니다.
+  `Concurrency` - 병렬로 다운로드할 파트의 수를 지정합니다.

 `Concurrency` 값은 주어진 `Download` 직접 호출에 대해 발생할 수 있는 동시 파트 다운로드 수를 제한합니다. 이는 글로벌 클라이언트 동시성 제한이 아닙니다. `PartSize` 및 `Concurrency` 구성 값을 조정하여 최적의 구성을 찾습니다. 예를 들어, 연결 대역폭이 높은 시스템은 더 큰 파트와 더 많은 다운로드를 병렬로 수신할 수 있습니다.

 예를 들어, 애플리케이션에서 `Downloader`의 `Concurrency`를 `5`로 구성합니다. 그러면 애플리케이션이 서로 다른 두 개의 고루틴에서 `Download`를 직접 호출하고 결과는 `10`개의 동시 파트 다운로드(고루틴 2 \* `Concurrency` 5)가 됩니다.

**주의**  
 애플리케이션 리소스 소진을 방지하기 위해 애플리케이션은 동시 직접 호출을 `Download`로 제한해야 합니다.

 `Downloader` 및 기타 구성 옵션에 대한 자세한 내용은 AWS SDK for Go API 참조의 [manager.Downloader](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/feature/s3/manager/#Downloader)를 참조하세요.

#### 다운로드당 다운로더 옵션 재정의
<a name="overriding-downloader-options-per-download"></a>

 메서드에 하나 이상의 함수 인수를 제공하여 `Download`를 직접 호출할 때 `Downloader` 옵션을 재정의할 수 있습니다. 이러한 재정의는 동시성에 안전한 수정 사항이며 진행 중인 업로드 또는 후속 관리자 `Download` 직접 호출에는 영향을 주지 않습니다. 예를 들어 특정 업로드 요청에 대한 `PartSize` 구성을 재정의하려면 다음을 수행합니다.

```
params := &s3.GetObjectInput{
    Bucket: aws.String("{{amzn-s3-demo-bucket}}"),
    Key:    aws.String("{{my-key}}"),
}
resp, err := downloader.Download(context.TODO(), targetWriter, params, func(u *manager.Downloader) {
    u.PartSize = 10 * 1024 * 1024, // 10 MiB
})
```

##### 예제
<a name="examples-1"></a>

##### 버킷의 모든 객체 다운로드
<a name="download-all-objects-in-a-bucket"></a>

 다음 예제에서는 페이지 매김을 사용하여 Amazon S3 버킷에서 객체 목록을 수집합니다. 그런 다음 각 객체를 로컬 파일로 다운로드합니다.

```
package main

import (
    "context"
    "fmt"
    "log"
    "os"
    "path/filepath"

    "github.com/aws/aws-sdk-go-v2/aws"
    "github.com/aws/aws-sdk-go-v2/config"
    "github.com/aws/aws-sdk-go-v2/feature/s3/manager"
    "github.com/aws/aws-sdk-go-v2/service/s3"
)

var (
    Bucket         = "{{amzn-s3-demo-bucket}}" // Download from this bucket
    Prefix         = "logs/"    // Using this key prefix
    LocalDirectory = "s3logs"   // Into this directory
)

func main() {
    cfg, err := config.LoadDefaultConfig(context.TODO())
    if err != nil {
        log.Fatalln("error:", err)
    }

    client := s3.NewFromConfig(cfg)
    manager := manager.NewDownloader(client)

    paginator := s3.NewListObjectsV2Paginator(client, &s3.ListObjectsV2Input{
        Bucket: &Bucket,
        Prefix: &Prefix,
    })

    for paginator.HasMorePages() {
        page, err := paginator.NextPage(context.TODO())
        if err != nil {
            log.Fatalln("error:", err)
        }
        for _, obj := range page.Contents {
            if err := downloadToFile(manager, LocalDirectory, Bucket, aws.ToString(obj.Key)); err != nil {
                log.Fatalln("error:", err)
            }
        }
    }
}

func downloadToFile(downloader *manager.Downloader, targetDirectory, bucket, key string) error {
    // Create the directories in the path
    file := filepath.Join(targetDirectory, key)
    if err := os.MkdirAll(filepath.Dir(file), 0775); err != nil {
        return err
    }

    // Set up the local file
    fd, err := os.Create(file)
    if err != nil {
        return err
    }
    defer fd.Close()

    // Download the file using the AWS SDK for Go
    fmt.Printf("Downloading s3://%s/%s to %s...\n", bucket, key, file)
    _, err = downloader.Download(context.TODO(), fd, &s3.GetObjectInput{Bucket: &bucket, Key: &key})

    return err
}
```

### GetBucketRegion
<a name="getbucketregion"></a>

 [GetBucketRegion](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/feature/s3/manager#GetBucketRegion)은 Amazon S3 버킷의 AWS 리전 위치를 결정하는 유틸리티 함수입니다. `GetBucketRegion`는 Amazon S3 클라이언트를 가져와 클라이언트의 구성된 리전과 연결된 AWS 파티션 내에서 요청된 버킷의 위치를 결정하는 데 사용합니다.

 예를 들어 버킷 `{{amzn-s3-demo-bucket}}`의 리전을 찾으려면 다음을 수행합니다.

```
cfg, err := config.LoadDefaultConfig(context.TODO())
if err != nil {
    log.Println("error:", err)
    return
}

bucket := "{{amzn-s3-demo-bucket}}"
region, err := manager.GetBucketRegion(ctx, s3.NewFromConfig(cfg), bucket)
if err != nil {
    var bnf manager.BucketNotFound
    if errors.As(err, &bnf) {
        log.Printf("unable to find bucket %s's Region\n", bucket)
    } else {
        log.Println("error:", err)
    }
    return
}
fmt.Printf("Bucket %s is in %s region\n", bucket, region)
```

 `GetBucketRegion`이 버킷의 위치를 확인할 수 없는 경우 함수는 예제와 같이 [BucketNotFound](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/feature/s3/manager#BucketNotFound) 오류 유형을 반환합니다.

## 확인할 수 없는 스트리밍 입력
<a name="unseekable-streaming-input"></a>

 `PutObject` 및 `UploadPart`와 같은 API 작업의 경우 Amazon S3 클라이언트는 `Body` 입력 파라미터의 값이 기본적으로 [io.Seeker](https://pkg.go.dev/io#Seeker) 인터페이스를 구현할 것으로 예상합니다. 클라이언트는 `io.Seeker` 인터페이스를 사용하여 업로드할 값의 길이를 결정하고 [요청 서명](https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-authenticating-requests.html)에 대한 페이로드 해시를 계산합니다. `Body` 입력 파라미터 값이 `io.Seeker`를 구현하지 않으면 애플리케이션에 오류가 발생합니다.

```
operation error S3: PutObject, failed to compute payload hash: failed to seek
body to start, request stream is not seekable
```

 기능 옵션을 사용하여 작업 메서드의 [미들웨어](middleware.md)를 수정하여 이 동작을 변경할 수 있습니다. [WithAPIOptions](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/s3#WithAPIOptions) 헬퍼는 0개 이상의 미들웨어 변형기에 대한 기능 옵션을 반환합니다. 클라이언트가 페이로드 해시를 계산하지 않도록 하고 [서명되지 않은 페이로드](https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-header-based-auth.html) 요청 서명을 사용하려면 [v4.SwapComputePayloadSHA256ForUnsignedPayloadMiddleware](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/aws/signer/v4#SwapComputePayloadSHA256ForUnsignedPayloadMiddleware)를 추가합니다.

```
resp, err := client.PutObject(context.TODO(), &s3.PutObjectInput{
    Bucket: &bucketName,
    Key: &objectName,
    Body: bytes.NewBuffer([]byte(`example object!`)),
    ContentLength: 15, // length of body
}, s3.WithAPIOptions(
    v4.SwapComputePayloadSHA256ForUnsignedPayloadMiddleware,
))
```

**주의**  
 Amazon S3에서는 버킷에 업로드된 모든 객체에 콘텐츠 길이를 제공해야 합니다. `Body` 입력 파라미터는 `io.Seeker` 인터페이스를 구현하지 않으므로 클라이언트는 요청에 대한 `ContentLength` 파라미터를 계산할 수 없습니다. 파라미터는 애플리케이션에서 제공해야 합니다. `ContentLength` 파라미터가 제공되지 않으면 요청이 실패합니다.  
 탐색할 수 없고 알려진 길이가 없는 업로드에는 SDK의 [Amazon S3 Upload Manager](#s3-upload-manager)를 사용합니다.