S3に完全に同じファイルが上がってるか確認するロジックでちょっとハマったのでメモ。

S3上のファイルのチェックサムを取得する方法でハマりやすいとこと、下の方におまけでJavaでfileのチェックサムを計算する方法を書いておきました。

誰かの役に立てば幸いです。

S3のチェックサムを比較したい

数GBのファイルをS3に挙げるような処理を書いていて、後続処理の失敗などでリトライしたいというのがあった。

この場合、ファイルをアップロードし直すのはムダだが、移行元ストレージ内の対象ファイルが変更にされている可能性もある。 したがって、すでにS3上に完全に同じファイルがあるときだけアップロードをリトライしたい。

この場合、チェックサムを比較するのが楽なので、S3のgetObjectMetaData APIを利用して以下のようにコードを書いた。

 private boolean shouldSkip(String bucketName, String key, String md5CheckSum) {
      try {
        ObjectMetadata meta = s3Client.getObjectMetadata(bucketName, key);
        if (meta == null || meta.getContentMD5() == null) {
          log.info("meta data not exist for the file {} in bucket {}", key, bucketName);
          return false;
        }
        log.info(
            "Checksum of existing file is {} and present file checksum is {}",
            meta.getContentMD5(),
            md5CheckSum);
        return meta.getContentMD5().equals(md5CheckSum);
      } catch (SdkClientException e) {
        log.error("Exception thrown while validating the checksum of the file {}", key, e);
        return false;
      }
    }

しかし、どうもうまく行かない。 ObjectMetaData#contentMD5がどうしてもNullになってしまうのだ。

調べたところ、S3における既存オブジェクトのチェックサムはcontentMD5ではなく、Etagに付与されるらしい。

じゃあcontentMD5は何に使うかと言うと、更新時にS3内で元のファイルから改ざん確認に使うため、getで取得する時は返されないのだとか。

したがって、S3から落として来たファイルのチェックサムを知りたい時はEtagと比較する必要がある。

こんな感じ。

    private boolean shouldSkip(String bucketName, String key, String md5CheckSum) {
      try {
        ObjectMetadata meta = this.s3Client.getObjectMetadata(bucketName, key);
        if (meta == null || meta.getETag() == null) {
          log.info("meta data not exist for the file {} in bucket {}", key, bucketName);
          return false;
        }
        log.info(
            "Checksum of existing file is {} and present file checksum is {}",
            meta.getETag(),
            md5CheckSum);
        return meta.getETag().equals(md5CheckSum);
      } catch (SdkClientException e) {
        log.error("Exception thrown while validating the checksum of the file {}", key, e);
        return false;
      }
    }

誰かの役に立てば幸いです。

おまけ Javaでのチェックサム確認方法

チェックサムでググって飛んで来た人のために一応書いておくと、Javaでチェックサムを計算する方法はこんな感じ。

  public static String checkMd5Checksum(File file) {
    try (BufferedInputStream is = new BufferedInputStream(new FileInputStream(file))) {
      return DigestUtils.md5Hex(is);
    } catch (Exception e) {
      // Not likely to occur.
      log.error(
          "ERROR Happened while calculating the check sum for file {}", file.getAbsolutePath(), e);
      return "NOT FOUND";
    }
  }

sha256なら DigestUtils#md5HexDigestUtils#sha256Hexに変えるだけ。

チェックサムに関してもっと知りたい人は世界でもっとも強力な9のアルゴリズムを読もう。

簡単に鍵交換やチェックサムの理論を紹介していて、読み物としてはとても面白いよ。

以上、メモでした。