こんにちは! エンジニアインターンの須長です。インターン生二人で作ったserp-proxyについて得られた知見、挑戦したこと、その解決方法などを紹介していきたいと思います。
SERPサービスという言葉自体が勝手に作った造語ですが、決まった呼び方がなく不便なのでこの記事内ではSERPサービスとさせてください。SERPサービスとはgoogle等の検索結果の順番をJSONなどの形式で返してくれるサービスでSEO対策で使われるものです。
元々使用していたSERPサービスがレスポンスや様々な対応に異常をきたしていて、このままでは問題だということが始まりでした。他社のサービスを検討するもAPI実行回数が大きくなかったりしたため、複数あるSERPサービスをまとめて1つのものとして使える物を作ろうということになりました。そのような流れから生まれたのがserp-proxyというプロジェクトです。
・Node.js
・AWS(ECR,ECS)
・redis
今回開発したのはリクエストが来た際に複数のSERPサービスをロードバランシングし、負荷が1つのサービスに集中しないようにするシステムです。
またserp-proxyサーバーのステータスが確認できます。具体的には各SERPサービスの残りリクエスト回数やレスポンスタイム、serp-proxyサーバーの各ノードの起動時間や受け付けたリクエストの回数などが確認できます。
加えてエラーが起きたSERPサービスは使われないように一時的に設定し、30分ごとにリフレッシュすることでエラーが起きているSERPサービスをできるだけ使わないようにすることと、一度だけのエラーである可能性を考慮し早急に復旧させることの両立を果たしています。
サーバーからのリクエスト
↓
ロードバランスしてserp-proxyの使用するサーバーを決定(serp-proxyサーバーは複数存在)
↓
redisからそれぞれのSERPサービスの利用状況を読み取り、使用するサービスを決定。ほぼ同時に接続に必要なapikeyなどを取得
↓
決定したSERPサービスにリクエストを送る
↓
SERPサービスごとのアダプターで帰ってきたレスポンスを統一した形に整形して返す
↓
redisに変更した値を保存する
↓
サーバーにレスポンス
今回はserp-proxyのサーバーは複数あり、redisが1つしかないという設計をとっていました。その状況下において毎回serp-proxyサーバーが足並みをそろえずにSERPサービスの情報を書き換えていては、serp-proxyサーバー間でSERPサービスの情報の整合性が取れなくなるという問題が発生しました。
redisはRDBではないので当然ACID特性などはなく我々で対応しなければならない問題でした。
redisとの戦いはさまざまな対応と試行錯誤があり長くなってしまう上に、私ではなくもう1人が多くを対応したので別の記事にまとめてあります。こちらを参照ください。
まず初めは残りリクエスト回数で振り分けようと考えていました。その時は2つのサービスを使っていたのですが、片方があまりにも遅く(2〜3倍近く)残りリクエスト回数で振り分けるのはよくないのでは?という思考にたどり着きました。
残りリクエスト回数で振り分けると遅い方のSERPサービスに当たった人は2、3倍近く待つ可能性が出るのです。そしてそれは無視できるほど小さな差ではないと考えました。
その問題を解決するためにレスポンスタイムを記録しておきその比率によって確率を決定し、その確率に基づいて振り分けることにしました。
字面だとわかりづらいので例を挙げると、サービスAとサービスBのレスポンスタイムの比率が1:3だとすると、使われる確率を3:1にするのです。そうすることで遅いサービスは遅ければ遅いほど使われなくなり、速いサービスは速いほど使われるようになります。
それなら速いサービスだけ使えばいいのではという思考に行き着くかもしれませんが、もちろんリクエスト回数には制限があり、どちらか速い一方を使い続ければ待っているのはずっと遅いサービスのみです。また片方だけがずっと遅いわけではなく、一時的に遅くなっていたという可能性もあるわけです。こういった観点から程よいバランスで振り分けることが重要だという結論に達せます。
この実装によって使用者が比較的快適にサービスを使えるのではないかと考えました。もちろんこの方法だと速いサービスを優先的に使う分残りリクエスト回数には偏りが出るので、ある閾値を超えると残りリクエスト回数で振り分けるように設定しました。
またレスポンスタイムの導出もこだわり、外れ値や最新の結果に左右されすぎることのないように、毎回前回のタイムを4倍し、今回のタイムを足し合わせ5でわることで、最新の結果を強く反映しつつも今までの結果も反映するという方式をとり、SERPサービスの振り分けの問題を解決しました。
私はこのプロジェクトに少々遅れながら参加したのですが、それでも概要や個々のフォルダーやファイルが何をしているものなのかなどすぐに理解できたのは偏にその設計の良質さにあると思います。
最近この世界に飛び込んだ身ですので、はっきりと良い設計がわかるわけではないので偏見や知識不足が混じりますが、1ファイルに1つの機能だけを載せることはシンプルでわかりやすいものにするだけでなく、効率までも向上させることになるのだと思いました。
綺麗に分ければファイルごとの結びつきは小さくなるので、分担することができますし、実装難易度に応じて、実装を任せる人を変えることもできます。
私の功績ではないので私が誇れることではありませんが、見習いたいなと思いました。
今回挙げた問題たちは気にしなければ、動くことに問題はないようなものたちです。redisとの戦いも時間など気にしないという立場の人ならば些事ですし(整合性の問題はのぞく)、SERPサービスの振り分けの問題なども別に気にしないという人の立場からすれば木っ端であることは容易に想像ができます。
しかしながら動くことが至上でそれ以外はいらないという立場は問題でしょう。こういった細かいところに心血を注ぎ、時間の許す限り立ち止まり、改善に改善を重ねる。その先に良いサービスがあるのだと感じました。もちろん細かいことに拘りすぎて、動かないというのは問題だとは思いますが。
比率の知識がなければSERPサービスの振り分けの解決方法は思い浮かばないわけで、またさらにredisの問題の解決方法のメソッド(こちらの記事をご覧ください)も知らなければ思いつかなかったわけです。
少々飛躍のしすぎかもしれませんが、全てを解決するような、魔法のようなアイデアというのもこういった知識の寄せ集めでできているのかもしれないと思いました。
凡庸なアイデアも奇跡的なアイデアもその根底に知識が必要ならば、無駄かもしれない知識であっても収集して仕舞っておくのも悪くはないと思います。
※2022年10月26日時点