本記事は
【Advent Calendar 2025】
20日目の記事です。
🌟🎄
19日目
▶▶ 本記事 ▶▶
21日目
🎅🎁
はじめに
こんにちは、西本です。入社してもう5年目になるのか、、と毎年のように時の流れの速さに驚いていますが日々奮闘しています。
今回のテーマ
早速ですが、AWS CLIにおけるs3 syncの同期トリガー条件について見ていければと思います。いくつか既に公開されている記事もありますが、私が検証してみて気づいた点についてまとめられればと思います。
AWS CLIにおけるs3 cpとs3 syncの概要
syncと似た処理として、cpがあるかと思います。公式ドキュメントは下記になります。
- s3 cpの公式ドキュメント: cp — AWS CLI 2.32.17 Command Reference
- s3 syncの公式ドキュメント: sync — AWS CLI 2.32.17 Command Reference
どちらもコピー処理ではありますが、細かい動作が実は違っています。そのため、コピーする際に「s3 cp、s3 sync別にどっちでもいいんじゃない?」というのは結果的には問題ないこともありますが、仕様を理解しておかないと本来やりたかったコピーが実はできていない、といったことになりえます。本記事では、s3 syncの同期トリガー条件について深掘りできればと思います。
s3 cp並びにs3 sync間のコピー(同期処理)のトリガー条件
仕様差異
- s3 cp
除外オプション(--exclude)を付与しない限り、基本的にコピーが行われます。また、同一ファイルがターゲットバケットにあったとしても上書きされることになります。
- s3 sync
ソースバケット内のコンテンツに、更新があった場合に限りコピーが行われます。またいくつかの制約事項がありますが、以降の検証結果を見ていければと思います。
検証してみた
実行例①:s3 cp処理の基本動作を確認してみる
ターゲットバケットにコンテンツが無い状態で、s3 cp処理を実施します。(--recursiveは再帰的にコピーを行うためのオプション)
~ $ aws s3 cp s3://blog-demo-source-s3/ s3://blog-demo-target-s3/ --recursive
copy: s3://blog-demo-source-s3/blog-demo-data-b.txt to s3://blog-demo-target-s3/blog-demo-data-b.txt
copy: s3://blog-demo-source-s3/blog-demo-data-c.txt to s3://blog-demo-target-s3/blog-demo-data-c.txt
copy: s3://blog-demo-source-s3/blog-demo-data-a.txt to s3://blog-demo-target-s3/blog-demo-data-a.txt
当然ですが、コピー処理は成功しました。ターゲットバケット内のコンテンツの最終更新日は下記の添付の通りです。

この状態で、再度更新の入っていないコンテンツのコピー処理を行います。
~ $ aws s3 cp s3://blog-demo-source-s3/ s3://blog-demo-target-s3/ --recursive
copy: s3://blog-demo-source-s3/blog-demo-data-b.txt to s3://blog-demo-target-s3/blog-demo-data-b.txt
copy: s3://blog-demo-source-s3/blog-demo-data-c.txt to s3://blog-demo-target-s3/blog-demo-data-c.txt
copy: s3://blog-demo-source-s3/blog-demo-data-a.txt to s3://blog-demo-target-s3/blog-demo-data-a.txt
実行結果は変わらず、copy処理が行われました。ターゲットバケット内のコンテンツの最終更新日は下記の添付の通りです。

実行例②:s3 sync処理の基本動作を確認してみる
ターゲットバケットにコンテンツが無い状態で、s3 sync処理を実施します。
~ $ aws s3 sync s3://blog-demo-source-s3/ s3://blog-demo-target-s3/
copy: s3://blog-demo-source-s3/blog-demo-data-a.txt to s3://blog-demo-target-s3/blog-demo-data-a.txt
copy: s3://blog-demo-source-s3/blog-demo-data-c.txt to s3://blog-demo-target-s3/blog-demo-data-c.txt
copy: s3://blog-demo-source-s3/blog-demo-data-b.txt to s3://blog-demo-target-s3/blog-demo-data-b.txt
当然ですが、コピー処理が行われています。(出力結果はcpと同様ですね)

