大学の学部四年生でエンジニアをしている者です。
普段は業務委託でバックエンド開発をメインに行っています。
今回はPHPのプロダクト内で起きていた問題解消にGolangの機能(Go routine, channel)を使用して解消をしました。
その概要をまとめます。
コード自体はプロダクトに反映してしまったので載せるのは控えます。
プロダクト使用技術
- AWS EC2 (t3.medium)
- AWS S3
- PHP (Laravel)
- poppler-utils (pdftoppmコマンド) ⇒ PDFの画像変換処理で使用
- Golang ⇒ 改善のために使用
開発機能詳細
ピッチ資料を生成する機能です。
機能の内部処理の流れとしては以下の通りです。(AS IS)
1️⃣ ユーザーはPDFファイルをアップロード
2️⃣ アップロードされたファイルをローカルのディスクに一時保存
3️⃣ ローカルディスクに保存されているPDFスライドを画像変換するコマンドを実行し、変換された画像をすべてローカルディスクに一時保存
4️⃣ 画像を一枚ずつS3に保存し、保存完了した画像に関しては削除する
5️⃣ アップロードされていたPDFファイルを削除
6️⃣ ユーザーにS3保存先フォルダのパスを返す
起きていた問題
画像変換の遅延 (CPUバウンド)
画像変換処理にはpdftoppmコマンドをPHPから叩いてPDFのスライドを全て画像に一括変換処理を走らせていました。
コマンドの内部では逐次実行(一枚ずつ変換)が走るのでCPUバウンドによる遅延が大きくなっていました。
一般的なファイルでは問題がなかったのですが、変換が難しいスライド(高解像度、大量テキストなど)が含まれているとそこで変換が止まってしまうような現象も起きていました。
ディスク操作による遅延(I/Oバウンド)
画像やPDFを一時的にローカルストレージ内に保存する処理や、処理終了後に削除する処理があります。
これをこれまで逐次実行していたため、ディスク操作のI/Oが発生していました。
画像変換の遅延に比較すると影響は軽微ではあります。
S3保存の実行 (I/Oバウンド)
全ての変換処理が終わったら変換した画像ファイルをローカルディスクからs3に保存する処理を走らせていました。
ループで1枚1枚保存を走らせるので、待ち時間がかかる原因になっていました。
こちらも同様に画像変換の遅延に比較すると影響は軽微です。
考えた改善案
コードベースで最終的に以下4点を用いて改善を試みました。
- 画像の変換は並列化し、インフラのCPUをなるべく効率的に使う
- これまでは全ての画像変換が終わってから、S3保存を走らせていたが、変換処理が終わったものから順にS3への保存を実行するようにする。(S3保存と画像変換は並行して行う)
- 画像のS3保存も1枚ずつ実行していたのを変換処理が終わったものからすべて並行して行うようにする
- ディスクへの一時保存をメモリを使った方法に変更する
技術選定根拠
- Goは多少使ったことがあるのと、開発の他メンバーにも使っている人がいたため
- goキーワードを使うだけで並行処理が実現でき、記述が簡単
- buildしたバイナリをデプロイすルだけなので特に環境を変える手間もなかった。(私の業務範囲で事足りる(サーバーサイド, CICD))
- PHPで同様のことを実現するのはできなくなさそうだったが、複雑な上現在の実行環境を変更する必要があった
結果
実行テストは既存コードでかなり処理が遅かったファイルを採用して行いました。
画像変換処理のみ ⇒ 35%のパフォーマンス改善
全処理 ⇒ 50%のパフォーマンス改善
より良くするために
-
この機能だけAWS Lamdaに切り離す
改善はしたもののいまだに待ち時間はあります。CPUコアを増やせば(インスタンスを変える)この機能に最適化されたものを作れますが、実際のプロダクトではコストを考える必要があります。そこでLamdaがちょうど良いかなと考えています。(未実装)
Lamdaはリクエストの回数と、インフラのスペックで金額が決まるそうです。インフラで負荷がかかるのはこの機能以外に今の所はないのでLamdaに切り離して実装できると良いかなと思っています。今後機会があればチャレンジして見たいなと思います。
終わりに
Golangに関しては初心者なのでより良くする方法があれば教えていただけると嬉しいです。
技術の幅が広がると、解決できる問題の幅も広がるなと実感しました。
並行処理などについて実務の中で理解が深められ、良い経験になりました。