Stack Overflow: 技術的負債の必然性

http://marcgravell.blogspot.co.uk/2014/04/technical-debt-case-study-tags.html

3 comments | 2 points | by WazanovaNews 約3年前 edited


Jshiike 約3年前 edited | ▲upvoteする | link

Stack ExchangeのエンジニアであるMarc Gravellがブログで、Stack Overflowのタグ検索のパフォーマンスをあげるために一時的に対応した迂回策を、時間をかけて修正していった経緯を紹介しています。「あまり褒められたやり方ではないけど、その時点ではそうするのがベストだった。」という負債はあるよねという話しです。

Step 0 : 背景

  • Stack Overflowでは、質問に紐づいたタグを検索(“{a} and {b} and {c}”, “{d} or {e}”, “{f} but not {y}” など)のうえ、該当する質問を抽出したり、検索結果ページを表示させる利用シーンが莫大にある。
  • 一般的にはPostsテーブル、Tagsテーブル、PostTagsテーブルを用意し、 PostTagsをかなり駆使すると思うが、それではパフォーマンスがよくない。3つのテーブルに分かれたデータは、複雑なjoinsや複数のクエリ(質問を500件抽出する際にそれぞれに1-5件のタグがある。)を必要とし、結果処理が煩雑になる。
  • そこでタグをDBの中で一つのcharacter-dataフィールドにまとめて保存することにした。これで一発のクエリでタグデータもまとめて呼び出して、後はUIでどう表示するかだけ心配すればよいことになる。

Step 1 : 迂回策

  • インライン化されたタグデータは表示するのはシンプルだが、クエリをかけるには厄介。通常のインデックスは、character dataの間でマッチするものを探すのには向かない。
  • そこで、SQL Server Full-Text Searchを採用。CONTAINS, FREETEXT, CONTAINSTABLE, FREETEXTTABLEを使って複雑なマッチングに対処できるようになった。しかしそれでは、NGワードや一部の表現(“c#”, “c++”, etc)に対応できない。
  • 幸い当時は、ASCII alpha-numericsと特定の記号(+, –, ., #)しか許容してなかったため、その4つの記号を特定の非英語の文字で代替(例えば、“.net c#” は “éûnetà écñà” と表示。)し、NGワードは他のcharacterを組み合わせてラッピングすることで対処した。
  • あまりお薦めできる迂回策ではないが、予定とおりのスケジュールで仕様の実装を完了できた。

Step 2 : ほころびがではじめる

  • そうしているうちにStack Overflowのトラフィックがどんどん増えていって、キャッシュ後でもパフォーマンスのボトルネックになってきた。そもそもFull Text Searchに適した利用ケースでもなかったし。
  • 検索エンジンも、Lucent.NetLucene.Net、続いてElasticsearchで書き直し、タグでリスティングする作業も、カスタムメイドのタグエンジンに進化していった。

Step 3 : 多言語対応

  • Stack Overflow自体は英語のサイトであるが、フランス語、ロシア語、日本語などについて言及するコンテンツがでてきたので、ASCII alpha-numericsしばりをやめる必要がでてきた。
  • 幸いこの時点ではElasticsearchとタグエンジンができており、Full Text Searchを使うのは、記号を特定の非英語の文字で代替したものを検索するときだけの限定的なパターンになっていた。
  • そこで、フランス語のコンテンツはアクセントに関係したタグがなかったので、フランス語の6つの文字だけを代替え用にキープしておくことにした。

Step 4 : 本当の意味で多言語サイトになる

  • Stack Overflowのポルトガル語版をローンチすることになり、本当の意味で多言語サイトになった。そこで問題になったのが、ポルトガル語ではリザーブした文字のうちかぶるものがでてきてしまった。
  • 検討のうえ、前後をパイプで囲うかたちに変更した。例えば、“.net c#” は “|.net|c#|” となる。シンプルにパースできるし、最初のcharacterでそのフォーマットを使っているがすぐに判断でき、まとめて更新もかけられる。パイプ記号をこの特別な利用方法のためにリザーブしておく必要があるが、支障はない。印刷されないcharacterを使用する案もあったが、JSONなどではバグとなるリスクが高いのでやめた。

Step 5 : 残件処理

  • パイプ記号の採用は、新規の書込みだけにして、過去データは安全を見てしばらくそのままにしていた。しかし、いつまでも二つのフォーマットに対応せねばならないのはメンテナンスが面倒なので、最終的にはバッチ処理で一括変換した。

Jan-Mar/2014: ワザノバTop25アクセスランキング


#stackoverflow

k12u 約3年前 | ▲upvoteする | link

Lucent -> Lucene ですね

ご指摘ありがとうございます。

Back