この状態で、再度更新の入っていないコンテンツのコピー処理をcpの時と同様に行います。
~ $ aws s3 sync s3://blog-demo-source-s3/ s3://blog-demo-target-s3/
結果の応答がありませんでした。念のため、ターゲットバケット内のコンテンツを確認します。

このようにsync処理では、ソースコンテンツに更新があって初めてコピー(同期)されることがわかります。
実行例③:更新後のオブジェクトサイズに変化がない場合のs3 sync処理の動作について確認してみる
ソースバケットのコンテンツを同じサイズのコンテンツで更新し、sync処理を行います。
~ $ aws s3 sync s3://blog-demo-source-s3/ s3://blog-demo-target-s3/
copy: s3://blog-demo-source-s3/blog-demo-data-a.txt to s3://blog-demo-target-s3/blog-demo-data-a.txt
copy: s3://blog-demo-source-s3/blog-demo-data-c.txt to s3://blog-demo-target-s3/blog-demo-data-c.txt
copy: s3://blog-demo-source-s3/blog-demo-data-b.txt to s3://blog-demo-target-s3/blog-demo-data-b.txt
コピーが正常に行われて、ターゲットバケット内のコンテンツも更新されていることを確認できました。
実行例④:実行例②、③についてターゲットをCloudShellのローカルストレージに変更して試してみる
実行例②、③のターゲットをCloudShell上のローカルストレージ向きにやってみます。 1回目の実行は当然コピー処理が正常に行われました。
~ $ aws s3 sync s3://blog-demo-source-s3/ ./
download: s3://blog-demo-source-s3/blog-demo-data-c.txt to ./blog-demo-data-c.txt
download: s3://blog-demo-source-s3/blog-demo-data-a.txt to ./blog-demo-data-a.txt
download: s3://blog-demo-source-s3/blog-demo-data-b.txt to ./blog-demo-data-b.txt
ソースバケットのコンテンツを更新(サイズは同じ)後、再度s3 sync処理を行います。
~ $ aws s3 sync s3://blog-demo-source-s3/ ./
結果としては検証③とは異なり同期されませんでした。こちらは仕様上更新後のコンテンツのサイズが同じ場合、差分として認識されず同期されないことが原因となります。そのため「--exact-timestamps」オプションを付与し、タイムスタンプが異なっている場合はオブジェクトサイズが同じでもコピーされるようにする必要があります。
~ $ aws s3 sync s3://blog-demo-source-s3/ ./ --exact-timestamps
download: s3://blog-demo-source-s3/blog-demo-data-a.txt to ./blog-demo-data-a.txt
download: s3://blog-demo-source-s3/blog-demo-data-c.txt to ./blog-demo-data-c.txt
download: s3://blog-demo-source-s3/blog-demo-data-b.txt to ./blog-demo-data-b.txt
syncを使用する場合は「--exact-timestamps」が必要か必ず確認するようにしましょう。 ※ ただ、S3 - S3の場合は更新後のオブジェクトサイズが同じでも同期されるんですね。。こちらの理由については後半で整理しています。
実行例⑤:ターゲット側が更新された場合のs3 sync処理の動作を確認してみる
ソースバケット - ターゲットバケット間で同期がとれている状態で、ターゲット側のバケットを更新し、sync処理を行います。 ※ターゲットバケットの更新日時がソースバケットの更新日時より新しい状態になっている状態になります。
〇ソースバケット

〇ターゲットバケット

~ $ aws s3 sync s3://blog-demo-source-s3/ ./
結果としては、同期されませんでした。ソースバケットの更新日時がターゲットバケットの更新日時より新しい必要があるようです。
実行例⑥:実行例⑤についてターゲットをCloudShellのローカルストレージに変更して試してみる
実行例⑤の作業をS3 - CloudShellローカルストレージ間でやってみます。
〇ソースバケット

