AWS CLI s3コマンドのFilterオプションを深掘りしてみた

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

清水です。先日以下のエントリで、AWS CLIのs3コマンドの--exclude、--includeのFilterオプションを使ってみました。

エントリ公開の後、社内の方から「S3のAPI自体には--exclude--includeに相当しているオプションはないので、prefixパラメータで指定したものをローカルで処理しているのでは?」という見解をいただきました。
こちらのS3 API Referenceにその旨が記載されています)

この点をAWS CLIのデバッグログを使って確認してみましたのでまとめてみます。
結論から先に述べますと、社内の方からのご指摘どおりで、--exclude、--includeオプションを指定した場合、APIとしてはprefixのパラメタ付きで実行、そこからローカルで--exclude、--includeそれぞれの条件にマッチするか確認する処理(awscli.customizations.s3.filters)が実行されていました。

そのため、--exclude、--includeを付けた処理を行う場合、s3://で始まる部分(以降、本エントリではこの部分をS3Uriと記載しています)をなるべく深めに(長めに)設定して、条件にマッチするか確認するFilter処理を少なくすることで効率的に実行ができると考えられます。

確認してみた

それでは実際に確認してみたことをまとめてみます。
確認方法としては、aws s3 cpコマンドを行う際に--debugオプションを付けてデバッグログを有効にして、このログから実際にどのようなAPIを投げているかを表示してみました。

この--debugオプション、相当量なデータが出力されることもあり、わかりやすくするためにS3側に以下のようなシンプルなデータを用意して、これをローカルにコピーすることで確認してみました。

s3://mybucket/
└── logs
    ├── cloudfront
    │   └── log1.txt
    ├── elb
    │   ├── log1.txt
    │   └── log2.txt
    └── s3
        ├── log1.txt
        ├── log2.txt
        └── log3.txt

方法として、 S3Uriを長く(深く)した場合と短く(浅く)した場合、2つのデバッグログを比較してみました。

S3Uriを長くした場合は下記のコマンドの実行、

 $ aws s3 cp s3://mybucket/logs/s3/ . --recursive --exclude "*" --include "*.txt" --debug 

S3Uriを短くした場合は次のコマンドの実行になります。

 $ aws s3 cp s3://mybucket/ . --recursive --exclude "*" --include "logs/s3/*.txt" --debug

S3Uriを長くした場合

まずはS3Uriを長くした場合です。

 $ aws s3 cp s3://mybucket/logs/s3/ . --recursive --exclude "*" --include "*.txt" --debug

--debugオプションをつけたデバッグログはとても長くなるので、すべての表示は割愛しますが、注目点をまとめてみます。

まず、対象バケットmybucketに対して、prefix=logs/s3/付きでListBucketのリクエストを投げています。

2016-09-26 20:45:53,675 - MainThread - botocore.endpoint - DEBUG - Making request for <botocore.model.OperationModel object at 0x123456789> (verify_ssl=True) with params: {'headers': {'User-Agent': 'aws-cli/1.10.17 Python/3.5.0 Darwin/15.6.0 botocore/1.4.8'}, 'query_string': {'encoding-type': 'url', 'prefix': 'logs/s3/'}, 'url': 'https://s3-ap-northeast-1.amazonaws.com/mybucket?encoding-type=url&prefix=logs%2Fs3%2F', 'body': b'', 'method': 'GET', 'url_path': '/mybucket'}
2016-09-26 20:45:53,676 - MainThread - botocore.hooks - DEBUG - Event request-created.s3.ListObjects: calling handler <bound method RequestSigner.handler of <botocore.signers.RequestSigner object at 0x123456789>>

このレスポンスを見やすいように整形すると以下のようになっていました。(一部情報は割愛しています)

<?xml version="1.0" encoding="UTF-8"?>
<ListBucketResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
  <Name>mybucket</Name>
  <Prefix>logs/s3/</Prefix>
  <Marker></Marker>
  <MaxKeys>1000</MaxKeys>
  <EncodingType>url</EncodingType>
  <IsTruncated>false</IsTruncated>
  <Contents>
    <Key>logs/s3/log1.txt</Key>
    <LastModified>2016-09-26T11:43:43.000Z</LastModified>
    <Size>4</Size>
    <StorageClass>STANDARD</StorageClass>
  </Contents>
  <Contents>
    <Key>logs/s3/log2.txt</Key>
    <LastModified>2016-09-26T11:43:43.000Z</LastModified>
    <Size>4</Size>
    <StorageClass>STANDARD</StorageClass>
  </Contents>
  <Contents>
    <Key>logs/s3/log3.txt</Key>
    <LastModified>2016-09-26T11:43:44.000Z</LastModified>
    <Size>4</Size>
    <StorageClass>STANDARD</StorageClass>
  </Contents>
