WEARの画像アップロード機能リプレイスによるパフォーマンスと運用保守の効率化

ogp

こんにちは、WEAR部 運用改善チームの三浦です。普段は WEAR の運用改善を行っていますが、最近は新規プロジェクトの開発にも携わっています。

本記事では、WEARのS3への画像アップロード機能をインフラ・バックエンド両面からリプレイスを行い、パフォーマンスの向上と安全かつ効率的に運用保守を行えるよう改善をした事例を紹介します。

背景

現在取り組んでいる新規プロジェクトで、WEARの外部連携用APIを通してWEARへコーデ投稿をできる機能を作ることになりました。WEARのコーデ画像はAmazon S3で管理しており、今回作成するコーデ投稿機能でもWEARのバケットに対して画像をアップロードする必要があります。しかし、現状の画像アップロードの仕組みには様々な課題がありました。

その仕組みと課題の概要を説明します。

現状の画像アップロード機能の仕組み

WEARの現状の画像アップロードの仕組みは以下の通りです。

  1. WEARのアプリからAPIを呼び出し、WEARサーバーに画像アップロード
  2. APIからS3バケットへ画像をアップロード
  3. 画像がバケットへアップロードされたのをトリガーにAWS Lambdaが起動
  4. Lambdaがアップロードされた画像のリサイズを行い、サイズ毎にバケットへ保存

画像の参照は、「現在使用しているCDN」から「Amazon CloudFront」を経由して行います。 image

課題

現状の画像アップロードの仕組みではインフラ、バックエンドそれぞれ下記のような課題を抱えていました。

インフラ

  • インフラリソースの設定がコード管理されていない
  • 開発環境用のS3バケットがないため、本番のバケットでテストをしなければならない
  • Lambdaの画像のリサイズ処理が遅い
  • 複数種類にリサイズして保存しているため、S3の利用料がかさむ

バックエンド

  • APIを経由してS3バケットへ画像をアップロードするので時間がかかる
  • APIのリプレイスはRailsで行うが、このままリプレイスするとPumaのスレッドを長時間占有したり、メモリの使用量が増えるなどパフォーマンスに影響を及ぼす

Railsでリプレイスを行う背景は 過去記事 で説明しているので、併せてご覧ください。 techblog.zozo.com

これらの課題をクリアし、画像アップロード機能のパフォーマンスの向上と運用効率を改善するため、インフラ、バックエンド両面からリプレイスを行うことにしました。

実現したいこと

最終的なゴールは、画像アップロードのリプレイスをすることにより、前述の保守・開発・パフォーマンスの課題を解決することです。このリプレイスは4つのフェーズに分けて行うことを計画しています。

本記事ではフェーズ1とフェーズ2で予定している構想を紹介し、今回対応したフェーズ1で工夫した点や得られた効果を紹介します。

フェーズ1

フェーズ1ではCDNを切り替えます。なお、その理由は後述します。

具体的な手順は以下の通りです。

  • 既存とは別に新しいS3バケットを用意する
  • 画像のアップロードは最新のアプリでは新バケットに向けて行う
    • ユーザーがWEARのアプリをアップデートするまでは新旧バケット両方への画像アップロードが発生することになる
  • 最新のアプリの画像のアップロードはPresigned URLを発行してクライアントから直接新バケットへ画像をアップロードする
  • 旧環境で使用しているCDNを現在使用しているCDNからAkamaiへ切り替える

アップロードした画像のURLはDBに保存しているため、画像参照時はそのURLを基に新旧どちらのバケットに保存されているかを判断し画像をダウンロードします。

このフェーズではCDNの切り替えを行いますが、DNSのレコードを修正することで既存の画像URLは変更せず今まで通りアクセスできるようにしています。 image

フェーズ2

フェーズ1の手順を終え、すべてのWEARユーザーのアプリのバージョンが最新になると、旧環境のバケットには新規のファイル追加がなくなります。それを踏まえ、フェーズ2では下記の手順を実施します。

  • 旧バケットにある画像をすべて新バケットに移行する
  • DBに保存している旧バケットの画像のURLを新バケットのURLに変更する
  • 新環境にAkamaiを導入する

image

なお、後続するフェーズ3〜4では画像リサイズ機能のリプレイスや、不要になった旧環境のリソースを削除する予定です。

次章では、フェーズ1で実際に行ったこと内容をインフラ・バックエンドの両面から紹介します。

フェーズ1の実施内容と効果

本章では、フェーズ1で実際に行った内容とそのポイント、そこで得られた効果を紹介します。

インフラのリプレイス

既存とは別のS3バケットを用意する

既存の仕組みでは、インフラリソースの設定がコード管理されていない課題がありました。そのため、既存のS3バケット上で修正を加えていくのはリスクがあると判断しました。 そこで、新しくAWS環境を用意し、S3バケットを新規で用意しました。

