こんにちは。バックエンドエンジニアの @naopr です。
3/20にUS版メルカリでリブランディングを実施し、ロゴやアイコン、デザインを一新しました!
今回は、リブランディングに伴うHTMLメール変更にまつわる地味で泥くさいお話をご紹介します。
メルカリでのHTMLメールの実装
メルカリはコア部分をPHPで実装しており、HTMLメールのテンプレートエンジンとしてTwigを利用しています。
一部の古いテンプレートは生のPHPで書かれていますが、多くのテンプレートはTwigで書かれています。
検証環境でメールが送られないトラブル
今回、リブランディングに伴いTwigのテンプレート変更を行いました。
ローカル環境での開発が終わり、いざ検証環境でテストをしてみると残念なことにメールが送られてきません。
不思議なことにメールの種類によって送られるものと送られないものがあり、環境によって送ることのできるメールの種類にも違いがあることがわかりました。
開発中のブランチだけでなくmasterブランチでも同様の事象が発生していたものの、幸いなことにこの事象は開発環境特有で本番環境では発生していませんでした。
過去にHTMLメールの対応経験があるエンジニアにヒアリングしたところ、以前から同様の不具合はたびたび起こっていたが詳細な調査や根本対応まで手が回っていない状況とのことで、ちょうどこのタイミングでメルカリ社内イベントである Be Professional Days が開催されたこともあり、腰を据えて調査と対応を行うこととしました。
Be Professional Daysについてはこちらの記事をご参照ください。
次章から、メールが送られない原因の調査から根本対応までの流れをご説明します。
エラーログの調査
アプリが意図したふるまいをしない場合、まずはエラーログの調査を行うことがほとんどかと思います。
ただ、残念なことにこのケースではエラーログが吐かれていませんでした(過去にここで多くのエンジニアの時間が浪費されていた模様)。
エラーログが吐かれない原因を地道に探っていくとメール送信時にtry~catch
で例外が握りつぶされていることがわかり、catch時にログを吐くようにしたところ下記メッセージが出力されました。
Unable to write in the cache directory (/path/to/cache/dir/...).
どうやら、キャシュファイルの書き込みが失敗しているようです。
キャッシュディレクトリの調査
Twigでは、高速化のためにコンパイル済みのテンプレートファイルをキャッシュすることができます*1。
Twig_Environment
インスタンス生成時にオプションとしてキャッシュディレクトリのパスを与えることで、キャッシュを保存しておくことができます。
キャッシュファイル生成時には、指定したキャッシュディレクトリ配下にいくつかのサブディレクトリが作成され、キャッシュファイルはその中に保存されます。
メルカリでもこの機能を利用しているのですが、先のエラーメッセージを見る限り、キャッシュディレクトリへのファイル書き込みが失敗しているようです。
ファイル書き込みの失敗原因はいくつか考えられますが、書き込みが成功している環境と失敗している環境の違いを調べて比較してみました。
成功している環境
$ ll -a /path/to/cache/dir/ab drwxr-xr-x. 2 apache apache 4096 Jan 1 2018 . -rw-r--r--. 1 apache apache 18647 Jan 28 2018 abcde.php
失敗している環境
$ ll -a /path/to/cache/dir/ab drwxr-xr-x. 2 root root 4096 Jan 1 2018 .
比較してみたところ原因は一目瞭然で、ファイル書き込みの権限がないことによるエラーでした。
メルカリの検証環境ではメール送信を行っているユーザが2種類おり、それが今回のエラーにつながっていました。
- Apache: ユーザリクエストを受けてメールを送信する
- root: バッチコマンドやq4m経由でメールを送信する
具体的には、rootユーザがキャッシュディレクトリを作成しその中に任意のキャッシュファイルを作成した後に、Apacheユーザがそのディレクトリ内に別のキャッシュファイルを作成しようとした際にエラーが起こります。
問題への対応
ここまでの調査から、キャッシュディレクトリへのファイル書き込み失敗が原因でメールが送られていないことがわかりました。
暫定対応としてキャッシュディレクトリを消してしまうことでメールが送信されることは確認できたのですが、あくまで一時的な対応なので再発の可能性があるため恒久的な対応は別途必要です。
恒久対応としていくつかのパターンを検討しましたが、実装工数と既存機能への影響を考慮して、検証環境だけファイルの書き込みに失敗しても例外を投げずにログだけ残す方針としました。
具体的には、lib/Twig/Cache/Filesystem.php
を継承した下記のような別クラスを新規に作成し、開発環境ではそちらを利用することとしました。
<?php // メインの部分以外は省略した擬似コードです class OriginalFileSystem extends \Twig_Cache_Filesystem implements \Twig_CacheInterface public function write($key, $content) { $dir = dirname($key); if (!is_dir($dir)) { if (false === @mkdir($dir, 0777, true) && !is_dir($dir)) { // throw new RuntimeException(sprintf('Unable to create the cache directory (%s).', $dir)); \Log::error(sprintf('Unable to create the cache directory (%s).', $dir)); return; } } elseif (!is_writable($dir)) { // throw new RuntimeException(sprintf('Unable to write in the cache directory (%s).', $dir)); \Log::error(sprintf('Unable to write in the cache directory (%s).', $dir)); return; } // ... } }
かなり泥くさい対応となってしまいましたが、最小限の影響範囲にとどめてクイックに改修できたことで、US版メルカリのリブランディングも無事にリリースができました。
メルカリには、新しい技術を使って高速にサービスを開発できるエンジニアが多数在籍していますが、泥くさく愚直にサービス改善を行うのが得意なエンジニアも募集しております。
少しでも興味があれば、ぜひご応募ください。お待ちしております。