大约6年前,我使用WordPress搭建了一个拥有500万篇内容的网站,但最终以失败告终的经历
首先
WordCampTokyo 2015即将在本周末举行。
虽然我没有参加圣诞节日历活动,但被WordCampTokyo 2015上拥有月均1000万PV的网站演讲所启发,我将从工程师的角度写一篇关于使用WordPress创建大量内容网站的故事。
概要
あるとき自社開発のウェブサービスで「とにかくPVを稼ぐサイト」を作ることになりました。
「PVさえあれば広告費は勝手に付いてくる」ということで「とにかくPVを稼ぐサイト」の作成にとりかかりました。
とはいえ自分はあくまでエンジニアで、集客のできるコンテンツを作っていく自信はありませんでした。
そこでチームで相談して、「海外のサイトをクローリングして機械的に日本語翻訳させるウェブサイト」を作ることにしました。
当時流行っていた言葉を借りて良く言えば「マッシュアップ」、悪く言えば「パクり」サイトの作成となりました。
(とはいえまだクローリングという言葉も浸透していないぐらいでしたし、機械的な翻訳で日本人向けに作成することは一定の意義があったかなと思っています)
クローリングしてウェブサイトを作る、ということでCMSの採用を検討しました。
当時のCMSといえばMTがまだまだメジャーで、自分が利用していたこともありMTで開発をスタートさせました。
しかしながらすぐに性能限界が近づきます。コンテンツを追加するとHTMLの再生成を行うMTでは1万件ぐらいで再生成が終わらなくなってしまいました。
一日に数万件の記事をアップしていたのですぐにこれではダメだ、ということになりました。
そこで検索してヒットしたWordPressを利用する事を決断しました。
当時はWordPressのバージョンが2.3ぐらいだったと記憶しています。
このころはカスタムタクソノミーどころかタグ機能すら無くてカテゴリをカスタマイズしてタグにしていた記憶があります。
そんな中で開発がスタートし、記事をどんどんプログラムでWordPressに追加していきました。
前置きが長くなってしまいましたが実際に利用したテクニックについて述べていきます。
環境など
人員
-
- 兼任プロジェクトマネージャ1名
- 専任エンジニア 2名 (ここに配置)
デザイン、HTMLコーディングは外注しました。
デザインについては僕たちも素人で、デザイナーさんに対して「お客さんがたくさんきてくれるような構成にして欲しいと」言って、本来であればディレクターのするべきことなど、色々と構成の組み直しをお願いしました。
今になって振り返ると、デザイナーさんにあそこまで作ってもらったのは酷だったなぁと反省しています。
でもそもそもディレクターが居なかったですし、存在も知りませんでした。
服务器
当時は円高が始まる頃で今ほどスマホタブレット市場も無くパソコン関連の価格が割安でしたので、自作サーバ最盛期でした。
ホスティングやハウジングは利用せず、後に鼻毛鯖と命名されるExpress5800をカスタマイズして使っていました。
鼻毛鯖にnon-ECCメモリを16GB〜32GB、CPUに初代Core i7を積んで一台当たり5万円ぐらいで作成しました。
トータルで10台ぐらい導入しました。
网络环境
我使用常规的ISP回线来设置网络服务器。
我们公司签约的ISP回线多次受到带宽限制。
由于公司董事长的个人住宅有多余的ISP回线,我们将公司和董事长的住宅分别通过VPS连接,并通过rsync同步静态文件,然后作为CDN进行公开和运营。
由于这是海外网站的翻译工作,我们还签约了几个价格约为1000日元/月的服务器作为海外跳板使用。
2015年現在では自宅サーバよりクラウドやVPSのサーバ環境、ネットワーク環境がかなり安くなってきていると感じています。
自宅サーバは円安や需要低減によるコスト髙を感じていますが、例えばバックアップ目的などにはクラウドと併用すると良いと考えています。
适用于大型网站的技巧。
从这里开始是我实际使用过的技巧。
管理软件
WordPressの記事が増えてくると管理できなくなります。
管理画面はそのままでは開けません。特に記事やタグの読み込みがひどくすぐに使わなくなりました。
記事の追加などを行うソフトウェアは自前で書き、細かいデータ変更は手動のSQL発行とテーブルの直接編集を行っていました。
エンジニアはテーブルの直接編集もそれほど苦ではありませんでした。
しかしプロジェクトマネージャはテーブル編集が出来ませんでした。
後に類似のサービスを立ち上げましたが、その際にはそういった非エンジニアのために管理部分はCakePHPで専用の管理画面を設けました。
WordPressフロントエンドを担当してバックエンドは他のフレームワークを使う、というやり方はなかなか良いかなと思います。
MySQL
ご存じの通りWordPressではバックエンドDBにMySQLを採用しています。
日々そこそこ大量のデータが入ってきますので更新系だけでもサーバはめいっぱいになっていまいました。
WordPressのデータなので容量はそれほどではなく、終盤で100GB、他に管理用で100GBぐらいでした。
用途別にDB自体をいくつかに分割して物理サーバごと分けました。
運用開始からしばらくしてSSDが普及し初め、120GBで一万円ちょっとだったのでそれに載りきるようにしていました。
水平分隔
MySQLは更新系(UPDATE, INSERT)と参照系(SELECT)を分割しました。
更新系を1台、参照系に最大で3台を割り当てました。
更新系から参照系へは標準のレプリケーションを利用しました。
クローラから引き出されるデータは全て更新系に書き込み、参照系では実際にウェブサーバから読み出されるWordPressのテーブル群を格納していました。
WordPressは標準ではレプリケーションによる読み出しをサポートしていません。自前のDB振り分け機能を実装していました。
この振り分け機能はDB負荷を分散させる上で非常に有用でしたが、同時にWordPress側からの更新は非常に行いづらくなっていました。
例えばコメントを付けようと思ってもWordPressが繋いでいるMySQLは参照系ですから、そのままではコメントを付けることが出来ません。
しかしながらサービス自体が提供者->利用者の一方向性のものでしたからコメント機能は重要視しておらず問題にはなりませんでした。
(とはいえ現在から振り返ってみるとこの辺りのサービスの戦略やディレクションについては非常に甘かったと思います。コメント機能などを拡充してもっと利用者によりよいサービスを提供すべきだったな、と反省しています)
WordPress側からもデータの更新が行えましたが、ウェブサーバが一旦引き受けて更新系のMySQLに繋がったサーバを経由して書き込んでいました。
コメント機能についても同様の手順で実装できたと思います。
进行水平分割的代码 de
我已将以下代码加载到wp-config.php文件中。
// ラウンドロビンで回す秒数
define("_ROUND_ROBIN_TIME_", 180);
// metricは高い方が優先される
// ringをtrueにするとラウンドロビンに参加、falseなら不参加
$dbhosts = array
(0=>array
('host' => 'buren',
'metric' => 0,
'ring' => false,
),
1=>array
('host' => 'polk',
'metric' => 0,
'ring' => true,
//'ring' => false,
),
2=>array
('host' => 'tyler',
'metric' => 0,
//'ring' => true,
'ring' => false,
),
);
// getCurrentHostはラウンドロビン, getRandはランダム
//$key = getCurrentHost($dbhosts);
$key = getRand($dbhosts);
define('DB_HOST', $dbhosts[$key]['host']);
function getCurrentHost($dbhosts){
// 1970/01/01 00:00:00がスタート
$unixtime = time();
foreach($dbhosts as $key => $host){
if($host['ring']){
$keys[] = $key;
}
}
$kekey = ($unixtime/_ROUND_ROBIN_TIME_)%count($keys);
return $keys[$kekey];
}
// ランダムな振り分けを行ってくれる関数
function getRand($dbhosts){
$ttl = 0;
foreach($dbhosts as $dbhost){
$ttl += $dbhost['metric'];
$path[] = $ttl-1;
}
$rand = rand(0, $ttl-1);
foreach($path as $key => $val){
if($rand-1 < $val){
break;
}
}
$key;
//echo "$rand $key\n";
return $key;
}
...
include 'db-sort.php';
...
网络服务器
当時はapache+mod_php一択でした。運用開始後ようやくlighttpdが脚光を浴び始めた時代で、nginxはまだ日の目を見ていませんでした。
apacheのリバースプロキシを導入し、PHPを動作させるサーバを複数台用意していました。
ウェブサーバとMySQLが複数台ずつあったので、セション管理は大変そうです。
大変そう、と書いたのはサービスとしてログインなどのセション管理が無かったからです。この点は非常に助かりました。
静的ファイル化によるキャッシュは効果が高かったです。
うろ覚えですが当時はSuperCacheがあまり有名ではなかったこともあり、cronとwgetと.htaccessを組み合わせて自前でキャッシュしていました。
.htaccessを機械生成していたので数千行に及んでいたような気もしますが、それは夢だったのかもしれません。
网站地图
苦心努力中的一项工作是创建网站地图,这是因为我们依赖于搜索引擎的流量。因此,制作网站地图成为了我们的首要任务。但是,由于内容数量庞大,普通的WordPress插件无法完全满足我们的需求。最终,我们只能自己动手制作网站地图。
上の図はサイトマップの更新をやめてしまう前の情報で、300万件を送信していますね。
这个技巧现在是否还可以使用,有点难说。
以下は当時導入したテクニックです。今でも利用できるかどうかは疑問が残ります。
内存缓存
我们在MySQL中引入了memcache作为中间缓存。当时,在存在多台Web服务器的环境中,memcached非常流行,并且它提高了性能。
实际上,在WordPress中,它提供了使用memcached的机制(我记得有这样的功能),很容易就能引入。
虽然现在不确定是否还能使用,但也许它仍能有一定效果。
对于原物的护理
軽量化のためWordPressの本体も改造していました。
デフォルト状態だと目立って遅かったのがsingleの表示周りにある前後の記事を拾ってくる箇所でした。
この辺りについて必要の無い箇所はぶった切っていきました。
またquery stringを削って軽量化する仕組みも組み込んでいました。
しかしながら当然ですが、本体へ手を入れる、というのは引き返せない麻薬みたいなものです。
一度本体に手を入れてその速度を味わってしまうともう後戻りできません。
後戻り、というかバージョンアップができません。
WordPressは当時でも、現在でも、非常に精力的にバージョンアップを続けています。個人的にはそこがWordPressの魅力だと思っています。
バージョンアップ出来ない、と書きましたが、実際にはバージョンアップできるのですが本体に当ててしまった自作のパッチが足かせにり、頻繁に更新していたWordPress本体にパッチを書き続けることが不可能になりました。
そのため最終的には2.9.2で止まってしまいました。
最近ではフックの数が右肩上がりに増えており、重要な箇所にフックを入れてチューニングを含め様々なことができるようになりました。
しかしながら2015年現在でもフックの数は足りない、と個人的には思っています。
ただフックを増やすだけでは性能的、見通し的にに悪くなってきます。
本家のコードの傾向は追いかけられていませんが、クラスのインタフェース化などによるウォーカーなどを利用したオーバーライドで対応していけるのかなぁ、と考えています。
其他
这是关于当时环境等各种杂记的记录。
操作系统 (CZXT)
服务器的操作系统使用的是FreeBSD。
我们使用了一个叫做ports的软件包管理工具。
之前由于使用Redhat的rpm而陷入了依赖地狱,编译需要很长时间,这让我深受创伤,所以有段时间我一直在使用FreeBSD。
文件系统
データベースのファイルシステムにはFreeBSD/Solarisお得意のZFSを使っていました。
MySQLのレプリケーションが壊れることが多くてそのバックアップに活躍してくれました。
ZFSではHDDのスナップショットを取ることができるのですが、
MySQLダウン -> スナップショット -> MySQLスタート -> スナップショットの転送
とすることでコールドバックアップを手早く行っていました。
このやり方は他に聞いたこと無いですがなかなか良かったなぁと思っています。
资源监控
サービスの死活監視やリソースの履歴はcactiを利用してました。
リソースグラフがあればサーバの状態を相対的に見る事ができるので非常に便利でした。
MRTG+RRDToolに比べてすごく簡単になった!と喜んでいましたがcactiの運用は結構時間を取られることが多く、使いこなしているとは言えない情況でした。
現在ではmuninがお手軽で凄く良いですね。
現在でもアクセス系のGoogleAnalyticsなどとサーバリソースの対応は必ず取るようにした方が良いです。
サーバが重くて人が帰ってしまったのかどうかは計らなくてはなりません。
版本控制
我們採用了Subversion來進行版本管理。以前我們只是靠著模仿使用CSV,但並不善於運用它。
這個專案讓我們終於能夠使用Subversion了。
雖然常常被說是一個不好的例子,但版本管理也兼具了備份的功能。
版本管理對我們非常有幫助。
現在的話大概會選擇git吧。
项目管理工具,BTS
最初我使用的是Trac。
现在我使用的是Redmine。
在日本語搜索引擎上搜索
WordPressは日本語検索が遅いです。
(そういえば現在はどうなっているのか、もしかすると改善しているのかもしれませんね)
サービスを開始してすぐに日本語検索の遅さに困ることになりました。
こちらについては大量のデータを保持しているサービスの都合上、検索を重視していましたので検索が遅いことは致命的でした。
検索が遅いことの原因は日本語であるためにフルテキストインデックスが張れていないことです。
そこで日本語検索専用のシステムを構築しました。
サマリテーブルとよばれる、WordPressのテーブルから日本語検索の対象となるもののみを抜き出したテーブルを作成し、そこをフルテキストインデックスが張れるTritonnパッチドなMySQLサーバに置きました。
http://gihyo.jp/dev/clip/01/groonga/0006
(現在は標準でフルテキストインデックスが張れるようですね、良いな!)
https://www.google.co.jp/url?sa=t&rct=j&q=&esrc=s&source=web&cd=1&ved=0CB0QFjAAahUKEwjNmMrC697IAhUGEpQKHblNBow&url=http%3A%2F%2Fqiita.com%2FArimaRyunosuke%2Fitems%2Fd2b3b94f223cb83c463d&usg=AFQjCNEeg5NsjAnCv_U0w_wJpvlC1r1Stg&sig2=rlhDGIBo-0NYAOf16GZA7w&bvm=bv.105841590,d.dGo
WordPressの日本語検索をサマリテーブル参照にする部分については本体に手を加えることで実装しました。
サマリテーブルについては一時間おきに再生成しました。
再生成だけでも時間がかかるためtempテーブルに作成し、alter tableで入れ替えていました。
サマリテーブルを作ってalter tableで入れ替えるというのは便利でした。
更新程序
所有的更新程序都是用PHP编写的。我认为现在这项技术已经没有太大的价值了。
实现更新程序花费了最多的时间。
两位工程师的水平也并不是很高,他们是在摸索状态下开始工作的。
翻译规则和向数据库写入的操作也很复杂。
在PHP从4系变化到5系的时代,我提出了将其进行“类化”的理论,以便编写更好的代码,但我不确定这种理论是否正确。
结果,创建一个实例需要大约10秒钟的时间。仅仅实例化就需要10秒,真是太可怕了。
为了勉强应对,我们将PHP的处理系统守护化,并通过进程间通信来应对这个问题。
正好借鉴了apache的预派生模型。
虽然可能只有本人这么认为,但我相信现在可以写出更好的代码,即使没有进行预派生,实例化也应该更快。
社区 (shè qū)
我開始進行開發後不久,我參加了在大阪舉辦的WordPress使用者社群的交流會。由於之前從未參加過任何研討會,這是我第一次參加社群活動,讓我感到相當驚訝。最重要的是,我感到很安心,因為我不再孤單一人了。之後,我也參加了WordCamp和地方的WordBench。憑藉這些經驗,我後來成功在我居住的地區啟動了WordBench,並且在這裡也遇到了很多美好的人。在地方進行開發確實感到孤獨,但是研討會和講座確實令人感激。溫暖的社群關懷是WordPress的優點之一。
思考一下自己
我对导致最重要的更新程序出了问题深感反省。
我认为我们应该早期引入框架,并养成重构的习惯。
我认为没有经验丰富的人或技术专家在团队中也是困难的。我希望至少能以外部顾问的形式获得指导和审查意见。
由于硬盘故障和服务器突发的过载,我很快筋疲力尽,但总体来说还是很有趣的。
我们没能及时专注于监控方面,经常会遇到创建监控规则→更改程序或服务器配置→邮件被故障不断发送的情况。
我不知道我的邮件箱多少次被填满了。
网站的结束
公開から二年ほどでアクセス数が500万PV/月ぐらいにまで伸びました。しかしながらソースコードも構成もひどいもので、とても保守できませんでした。
マネタイズも失敗しまいた。エンジニア二人が食べて行くほどお金を稼ぐことができませんでした。
その後は放置する、ということで緩やかにサイトは閉鎖されていきました。
总结
初心者に毛が生えたぐらいのレベルでスタートでしたが、全ての期間を通じてエンジニアとしては非常に良い経験になりました。
特にramディスクは汎用性が高い上に効果が上がることが多く、最近もその手法でのりきりました。
静的ファイルのキャッシュはめちゃめちゃ早いです。
キャッシュはしっかりとした戦略をたてることで効果を発揮します。更新が多いサービスなのかどうか、どれぐらいキャッシュさせるべきかといったことを想定してキャッシュを組む必要があります。
当時は手動でキャッシュを作っていましたが現在ならWPSuerCacheがすごく便利ですね。
例えば
「MySQLのサーバが死んだ!半日ぐらいで復旧させなきゃ!」
「ISPから規制喰らったけど負荷分散させなきゃ!」
などとその時々で問題になる事柄に全力で取り組むことが勉強になった思っています。そしてまだまだ足りない部分もあるのでこれからも全力で取り組んで行かねばと思います。
改變我的初衷,本週末將舉辦WordCampTokyo2015。如果大家方便的話,請來參加。但很遺憾,已經額滿了。對於那些已報名參加的人們,謝謝你們的支持,請多多關照。