</ListBucketResult>

mybucket内のlogs/s3/配下のファイル(キー)についての情報、合計3つのContents要素が含まれる ことがわかります。

その後、awscli.customizations.s3.filtersと記されているところがありました。 こちらも抜粋したものを下記に記載します。
仕様など確認しておらず、ログメッセージからの推測ですが、keyについて、filter(exclude, include)にそれぞれmatchしているかの確認が行われ、最終的にshould_includeに操作対象に含むか含まないかの情報を持たせていることが考えられます。

2016-09-26 20:45:55,252 - MainThread - awscli.customizations.s3.filters - DEBUG - mybucket/logs/s3/log1.txt matched exclude filter: mybucket/logs/s3/*
2016-09-26 20:45:55,252 - MainThread - awscli.customizations.s3.filters - DEBUG - mybucket/logs/s3/log1.txt did not match clude filter: /Users/shimizu.toshiya/local/path/*
2016-09-26 20:45:55,253 - MainThread - awscli.customizations.s3.filters - DEBUG - mybucket/logs/s3/log1.txt matched include filter: mybucket/logs/s3/*.txt
2016-09-26 20:45:55,253 - MainThread - awscli.customizations.s3.filters - DEBUG - mybucket/logs/s3/log1.txt did not match clude filter: /Users/shimizu.toshiya/local/path/*.txt
2016-09-26 20:45:55,253 - MainThread - awscli.customizations.s3.filters - DEBUG - =mybucket/logs/s3/log1.txt final filtered status, should_include: True

なお、このawscli.customizations.s3.filtersについては合計15個の出力がありました。 内訳は、ファイル(キー)3つに対して、出力がそれぞれ5つずつとなります。

S3Uriを短くした場合

続いてS3Uriを短くした場合です。S3Uriにはバケット名だけを指定して、--includeオプションで詳細なパス(キー)を指定しています。

$ aws s3 cp s3://mybucket/ . --recursive --exclude "*" --include "logs/s3/*.txt" --debug

対象バケットに対してListBucketのリクエストを投げていますが、prefixパラメタについては何も指定していません。

2016-09-26 20:48:39,866 - MainThread - botocore.endpoint - DEBUG - Making request for <botocore.model.OperationModel object at 0x123456789> (verify_ssl=True) with params: {'url': 'https://s3-ap-northeast-1.amazonaws.com/mybucket?prefix=&encoding-type=url', 'body': b'', 'headers': {'User-Agent': 'aws-cli/1.10.17 Python/3.5.0 Darwin/15.6.0 botocore/1.4.8'}, 'url_path': '/mybucket', 'method': 'GET', 'query_string': {'prefix': '', 'encoding-type': 'url'}}
2016-09-26 20:48:39,866 - MainThread - botocore.hooks - DEBUG - Event request-created.s3.ListObjects: calling handler <bound method RequestSigner.handler of <botocore.signers.RequestSigner object at 0x123456789>>

レスポンスを見やすいように整形すると下記となります。(こちらも一部情報は割愛しています)

<?xml version="1.0" encoding="UTF-8"?>
<ListBucketResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
  <Name>mybucket</Name>
  <Prefix></Prefix>
  <Marker></Marker>
  <MaxKeys>1000</MaxKeys>
  <EncodingType>url</EncodingType>
  <IsTruncated>false</IsTruncated>
  <Contents>
    <Key>logs/cloudfront/log1.txt</Key>
    <LastModified>2016-09-26T11:43:44.000Z</LastModified>
    <Size>4</Size>
    <StorageClass>STANDARD</StorageClass>
  </Contents>
  <Contents>
    <Key>logs/elb/log1.txt</Key>
    <LastModified>2016-09-26T11:43:43.000Z</LastModified>
    <Size>4</Size>
    <StorageClass>STANDARD</StorageClass>
  </Contents>
  <Contents>
    <Key>logs/elb/log2.txt</Key>
    <LastModified>2016-09-26T11:43:44.000Z</LastModified>
    <Size>4</Size>
    <StorageClass>STANDARD</StorageClass>
  </Contents>
  <Contents>
    <Key>logs/s3/log1.txt</Key>
    <LastModified>2016-09-26T11:43:43.000Z</LastModified>
    <Size>4</Size>
    <StorageClass>STANDARD</StorageClass>
  </Contents>
  <Contents>
    <Key>logs/s3/log2.txt</Key>
    <LastModified>2016-09-26T11:43:43.000Z</LastModified>
    <Size>4</Size>
    <StorageClass>STANDARD</StorageClass>
  </Contents>
  <Contents>
    <Key>logs/s3/log3.txt</Key>
    <LastModified>2016-09-26T11:43:44.000Z</LastModified>
    <Size>4</Size>
    <StorageClass>STANDARD</StorageClass>
  </Contents>
</ListBucketResult>

mybucket内のすべてのファイル(キー)についての情報、合計6つのContents要素が含まれることがわかります。

そのあと、awscli.customizations.s3.filtersと記されているところがありました。 こちらも一部を抜粋します。

2016-09-26 20:48:41,481 - MainThread - awscli.customizations.s3.filters - DEBUG - mybucket/logs/cloudfront/log1.txt matched exclude filter: mybucket/*
2016-09-26 20:48:41,482 - MainThread - awscli.customizations.s3.filters - DEBUG - mybucket/logs/cloudfront/log1.txt did not match clude filter: /Users/shimizu.toshiya/local/path/*
2016-09-26 20:48:41,482 - MainThread - awscli.customizations.s3.filters - DEBUG - mybucket/logs/cloudfront/log1.txt did not match clude filter: mybucket/logs/s3/*.txt
2016-09-26 20:48:41,482 - MainThread - awscli.customizations.s3.filters - DEBUG - mybucket/logs/cloudfront/log1.txt did not match clude filter: /Users/shimizu.toshiya/local/path/logs/s3/*.txt
2016-09-26 20:48:41,482 - MainThread - awscli.customizations.s3.filters - DEBUG - =mybucket/logs/cloudfront/log1.txt final filtered status, should_include: False

内容自体はS3Uriを長くした場合と大差ありませんが、注目すべきはこの出力の回数です。 S3Uriを長くした場合が15個の出力だったのに対して、S3Uriを短くした場合は30個の出力でした。 内訳はファイル(キー)6つに対して、出力は5つずつとなり、ListBucketの結果に含まれるキーの数がそのまま影響しているかたちです。

以上から、S3Uriを短くするとListBucketの結果が多くなり、ローカル側でFilter処理する対象が増えることが確認できました。

まとめ

AWS CLI s3コマンドのFilter機能を深掘りして、--exclude--includeを実行時にどのような処理が行われているかを確認してみました。 AWS CLIのデバッグログを完璧に把握しているわけではないのですが、動作のイメージがつかめました。

きっかけとなったのは元エントリの、特に以下の2つの書き方でした。

aws s3 cp s3://bucket/logs/elb/AWSLogs/123456789012/elasticloadbalancing/ap-northeast-1/2016/09/24/ . --recursive --exclude "*" --include "*.log"
aws s3 cp s3://bucket/ . --recursive --exclude "*" --include "logs/elb/AWSLogs/*/elasticloadbalancing/ap-northeast-1/2016/09/24/*.log"

私は従来から上のS3Uriを長くする方法で書いていたのですが、下の書き方をするとAWSアカウントIDの部分が省略できるな、ぐらいでこのような書き方をしてみました。 実際、AWSアカウントIDの部分を省略することはできますが、本エントリで確認したとおり、処理するローカルクライアント側での処理が莫大になる可能性がありますので、この場合は一度AWSアカウントIDを確認後、指定して実行したほうが効率的かと考えています。

コマンド形式など通常のコマンドと似ており、とっつきやすく容易に使用できるAWS CLI s3コマンドですが、どのように動いているか少し気にしてみると、より高度な使い方ができるのではないでしょうか。