新バケットでは、AWS CloudFormationを用いることでインフラをテンプレート(YAMLファイル)管理できるようにしました。また、CloudFrontの Origin Access Identity を使用し、S3バケットへのアクセスをCloudFront経由に限定しました。これにより、外部から新バケットに対してダイレクトURLでアクセスできないように制限しています。さらに、新バケットでは開発環境と本番環境を分離することで、開発環境でのテストも本番へ影響を与えることなく安全にできます。

旧環境のCDNをAkamaiへ切り替える

旧環境で使用していたCDNをAkamaiに変更します。その理由は、Akamaiの方が全体の配信単価が安くなる点と、Image Manager を活用することでLambdaを使用しなくても画像のリサイズが可能な点です。フェーズ1ではImage Managerの導入は行いませんが、リサイズ機能のリプレイスを行うフェーズ3で導入予定です。

バックエンドのリプレイス

画像をアップロードするためのURLは、S3のPresigned URL機能を利用して発行します。画像のアップロードはAPI経由ではなく、このURLを使うことでクライアントから直接アップロードできるようにします。

ここで使用するPresigned URLは、署名をクエリ文字として含むURLを発行する機能です。この機能を利用することで、認証情報を保持する必要がなく、期間限定で指定したS3バケットに対して操作が可能です。

WEARでも、クライアントで認証情報を保持することを避けたいので、このPresigned URLを使用した画像アップロード機能を実装しています。

Presigned URLの詳しい内容は 公式サイト をご覧ください。

リプレイス前後の画像アップロードのフロー比較

Presigned URLの導入により、画像アップロードのフローがどのように変化するのかを説明します。

これまでの方法では、クライアントのアプリから画像データを一度APIへ渡していました。その後、APIは受け取った画像データをS3バケットへアップロードします。
image

一方、Presigned URLを導入すると下記のフローに変わります。クライアントはS3へアクセスを行うために、APIに対してPresigned URLの発行を要求します。そして、APIはS3へ署名情報を渡し、Presigned URLを発行してクライアントへ返却します。その結果、クライアントは受け取ったPresgiend URLに対して画像データを渡すことで、APIを介さずにS3バケットへ直接画像をアップロードすることが可能です。

image

Presigned URLの発行方法

ここでは、Presigned URLの発行方法に触れておきます。

前述の通り、Presigned URLはAPIで発行します。今回はRubyを利用しているので、AWS公式のGemである AWS SDK for Ruby(Version 3) で実装しています。なお、他の開発言語のSDKは 公式サイト に記載されています。

次に、実装時のポイントを紹介します。

  • 認証情報は、Amazon ECSコンテナにアタッチされたIAMロールを使用する
  • Presigned URLを発行する時点で、アップロード先のバケット名とオブジェクトキーを指定する
  • URLの有効期限は expired_in で秒単位で設定可能だが、セキュリティを考慮し、デフォルトより短い時間に設定し、画像アップロードの度にPresigned URLを発行する
    • デフォルトは900秒(15分)、最大1週間まで期限を設定できる

なお、オプションの詳細は 公式リファレンス にも記載されています。

上記の内容を踏まえたソースコードのサンプルです。

client = Aws::S3::Client.new(profile: 'default')
resource = Aws::S3::Resource.new(client: client)
resource.bucket(#{bucket_name}).object(#{object_key}).presigned_url(:put, expires_in: 60)
# => https://xxx.amazonaws.com/yyy.jpg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=aaa&X-Amz-Date=bbb&X-Amz-Expires=60&X-Amz-SignedHeaders=ccc&X-Amz-Security-Token=ddd&X-Amz-Signature=eee

クライアントは、API側で発行されたこのPresigned URLに対して画像を送る事で、S3バケットへ画像をアップロード可能です。

導入効果

フェーズ1のリプレイスを実施し、以下の課題を解決できました。

  • インフラリソースの設定をCloudFormationでコード管理することにより、変更履歴の確認や複製が容易になった
  • 開発環境用と本番環境のバケットを分離することにより、開発が安全に行えるようになった
  • Presigned URLを使った画像アップロードを行うことにより、サーバーサイドへの負荷を削減とパフォーマンスの向上を実現できた

まとめ

本記事では、WEARの画像アップロード機能のリプレイスのフェーズ1において、インフラの構成変更やPresigned URLを使用した画像アップロードの仕組みを紹介しました。今後は、フェーズ2では旧バケットから新バケットへの画像データの移行、フェーズ3以降では画像のリサイズのリプレイスを引き続き行っていく予定です。

さいごに

運用改善チームではWEARの過去の負債から目を背けず、正攻法で解決案を議論し、運用改善に取り組んでいます。そのため、コミュニケーションと技術力を活かしながら一緒に会社を盛り上げてくれる方を募集しています。ご興味のある方は、以下のリンクからぜひご応募ください。

tech.zozo.com

カテゴリー