はてなブックマークのシステムについて
TRANSCRIPT
はてなブックマークのシステムについて
株式会社はてな / {Shibuya, Kansai}.pm伊藤 直也
自己紹介
株式会社はてな 執行役員 CTO
アジェンダ
はてなブックマークのこれまでと現状 各種機能の実現方法 パフォーマンスに関する話
はてなブックマークのこれまでと現状
はてなブックマーク
はてなブックマークのこれまで
2005/2 ベータ版リリース 2005/8 正式版リリース 2008/7 関連エントリー機能
株式会社プリファードインフラストラクチャー (PFI) と共同開発
2008/11 はてなブックマーク 2 システムリニューアル 全文検索 (w/ PFI) 、カテゴライズ etc
2009/4 Firefox 拡張 2009/5 はてなブックマークプラス
有料オプション
現在の数字
300,000 ユーザー登録 400 万セッション / 月 (Google
Analytics) 1,600 万 URL / 4,700 万 ブックマーク
データの規模の例
mysql> select count(*) from relword;+-----------+| count(*) |+-----------+| 351277311 |+-----------+1 row in set (0.00 sec)
データの規模
レコード数 1,600 万 エントリー 4,700 万 ブックマーク 5,000 万 タグ
データサイズ (MySQL MyISAM) エントリー 3GB ブックマーク 5.5GB タグ 4.8GB HTML 200GB 超 (zlib で圧縮済み )
中規模 ~ 大規模ウェブサービス
サーバ台数 httpd mysqld そのほか
コードの規模 Perl コード 10 万行弱 (WAF 等抜きで 45,000 行 ) .pm ファイル 1,200 個強 (600 個 )
Google, Yahoo!, Amazon が超大規模とすると中規模くらい
レコード数 千万件単位、データ規模は GB 単位
開発体制
8 名体制 ディレクター ( マネージャ ) 1 名 エンジニア 3 名 + アルバイト 3 名 デザイナー 1 名 + アルバイト 1 名
システム構成
基本は LAMP ウェブアプリ部分はオーソドックスな構成 Linux (Xen), Apache, MySQL, Perl
用途によってはサブシステムあり 検索用サーバー群 (PFI の Sedue 、自社開発の検索
サーバ ) カテゴライズサーバー スペルミス修正サーバー 非同期タスク用に TheSchwartz 分散ファイルシステムに MogileFS etc.
プログラミング言語
Perl 5.8 mod_perl WAF ・・・ Ridge ( 自社開発 ) O/R マッパ ・・・ DBIx::MoCo
JavaScript Ten.js ( 自社開発 )
C++ Thrift メモリ要件、速度要求が厳しい箇所
利用している主なソフトウェア
Perl, JavaScript, C++ LAMP, Xen LVS + keepalived
ロードバランサ Thrift
多言語 RPC フレームワーク (by Facebook) TheSchwartz MogileFS Lux IO
Key Value Store Squid
各種機能の実現方法
本文抽出
HTML::ExtractContent
はてなブックマーク 2 用に開発した本文抽出モジュール
HTML から「良い感じ」に本文テキストを抽出
CPAN に公開済 JavaScript 版を現在開発中
サイボウズ・ラボの中谷秀洋氏の Ruby 実装を Perl に移植
後述の検索、カテゴライズの精度に大きく影響
HTML::ExtractContent の仕組み 正規表現だけで高速に ヒューリスティクスで " 本文らしさ " を判定
HTML を適当なブロック要素ごとに分割 各ブロックにスコアリング
本文っぽさでスコア増 ( 句読点、テキストノードの文字列長 etc) 本文っぽくなさでスコア減 ( リンクばかり並んでいる etc)
つながっているブロックをまとめてクラスタにする 高スコアが続いたらクラスタ、低スコアが来たら切れ目 ブロックスコアの合計がクラスタのスコア
スコアの一番高いクラスタが本文
詳しくは : http://d.hatena.ne.jp/tarao/20090322#1237750634
HTML::LayeredExtractor ( 非公開 )
新聞など ・・・ ExtractContent で OK 一部のサイト ・・・ 確実に抜き出せる別の方法
<!-- google_ad_section_start --> Web API
ニコニコ動画、 YouTube 、 Amazon.co.jp
特定サイトはルールベースに処理 自社サイト、大手サイトは XPath で、など
フィードを利用 複数試して一番良い結果を選択するメタエンジン
Chain of Responsibility で順番に試して駄目なら ExtractContent に fallback
全文検索機能
2 つの全文検索システム
全登録文書からの全文検索 PFI の Sedue
自分のブックマークからの全文検索 有料オプション限定の機能 自社開発の Perl 検索エンジン
1. 全文書からの全文検索
Sedue http://preferred.jp/sedue/ 圧縮 Suffix Arrays
スコアリングのアルゴリズム よく知られた幾つかのヒューリスティクス ブックマーク数やブックマークされた時間を加味
文書登録 , 更新 ブックマークのタイミングに合わせて
TheSchwartz で 1st ブックマークから数分後には検索可能
2. 個人のブックマークからの全文検索 " マイブックマーク全文検索 " Perl で独自の全文検索システムを開発
オーソドックスな転置インデックス方式 ユーザー毎にインデックスを構築
動的に更新可能 id:naoya プロトタイプ → id:r_kurain ( 倉
井龍太郎 ), id:tarao が中心に開発
転置インデックス
辞書 → 含む文書の ID 列 (Postings List) の索引
...perl => [1, 5, 20, 333, 350, 362 ...]python => [8, 10, 11, 52 ...]ruby => [1, 10, 21, 333, 350, 428 ...]...
転置インデックスの構成
二つのインデックス 辞書が N-gram のインデックス
ノイズが少なく、且つ検索漏れして欲しくないデータ用のインデックス
タイトル、コメント、タグ、 URL 文字列など 辞書が形態素解析のインデックス
本文テキスト (by ExtractContent) などノイズの多い箇所 MeCab ( はてなキーワード + MeCab 辞書 )
検索は両者から行って、結果をマージ
Postings List には文書 ID と単語出現位置を記録 スニペットの表示やスコアリングに出現位置が必要 [ 10: 2, 100, 108, ... ]
転置インデックスの構成 ( 続き )
転置インデックスの圧縮 Postings List の差分を取って VB 符号 ( 相当 ) Array::Gap
http://d.hatena.ne.jp/naoya/20080906/1220685978
転置インデックスの保存に Lux IO Lux の KVS Perl バインディング Lux::IO (id:antipop) シンプルで高速 ユーザー毎にファイルひとつ。本システムの運用に
最適 索引を作り直したかったらファイルを削除して再構築 分散したいなら、ユーザー ID などでパーティショニング
検索結果のスコアリング
本夏のインターンシップで開発 id:yaotti, id:nyanp
スコアリングの手法
複数のヒューリスティクスの結果を重み付き線形和で
(例 ) Score = 0.5 * TF-IDF + 0.2 * ブクマ数 + 0.3 * 新鮮度
重みは適当 本格的にやるなら正解データからの機械学習
スコアリングの手法の一部 ( 続き )
クエリがタイトル等に含まれているかどうか 複数クエリ語の近傍度 クエリの出現位置 ・・・ 前半であるほど良 クエリのマッチの仕方 ( 単語境界かどうか etc) TF-IDF ブックマーク数 文書の新鮮度
文書の 1st ブックマーク日時 特定の URL にボーナス
Wikipedia, d.hatena.ne.jp/keyword など ルートドキュメント
スコアリング今後
PageRank や HITS 的なリンク解析 クリックストリームからのフィードバッ
ク学習
検索エンジンとアプリケーションのやりとり
JSON-RPC で Web API アクセス制限などはウェブアプリケーション側で
検索システムはあくまで検索結果を返すだけに専念
Web API は一般にも公開 google " マイブックマーク全文検索 API"
Firefox 拡張 + マイブックマーク検索 拡張から Web API で検索結果を取得
スペルミス修正機能
もしかして
スペルミス修正サーバー
クエリ語に対して修正候補を返す C++ と Perl ・・・ Thrift
スコアリングエンジンを C++ で、問い合わせは Perl で
スペルミス修正のアルゴリズム はてなキーワードを辞書に補正 N-gram 索引を使って候補を絞り、 Jaro-Winkler 距離 (編集距離のようなもの ) で近似度を測定
詳しくは google "Kansai.pm スペルミス "
関連エントリー機能
レコメンドエンジン BSim
PFI のレコメンドエンジン ブックマークのタグを入力
ほかも幾つか試したがタグの精度が圧倒的に良かった
タグは記事推薦に非常に有効
Perl とのやりとり C++ で書かれたエンジンと Perl アプリ Thrift を利用して RPC で
カテゴライズ機能
文書分類エンジン BDog
自社開発の文書分類エンジン C++ で実装、 Thfirt で Perl から利用
本文テキスト (by ExtractContent) から単語ベクトルを作って判定
ベイジアンフィルタ Complement Naive Bayes
http://d.hatena.ne.jp/tkng/20081217/1229475900 特定のクラスに偏りがある場合、 Naive Bayes よりも精度
が向上 動的な学習により精度向上
ユーザーによるカテゴリ修正で ... 過学習防止のため対象は特定ユーザーに限定
Complement Naive Bayes
そのほか
ブックマークスパム判定機能 複数のルールベースを組合わせた判定エンジ
ン 例 : 同一順で同一エントリをブックマークし続け
る複数アカウントによるブックマーク
特徴語抽出 はてなキーワード TF-IDF 関連広告表示などにも利用
各所で TheSchwartz を活用
TheSchwartz Six Apart 社開発のジョブキューフレームワーク CPAN
検索、スパム判定などブックマーク追加後に各種更新処理が発生
同期処理だとユーザーを待たせる TheSchwartz で非同期化
HTML を取得し本文抽出 / スパム判定 / カテゴリ判定 / Sedue への更新要求 / マイブックマーク検索エンジンへの更新要求 / はてなキーワード抽出 / リンク抽出 / favicon を取得 / AccountDiscovery / スクリーンショット撮影 ...
はてなブックマーク Firefox 拡張
Firefox 拡張
Firefox Add-ons XUL
http://subtech.g.hatena.ne.jp/secondlife/20090402/1238661633
id:secondlife, id:nanto_vi が中心に開発 オープンソース
http://github.com/hatena/hatena-bookmark-xul
はてなブックマークの各種機能を JSON API にして、 Firefox から利用
拡張用に追加した API の一部は公開 API としてもリリース
雑感
Web + DB システムの一歩外の開発をここ一年ぐらいで色々した
検索、レコメンド、 Firefox 拡張 ... なかなか楽しい 新しい体験をエンドユーザーに提供できる
パフォーマンスに関する話
バックエンドのシステム構成
定番の三層構造 LVS + KeepAlived で分散 lighttpd or Apache -> (Squid ->)
mod_perl -> MySQL
バックエンドシステムの三層構造
proxy proxy
LB
LB
app server app server app server app server
LB
DB DB
LB
LB
LB
ハードウェア : 自作サーバー
データベースの構成
オーソドックスな構成でスケーリング MySQL (MyISAM)
メモリ 16GB 一部で SSD を試験
マスタ・スレーブ構成 データのサイズ、用途に合わせてテーブル単
位で分割 メイン ( ユーザ情報、エントリ、ブックマーク ) タグ、キーワード HTML データ etc.
島分け
proxy
AP
bot / feed
通常のリクエスト
DB
画像 API etc.
proxy
Squid によるキャッシュ
Squid Reverse Proxy でキャッシュ ゲスト向けコンテンツ bot 向けコンテンツ 一部の API
○○ users 画像のバックエンドの API など
memcached によるキャッシュ
アプリケーションフレームワークに memcached キャッシュ機能
ページを丸ごと memcached にキャッシュする機能
ユーザー毎の設定パラメータをまとめて SHA1 キーを作り、同じ設定のユーザーには同じキャッシュを返す機能
Template::Plugin::Cache longtail はキャッシュしない
○○ users を閾値に
クライアント表示に関する工夫
昨今では定番の手法を実施 gzip 圧縮 静的ファイルの Expires を 1年に
更新される可能性のあるものは別ディレクトリで管理
CSS, JavaScript ファイルは git の commit + path の SHA1 をクエリパラメータに 例 : /js/foo.js?sha1hogehoge
静的ファイルを別ドメイン → Cookie 分の転送を削減
クライアント表示に関する工夫 ( 続き )
JavaScript による遅延ロード 広告の遅延ロード
広告表示でページ表示が待たされない Google の広告、自社広告
クライアント表示に関する工夫 ( 続き )
ユーザー毎に出し分ける部分を JS で遅延
残った共通部分 (HTML) をキャッシュできる
雑感
一般的な Web + DB システムの運用は苦労が減った
ハードウェアの進歩、特にメモリ容量 各種ノウハウがウェブや書籍等で公開されている
JavaScript の遅延ロードをうまく使うと高速化に寄与できる箇所が多い
RDBMS だけでは難しい規模 / 要件をどうするかが面白い
検索エンジンを作る、レコメンドエンジンを作る、文書分類エンジンを作る ... etc.
WEB+DB PRESS Vol.49
まとめ
はてなブックマークのシステムの裏側を紹介した
オーソドックスな Web + DB システムと幾つかのサブシステムを開発した
検索、レコメンド、文書分類 etc RDBMS では難しい /面倒なことをどう解決していくかが今後もテーマに
Thank You !