〇CloudShellローカルストレージ
~ $ ls -la
total 56
drwxrwxrwx. 5 cloudshell-user cloudshell-user 4096 Dec 16 11:30 .
drwxr-xr-x. 3 root root 4096 Dec 16 10:42 ..
-rw-r--r--. 1 cloudshell-user cloudshell-user 5 Dec 16 11:29 blog-demo-data-a.txt
-rw-r--r--. 1 cloudshell-user cloudshell-user 5 Dec 16 11:29 blog-demo-data-b.txt
-rw-r--r--. 1 cloudshell-user cloudshell-user 5 Dec 16 11:30 blog-demo-data-c.txt
更新日時がCloudShellローカルストレージの方が新しい状態で、s3 syncしてみます。
~ $ aws s3 sync s3://blog-demo-source-s3/ ./
download: s3://blog-demo-source-s3/blog-demo-data-c.txt to ./blog-demo-data-c.txt
download: s3://blog-demo-source-s3/blog-demo-data-b.txt to ./blog-demo-data-b.txt
download: s3://blog-demo-source-s3/blog-demo-data-a.txt to ./blog-demo-data-a.txt
~ $ ls -la
total 56
drwxrwxrwx. 5 cloudshell-user cloudshell-user 4096 Dec 16 11:31 .
drwxr-xr-x. 3 root root 4096 Dec 16 10:42 ..
-rw-r--r--. 1 cloudshell-user cloudshell-user 4 Dec 16 11:09 blog-demo-data-a.txt
-rw-r--r--. 1 cloudshell-user cloudshell-user 4 Dec 16 11:09 blog-demo-data-b.txt
-rw-r--r--. 1 cloudshell-user cloudshell-user 4 Dec 16 11:09 blog-demo-data-c.txt
同期処理が行われ、更新日時がソースバケットと同様になったことがわかります。(当然中身もソースバケット内のコンテンツ内容となっています)
このようにデータのターゲットがS3なのか、それ以外(サーバのローカルストレージなど)でもs3 sync処理においては動作が大きく変わることがわかります。
更新後のオブジェクトサイズが同じ場合の動作として、ターゲットがS3かS3以外かで挙動が変わった理由
S3 - S3とS3 - S3以外(サーバのローカルストレージなど)で、syncの動作が異なるのは時刻に関する判定基準がそれぞれ下記となっているからでした。
S3 - S3:ソースバケットにあるオブジェクトの更新日時がターゲットバケットよりも新しい場合は同期がされる
S3 - S3以外(サーバのローカルストレージなど):デフォルトでは、ローカルにある対象オブジェクトの更新日時 (last modified time) がソースバケットより新しい場合のみ、同期がされる
といったようにファイル更新後のサイズが同じ場合の動作としてターゲット先によってもトリガー判定基準が異なることがわかりました。オブジェクトのサイズや更新日時のチェックはどちらのパターンもされているものの、判定基準が異なる点に注意が必要と認識できました。
s3 syncの「exact-timestamps」のオプション説明にも下記の記載がありました。
When syncing from S3 to local, same-sized items will be ignored only when the timestamps match exactly. The default behavior is to ignore same-sized items unless the local version is newer than the S3 version.
オブジェクトサイズが同じ場合同期がされないので、オプションが必要というくらいの認識をしていましたが、今回の検証で深く仕様について理解ができました。
まとめ
syncにおいて、サイズの制約など一定の条件があることは認識していましたが同期先でトリガー条件が異なるなど、検証してみてわかったこともありました。同期処理においては、cpやsyncの他S3の機能でのレプリケーションなど様々な手段があるかと思いますが各仕様を理解して実装していければと思います。ここまで読んでいただき、ありがとうございました。