Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
/77
毎秒2000Reqを捌く
Perl製CMSの内部構造
nabe@abk
1
/77
Sec.1
はじめに
adiaryの紹介
2
/77
自己紹介
 名前:nabe
 ありきたりな名前にしたつもりが……
3
/77
自己紹介
 フリーランスエンジニア
 Perl
 JS, C, C#, C++, アセンブラ……
 Linux/FreeBSDサーバ管理
 DB設計、実装
 組み込み系プログラム
 回路設計、製作、アンプ、DAC……
 ...
/77
回路製作こんなの(ラズパイ用DAC)
5
/77
Perl製CMS「adiary」
 国産オープンソースCMS
 フルスクラッチ
 2006年から、ほぼ一人で開発
 HTML5準拠
 高機能
 超高速動作(ラズパイでも快適動作)
 pure Perlでも動作可能
 CG...
/77
adiaryの特徴
 インストールが超簡単
 見た目の変更が簡単
 テーマシステム
 デザイン編集機能
 何もしなくてもスマホ対応
 快適な画像管理(ファイラー)
 Markdown / はてな記法の超拡張
 他多数
7
/77
adiaryの細かい特徴
 マルチユーザー
 マルチブログ
 サイト引っ越し支援(画像取込)
 WebPush
 Google AMP(自動変換)
 Facebook OGP / Twitter Cards
 コンテンツ管...
/77
Sec.2
adiaryのフレームワーク
Satsuki-systemの概要
9
/77
Satsuki-system
 スケルトン機能が中心
 HTML + 埋め込み変数
 HTML::Templateなどの、テンプレートエ
ンジンに着想
10
<html>
<head><title>HTML::Template</...
/77
初期のスケルトン(2005年ごろ)
 埋め込み変数とハッシュの対応
11
<html>
<head><title><@title></title>
<body>
<p>こんにちは<@name>さん。 </p>
<p>今日は<@date...
/77
初期のスケルトン(2005年ごろ)
 計算ぐらいしたくない?
12
 コンパイラの本を片手に実装
 1行の構文解析
 逆ポーランド記法
 スタックマシン
<html>
<head><title><@title></title>...
/77
初期のスケルトン(2005年ごろ)
 どうせなら関数も呼び出しも……
13
 本に関数呼び出しなんて載ってない!
 短文解析で関数なんて想定外
<html>
<head><title><@title></title>
<body>...
/77
初期のスケルトン(2005年ごろ)
 if とか for も欲しい……
14
<p><@a=4>x^2 + <@b=7>x = <@c=20> の解</p>
<$y = b*b - 4*a*c>
<@ifexec(y>0, begin...
/77
初期のスケルトン(2005年ごろ)
15
HTML
処理済
HTML
構文解析
• ハッシュの埋め込み
• 関数の呼び出し
• 実行処理(インタプリタ)
出力
/77
初期のスケルトン(2005年ごろ)
16
http
アプリ
スケルトン
フレーム
ワーク
結果データ
出力
関数call
/77
スケルトンにできること
 式の記述(計算)
 関数の呼び出し
 制御構文
17
進化しすぎて
プログラムが書けてしまう
/77
改良スケルトン(2005年ごろ)
18
http
フレーム
ワーク
アプリ
スケルトン
出力
Load
main call
スケルトン
処理結果
dispatch
関数call
スケルトンが処理の中心
config file
/77
Satsuki-system全体像
 フレームワーク
 HTMLスケルトンの実行(config)
 ライブラリ郡
 mainルーチンの呼び出し
 アプリ
 アプリの初期処理
 対応スケルトンの呼び出し
 スケルトン(XX...
/77
スケルトンシステムのパラダイム
 アプリが対応するスケルトンを選択
 対応するスケルトンが処理の中心
 アプリもフレームワークも
スケルトンのための部品にすぎない
20
/77
adiaryの真相
 2005年 スケルトンシステム製作
 2006年
 スケルトンシステムがよく出来ていた
 何か作って良さをアピールしたい
 ブログが流行っていた
21
2006年末
adiary 1.00リリース
そうだ...
/77
なぜPerlだったのか
 2006年 Movable Typeが主流
 クソ重たかった
 開発前にベンチマークを取った
 Perl 5.6
 Ruby 1.8.x
 PHP5
 圧倒的にPerlが速かった
 その後も、P...
/77
Sec.3
スケルトンの高速化
インタプリタからコンパイラへ
23
/77
初期のスケルトンの問題点
 構文解析が遅い
 正規表現の塊
 開発が進み高機能になるほど遅くなる
24
解析結果をキャッシュしよう
/77
スケルトンのキャッシュ方法
 案1)逆ポーランド式での保存
 配列やハッシュをそのまま保存できない
 テキストで保存するしかない
 読み込む時にテキスト解析が必要
 案2)Perlのプログラムとして保存
 読み込んでeval...
/77
スケルトンコンパイラ
1. スケルトンファイルを読み込む
2. 構文解析する
3. Perlプログラムにコンパイル
4. ファイルにキャッシュ保存
5. Perlプログラムをevalでコンパイル
6. Perlコードをメモリにキャッシュ...
/77
コンパイルの様子(ソース)
27
<$a=4>
<$b=75>
<$c=20>
<$y = b*b - 4*a*c>
<@ifexec(y>0, begin)>
<p>x = <@(-b+sqrt(y))/(2*a)>, <@(-b-s...
/77
コンパイル(Step1 前処理)
28
0002 :: <$a=4>
0003 :: <$b=75>
0004 :: <$c=20>
0005 :: <$y = b*b - 4*a*c>
0006 :: <@ifexec(y>0, be...
/77
コンパイル(Step2 式解析)
29
0002 p)$ a 4 =
0003 p)$ b 75 =
0004 p)$ c 20 =
0005 p)$ y b b * 4 a * c * - =
0006 p)@ ifexec y 0 ...
/77
コンパイル(Step3 コンパイル)
30
0002 e$ $R->{a}=4
0003 e$ $R->{b}=75
0004 e$ $R->{c}=20
0005 e$ $R->{y}=(($R->{b}*$R->{b})-((4*$...
/77
コンパイル(Step4 ブロック抽出)
31
0002 e$ $R->{a}=4
0003 e$ $R->{b}=75
0004 e$ $R->{c}=20
0005 e$ $R->{y}=(($R->{b}*$R->{b})-((4*...
/77
sub {
my $O = shift;
my $R = shift;
my $v = $R->{v};
$_[1] = $v;
$R->{a}=4;
$R->{b}=75;
$R->{c}=20;
$R->{y}=(($R->{b}*...
/77
sub {
my $O = shift;
my $R = shift;
my $v = $R->{v};
$_[1] = $v;
$R->{a}=4;
:
}
Perlコードのコンパイルと実行
33
my $func;
eval "$f...
/77
スケルトンまとめ
 スケルトンはフレームワークの中核
 Ruby on Railsで言うところの rhtml
 高度な処理を記述できる
 最終的にPerlの関数(コード)になる
 しかも関数の状態でキャッシュされる
34
だか...
/77
Sec.4
ファイルのロードを速くする
徹底的にキャッシュする
35
/77
ファイルの読み込みは遅い
 1ファイル=1ms以上
 読み込みはキャッシュしよう
36
my %FileCache;
sub fread_lines_cached {
my ($self, $file) = @_;
my $c = ...
/77
標準データ形式
 key=value 形式のデータファイル
 ハッシュをそのまま保存
37
#------------------------------
# system default config file
#--------...
/77
単なるキャッシュでいいのか?
 ファイルの保存形式は色々
 解析処理が必ず入る
38
ファイル
入力
キャッシュ
ファイル
解析
ファイル
入力
ファイル
解析
キャッシュ
/77
解析結果キャッシュ
39
my %FileCache;
sub fread_lines_cached {
my ($self, $file, $PostProcessor) = @_;
my $c = $FileCache{"$file...
/77
解析結果キャッシュ付ファイル入力
 fread_lines_cached() 関数
 ファイル名と解析ルーチンを渡す
 キャッシュ処理はおまかせ
 ファイルの更新日時チェックだけ
 更新日時チェックも実はちょっと遅い
 基本...
/77
もっと高負荷なファイル処理がある
 ファイル入力より重いファイルロード
41
Perl モジュール(.pm)が
一番重い
※DynaLoaderなんて最悪に重い……
/77
モジュールロードは遅い
42
#
foreach(sort(keys(%INC))) {
print "$_n";
}
use Image::Magick;
foreach(sort(keys(%INC))) {
print "$_n"...
/77
モジュールロードはなぜ遅い
43
ファイル
読み込み
構文解析
(コンパイル)
インポート
処理
依存関係
ロード
/77
気にしないという手も……
 SpeedyCGI, FastCGI, mod_perl2
 Perlキャッシュ環境では関係ない
 Satsuki-systemはすべてに対応!
 CGI動作は捨てても良い?
 個人レベルではCGI...
/77
CGIでも速く動かすために
45
 モジュールは必要な時にロードする
 use ではなく require
 インポート処理をさせない
 use Encode; from_to(); ではなく
 require Encode; ...
/77
Satsuki::AutoLoader
46
 メインモジュールは3分割されている
 Base.pm GET関連の処理
 Base_2.pm POST関連の処理
 Base_3.pm FastCGI/mod_perl2関連
 ...
/77
FastCGI, mod_perl2にも問題あり
47
 モジュールをキャッシする
 ロードが発生しないので高速
 当然リロードもしない
 アップデート時や開発時に困る
 ライブラリ更新チェック機能を内蔵
 更新時は自動でリ...
/77
ライブラリunload(自作コード)
48
sub unload {
no strict 'refs';
my $pkg = shift;
$pkg =~ s/.pm$//;
$pkg =~ s[/][::]g;
my $names =...
/77
ファイルロードのまとめ
 ファイルの入力はキャッシュする
 モジュールのロードは遅い
 必要になったらロードする
 あらかじめ分割しておく
49
/77
Sec.5
データベースは必要ですか?
テキストファイルで作るデータベース
50
/77
RDBMSは遅い
Relational DataBase Management System
 高度な関係性(Relation)を記述
 高信頼性やスケール化は求めてない
 欠点
 高度でリッチすぎる
 SQL文の字句解析が入...
/77
DBモジュール
 DBのAPIを統一しモジュール化
 Satsuki::DB_mysql
 Satsuki::DB_pg
 Satsuki::DB_text
 入れ替え可能
 CMSにSQLなんて必要ない
52
/77
テキストDB
 テキストファイルで作ったDB System
 1テーブル=1ディレクトリ
53
size filename
636 #index.backup.dat
11791 #index.dat
6781 00375.dat
...
/77
テキストDB の index
54
4 ←DB Version
R14925654360.0138001278793567 ←nonce(更新検出用)
00000199 ←pkeyの最大値
(略) ←テーブル情報
pkey a_pkey...
/77
テキストDBの検索(Ver2まで)
55
 index を使いonメモリ検索
OUT_LOOP:
foreach my $h (@db) {
foreach (@mints) { # 数値マッチ
if ($h->{$_} != $mi...
/77
テキストDBの新しい検索(Ver3)
56
my $func=<<FUNCTION_TEXT;
sub {
my ($self, $db) = @_;
my @newary;
foreach(@$db) {
if ($cond) { #...
/77
テキストDBの更新処理
 index = 1ファイル
 検索(読み込み)が速い
 更新(書き換え)が遅い
 indexファイルを lock してすべて再生成
 更新ごとに、必ずHDDに同期させる
 アクセス数に対する更新の発...
/77
 更新が大量に発生すると問題になる
 記事のインポート
 記事の再構築
 なぜ問題になる?
 1記事ごとにHDDに同期させるから遅い
テキストDBの更新問題
58
メモリ中で書き換えを行い
最後に結果を反映させれば良いのでは?...
/77
RDBMSのトランザクション
59
 all or nothing な変更機能
> BEGIN;
> INSERT INTO test(name, price) VALUES('りんご', 200);
> INSERT INTO te...
/77
テキストDBのトランザクション
60
 更新ファイルをすべて lock
 更新データをすべてメモリに展開
 commit() 時に一気に書き換える
 失敗時は全データ破棄
100倍の高速化(記事が多いとき)
/77
テキストDBまとめ
61
 テキストファイルでもDBになる
 数百件ならRDBMSより速い
 検索の2重ループは遅い
 検索関数を動的生成して高速化
 大量の更新処理が遅い
 トランザクションを実装して高速化
/77
Sec.6
その他の高速化
小さなことでもコツコツと
62
/77
高速化の原則
 更新(POST)は表示(GET)の1%以下
 表示に必要なデータは更新時に生成し
キャッシュする
 最近の記事一覧
 最近のコメント一覧
 タグ一覧
 月別の過去ログ
 ほか
63
/77
こんなところまでキャッシュ
 タグごとに所属する記事のすべて
 コンテンツのツリー情報
 すべての記事について
「次の記事」「前の記事」
64
/77
クライアントで処理させる
 サーバ資源は希少
 クライアント資源は余裕あり
 ブラウザで処理できることはブラウザで
65
/77
ファイル数を少なくする
 処理に必要なファイル数を減らす
 いくらキャッシュが効いても……
 タイムスタンプ確認も速くはない
 ブログ設定は1ファイルに集約
 中身はハッシュ(標準データ形式)
 Windowsのレジストリみ...
/77
Sec.7
計測
そして禁断の果実へ……
67
/77
ベンチマーク方法
 Debian jessie
 Core i3-530(2010年製/第1世代)
 RAM 4GB
 Apache 2.4 + mod_perl2
 nginx + FastCGIでも似た結果
68
/77
テスト方法
 adiary公式サイトのコピーを作成
 テキストDB使用
 トップページを表示テスト
 ApacheBenchをローカルで発行
 ab -n 1000 -c 100 –l
 1000アクセス、同時100アクセス...
/77
ベンチマーク
70
Concurrency Level: 100
Time taken for tests: 1.418 seconds
Complete requests: 1000
Failed requests: 0
Total ...
/77
禁断の果実 ページキャッシュ
 ページの表示アクセスをキャッシュ
 「W○rdpress 1000倍高速化」では……
 キャッシュ用プラグイン
 または nginxの fastcgi_cache を使う
 fastcgi_ca...
/77
ページキャッシュ内蔵してます
 管理画面から設定をonにするだけ
 表示処理で問題ない時のみ動作
 ログイン時は動作しない
 キャッシュ数、時間に上限設定あり
 更新時はキャッシュを破棄
72
/77
ベンチマーク(ページキャッシュ有)
73
Concurrency Level: 100
Time taken for tests: 0.392 seconds
Complete requests: 1000
Failed request...
/77
Sec.8
まとめ
74
/77
高速化のポイント
 高速化するのは表示処理
 遅い処理を調べる
 ファイルアクセスは遅い
 RDBMSは高コスト
 キャッシュを有効活用
 解析済ファイル、コンパイル済コード…
 キャッシュは一番効率のよい状態で
 ペー...
/77
ひとことで言うと
Perlは速い
76
/7777
おわり
Upcoming SlideShare
Loading in …5
×

毎秒2000Requestを捌くPerl製CMSの内部構造(Debianサーバ1台にて)

6,385 views

Published on

圧倒的な速度で動作するadiaryの仕組みを解説します。勉強会等でスライド発表の要望がありましたらご連絡ください。

Published in: Software

毎秒2000Requestを捌くPerl製CMSの内部構造(Debianサーバ1台にて)

  1. 1. /77 毎秒2000Reqを捌く Perl製CMSの内部構造 nabe@abk 1
  2. 2. /77 Sec.1 はじめに adiaryの紹介 2
  3. 3. /77 自己紹介  名前:nabe  ありきたりな名前にしたつもりが…… 3
  4. 4. /77 自己紹介  フリーランスエンジニア  Perl  JS, C, C#, C++, アセンブラ……  Linux/FreeBSDサーバ管理  DB設計、実装  組み込み系プログラム  回路設計、製作、アンプ、DAC……  営業苦手 4 ※お仕事募集してます
  5. 5. /77 回路製作こんなの(ラズパイ用DAC) 5
  6. 6. /77 Perl製CMS「adiary」  国産オープンソースCMS  フルスクラッチ  2006年から、ほぼ一人で開発  HTML5準拠  高機能  超高速動作(ラズパイでも快適動作)  pure Perlでも動作可能  CGI, FastCGI, mod_perl全対応 6
  7. 7. /77 adiaryの特徴  インストールが超簡単  見た目の変更が簡単  テーマシステム  デザイン編集機能  何もしなくてもスマホ対応  快適な画像管理(ファイラー)  Markdown / はてな記法の超拡張  他多数 7
  8. 8. /77 adiaryの細かい特徴  マルチユーザー  マルチブログ  サイト引っ越し支援(画像取込)  WebPush  Google AMP(自動変換)  Facebook OGP / Twitter Cards  コンテンツ管理(ツリー管理)  階層式タグ機能  プライベート機能  強力な記事検索  外部DB不要 / PostgreSQL , MySQL対応  TeX数式標準対応 8
  9. 9. /77 Sec.2 adiaryのフレームワーク Satsuki-systemの概要 9
  10. 10. /77 Satsuki-system  スケルトン機能が中心  HTML + 埋め込み変数  HTML::Templateなどの、テンプレートエ ンジンに着想 10 <html> <head><title>HTML::Template</title> <body> My Home Directory is <TMPL_VAR NAME=HOME> <br> My Path is set to <TMPL_VAR NAME=PATH> <br> </body> </html>
  11. 11. /77 初期のスケルトン(2005年ごろ)  埋め込み変数とハッシュの対応 11 <html> <head><title><@title></title> <body> <p>こんにちは<@name>さん。 </p> <p>今日は<@date>の<@time>です。</p> </body> </html> sub parse_html { my $lines = shift; # HTMLファイル内容 my $hash = shift; # 置換情報 foreach(@$lines) { $_ =~ s/<@(w+)>/$hash->{$1}/g; } }
  12. 12. /77 初期のスケルトン(2005年ごろ)  計算ぐらいしたくない? 12  コンパイラの本を片手に実装  1行の構文解析  逆ポーランド記法  スタックマシン <html> <head><title><@title></title> <body> <$x = 5> <$y = 7> <p><@x>^2 + <@y>^2 = <@x*x + y*y></p> </body> </html>
  13. 13. /77 初期のスケルトン(2005年ごろ)  どうせなら関数も呼び出しも…… 13  本に関数呼び出しなんて載ってない!  短文解析で関数なんて想定外 <html> <head><title><@title></title> <body> <$x = 5> <$y = 7> <p>底辺<@x>と高さ<@y>の直角三角形の長辺 = <@sqrt(x*x + y*y)></p> </body> </html>
  14. 14. /77 初期のスケルトン(2005年ごろ)  if とか for も欲しい…… 14 <p><@a=4>x^2 + <@b=7>x = <@c=20> の解</p> <$y = b*b - 4*a*c> <@ifexec(y>0, begin)> <p>x = <@(-b+sqrt(y))/(2*a)>, <@(-b-sqrt(y))/(2*a)></p> <$end> <@ifexec(y==0, begin)> <p>x = <@-b/(2*a)>(重解)</p> <$end> <ul> <@forexec(x, lines)> <li><@x.num> : <@x.name> <$end> </ul>
  15. 15. /77 初期のスケルトン(2005年ごろ) 15 HTML 処理済 HTML 構文解析 • ハッシュの埋め込み • 関数の呼び出し • 実行処理(インタプリタ) 出力
  16. 16. /77 初期のスケルトン(2005年ごろ) 16 http アプリ スケルトン フレーム ワーク 結果データ 出力 関数call
  17. 17. /77 スケルトンにできること  式の記述(計算)  関数の呼び出し  制御構文 17 進化しすぎて プログラムが書けてしまう
  18. 18. /77 改良スケルトン(2005年ごろ) 18 http フレーム ワーク アプリ スケルトン 出力 Load main call スケルトン 処理結果 dispatch 関数call スケルトンが処理の中心 config file
  19. 19. /77 Satsuki-system全体像  フレームワーク  HTMLスケルトンの実行(config)  ライブラリ郡  mainルーチンの呼び出し  アプリ  アプリの初期処理  対応スケルトンの呼び出し  スケルトン(XXX.html)  対応する処理と結果の出力 19
  20. 20. /77 スケルトンシステムのパラダイム  アプリが対応するスケルトンを選択  対応するスケルトンが処理の中心  アプリもフレームワークも スケルトンのための部品にすぎない 20
  21. 21. /77 adiaryの真相  2005年 スケルトンシステム製作  2006年  スケルトンシステムがよく出来ていた  何か作って良さをアピールしたい  ブログが流行っていた 21 2006年末 adiary 1.00リリース そうだ、 ブログシステムを 作ろう!
  22. 22. /77 なぜPerlだったのか  2006年 Movable Typeが主流  クソ重たかった  開発前にベンチマークを取った  Perl 5.6  Ruby 1.8.x  PHP5  圧倒的にPerlが速かった  その後も、Perlはバージョンアップする ごとに速くなっている 22
  23. 23. /77 Sec.3 スケルトンの高速化 インタプリタからコンパイラへ 23
  24. 24. /77 初期のスケルトンの問題点  構文解析が遅い  正規表現の塊  開発が進み高機能になるほど遅くなる 24 解析結果をキャッシュしよう
  25. 25. /77 スケルトンのキャッシュ方法  案1)逆ポーランド式での保存  配列やハッシュをそのまま保存できない  テキストで保存するしかない  読み込む時にテキスト解析が必要  案2)Perlのプログラムとして保存  読み込んでevalするだけ! 25
  26. 26. /77 スケルトンコンパイラ 1. スケルトンファイルを読み込む 2. 構文解析する 3. Perlプログラムにコンパイル 4. ファイルにキャッシュ保存 5. Perlプログラムをevalでコンパイル 6. Perlコードをメモリにキャッシュ 7. コードを実行 26 メモリキャッシュが存在すれば 高速に実行できる!
  27. 27. /77 コンパイルの様子(ソース) 27 <$a=4> <$b=75> <$c=20> <$y = b*b - 4*a*c> <@ifexec(y>0, begin)> <p>x = <@(-b+sqrt(y))/(2*a)>, <@(-b-sqrt(y))/(2*a)></p> <$end> <@ifexec(y==0, begin)> <p>x = <@-b/(2*a)></p> <$end>
  28. 28. /77 コンパイル(Step1 前処理) 28 0002 :: <$a=4> 0003 :: <$b=75> 0004 :: <$c=20> 0005 :: <$y = b*b - 4*a*c> 0006 :: <@ifexec(y>0, begin)> 0007 :: <p>x = 0007 :: <@(-b+sqrt(y))/(2*a)> 0007 :: , 0007 :: <@(-b-sqrt(y))/(2*a)> 0007 :: </p>n 0008 :: <$end> 0009 :: <@ifexec(y==0, begin)> 0010 :: <p>x = 0010 :: <@-b/(2*a)> 0010 :: </p>n 0011 :: <$end>
  29. 29. /77 コンパイル(Step2 式解析) 29 0002 p)$ a 4 = 0003 p)$ b 75 = 0004 p)$ c 20 = 0005 p)$ y b b * 4 a * c * - = 0006 p)@ ifexec y 0 > begin , %r 0007 :: <p>x = 0007 p)@ b %m sqrt y %r + 2 a * / 0007 :: , 0007 p)@ b %m sqrt y %r - 2 a * / 0007 :: </p>n 0008 p)$ end 0009 p)@ ifexec y 0 == begin , %r 0010 :: <p>x = 0010 p)@ b %m 2 a * / 0010 :: </p>n 0011 p)$ end
  30. 30. /77 コンパイル(Step3 コンパイル) 30 0002 e$ $R->{a}=4 0003 e$ $R->{b}=75 0004 e$ $R->{c}=20 0005 e$ $R->{y}=(($R->{b}*$R->{b})-((4*$R->{a})*$R->{c})) 0006 e@ ifexec($R->{y}>0,[begin]) 0007 :: <p>x = 0007 e@ ((-$R->{b})+sqrt($R->{y}))/(2*$R->{a}) 0007 :: , 0007 e@ ((-$R->{b})-sqrt($R->{y}))/(2*$R->{a}) 0007 :: </p>n 0008 e$ end 0009 e@ ifexec($R->{y}==0,[begin]) 0010 :: <p>x = 0010 e@ (-$R->{b})/(2*$R->{a}) 0010 :: </p>n 0011 e$ end
  31. 31. /77 コンパイル(Step4 ブロック抽出) 31 0002 e$ $R->{a}=4 0003 e$ $R->{b}=75 0004 e$ $R->{c}=20 0005 e$ $R->{y}=(($R->{b}*$R->{b})-((4*$R->{a})*$R->{c})) 0006 e$ if ($R->{y}>0) { ---- :: <p>x = 0007 e@ ((-$R->{b})+sqrt($R->{y}))/(2*$R->{a}) ---- :: , 0007 e@ ((-$R->{b})-sqrt($R->{y}))/(2*$R->{a}) ---- :: </p>n 0000 e$ } 0009 e$ if ($R->{y}==0) { ---- :: <p>x = 0010 e@ (-$R->{b})/(2*$R->{a}) ---- :: </p>n 0000 e$ }
  32. 32. /77 sub { my $O = shift; my $R = shift; my $v = $R->{v}; $_[1] = $v; $R->{a}=4; $R->{b}=75; $R->{c}=20; $R->{y}=(($R->{b}*$R->{b})-((4*$R->{a})*$R->{c})); if ($R->{y}>0) { push(@$O, ' <p>x = '); $_[0]=7; push(@$O, ((-$R->{b})+sqrt($R->{y}))/(2*$R->{a})); push(@$O, ', '); $_[0]=7; push(@$O, ((-$R->{b})-sqrt($R->{y}))/(2*$R->{a})); push(@$O, '</p>n'); } if ($R->{y}==0) { push(@$O, ' <p>x = '); push(@$O, (-$R->{b})/(2*$R->{a})); push(@$O, '</p>n'); } } コンパイル(Step5 コード化) 32 <$a=4> <$b=75> <$c=20> <$y = b*b - 4*a*c> <@ifexec(y>0, begin)> <p>x = <@(-b+sqrt(y))/(2*a)>, <@(-b-sqrt(y))/(2*a)></p> <$end> <@ifexec(y==0, begin)> <p>x = <@-b/(2*a)></p> <$end>
  33. 33. /77 sub { my $O = shift; my $R = shift; my $v = $R->{v}; $_[1] = $v; $R->{a}=4; : } Perlコードのコンパイルと実行 33 my $func; eval "$func = $code"; コンパイル &$func(@output, $self, $line, $v_ref); # @output 出力 # $self ルートオブジェクト # $line 行番号 実行
  34. 34. /77 スケルトンまとめ  スケルトンはフレームワークの中核  Ruby on Railsで言うところの rhtml  高度な処理を記述できる  最終的にPerlの関数(コード)になる  しかも関数の状態でキャッシュされる 34 だから速い
  35. 35. /77 Sec.4 ファイルのロードを速くする 徹底的にキャッシュする 35
  36. 36. /77 ファイルの読み込みは遅い  1ファイル=1ms以上  読み込みはキャッシュしよう 36 my %FileCache; sub fread_lines_cached { my ($self, $file) = @_; my $c = $FileCache{$file} ||= {}; my $mod = (stat($file))[9]; if (0<$mod && $mod == $c->{modified} && $mod == $c->{modified}) { return $c->{lines}; } # ファイルから読み込み $lines = $self->fread_lines( $file ); $cache->{$key} = {lines => $lines, modified => $mod }; return $lines; }
  37. 37. /77 標準データ形式  key=value 形式のデータファイル  ハッシュをそのまま保存 37 #------------------------------ # system default config file #------------------------------ create_blog_only_admin=0 reload_time=3 http_timeout=3 *ping_servers_txt=<<__END_BLK_DATA # Google blog 検索 http://blogsearch.google.co.jp/ping/RPC2#ex # adiary official http://ping.adiary.org/ping/#adiary __END_BLK_DATA
  38. 38. /77 単なるキャッシュでいいのか?  ファイルの保存形式は色々  解析処理が必ず入る 38 ファイル 入力 キャッシュ ファイル 解析 ファイル 入力 ファイル 解析 キャッシュ
  39. 39. /77 解析結果キャッシュ 39 my %FileCache; sub fread_lines_cached { my ($self, $file, $PostProcessor) = @_; my $c = $FileCache{"$file//$PostProcessor"} ||= {}; my $mod = (stat($file))[9]; if (0<$mod && $mod == $c->{modified} && $mod == $c->{modified}) { return $c->{lines}; } # ファイルから読み込み $lines = $self->fread_lines( $file ); &$PostProcessor($self, $lines); $cache->{$key} = {lines => $lines, modified => $mod }; return $lines; }
  40. 40. /77 解析結果キャッシュ付ファイル入力  fread_lines_cached() 関数  ファイル名と解析ルーチンを渡す  キャッシュ処理はおまかせ  ファイルの更新日時チェックだけ  更新日時チェックも実はちょっと遅い  基本的にシステムコールは遅い 40
  41. 41. /77 もっと高負荷なファイル処理がある  ファイル入力より重いファイルロード 41 Perl モジュール(.pm)が 一番重い ※DynaLoaderなんて最悪に重い……
  42. 42. /77 モジュールロードは遅い 42 # foreach(sort(keys(%INC))) { print "$_n"; } use Image::Magick; foreach(sort(keys(%INC))) { print "$_n"; } $ time test.pl /etc/perl/sitecustomize.pl real 0m0.003s $ time test.pl /etc/perl/sitecustomize.pl AutoLoader.pm Carp.pm Config.pm DynaLoader.pm Exporter.pm Image/Magick.pm Image/Magick/Q16.pm parent.pm strict.pm vars.pm warnings.pm warnings/register.pm real 0m0.026s
  43. 43. /77 モジュールロードはなぜ遅い 43 ファイル 読み込み 構文解析 (コンパイル) インポート 処理 依存関係 ロード
  44. 44. /77 気にしないという手も……  SpeedyCGI, FastCGI, mod_perl2  Perlキャッシュ環境では関係ない  Satsuki-systemはすべてに対応!  CGI動作は捨てても良い?  個人レベルではCGIもまだまだ現役  adiary利用者の8割はCGI動作 44 CGIでも速く動かしたい!
  45. 45. /77 CGIでも速く動かすために 45  モジュールは必要な時にロードする  use ではなく require  インポート処理をさせない  use Encode; from_to(); ではなく  require Encode; Encode::from_to(); で  重いモジュールは使わない  車輪の再発明も辞さない  AutoLoader.pm によるモジュール分割
  46. 46. /77 Satsuki::AutoLoader 46  メインモジュールは3分割されている  Base.pm GET関連の処理  Base_2.pm POST関連の処理  Base_3.pm FastCGI/mod_perl2関連  最初は Base.pm  関数が見つからない時  Base_2.pm, Base_3.pmと順にロード  最後まで見つからなければエラー
  47. 47. /77 FastCGI, mod_perl2にも問題あり 47  モジュールをキャッシする  ロードが発生しないので高速  当然リロードもしない  アップデート時や開発時に困る  ライブラリ更新チェック機能を内蔵  更新時は自動でリロード  システムの再起動は不要
  48. 48. /77 ライブラリunload(自作コード) 48 sub unload { no strict 'refs'; my $pkg = shift; $pkg =~ s/.pm$//; $pkg =~ s[/][::]g; my $names = %{ $pkg . '::' }; # パッケージの名前空間からすべて除去 foreach(keys(%$names)) { substr($_,-2) eq '::' && next; undef $names->{$_}; # 全型の変数開放 } } ただの魔法(笑)
  49. 49. /77 ファイルロードのまとめ  ファイルの入力はキャッシュする  モジュールのロードは遅い  必要になったらロードする  あらかじめ分割しておく 49
  50. 50. /77 Sec.5 データベースは必要ですか? テキストファイルで作るデータベース 50
  51. 51. /77 RDBMSは遅い Relational DataBase Management System  高度な関係性(Relation)を記述  高信頼性やスケール化は求めてない  欠点  高度でリッチすぎる  SQL文の字句解析が入る  DBモジュールのロードが超遅い  30~50ms  使うなら FastCGI や mod_perl2 とかで…… 51
  52. 52. /77 DBモジュール  DBのAPIを統一しモジュール化  Satsuki::DB_mysql  Satsuki::DB_pg  Satsuki::DB_text  入れ替え可能  CMSにSQLなんて必要ない 52
  53. 53. /77 テキストDB  テキストファイルで作ったDB System  1テーブル=1ディレクトリ 53 size filename 636 #index.backup.dat 11791 #index.dat 6781 00375.dat 12813 00376.dat 14648 00377.dat 785 00378.dat 474 00379.dat 8235 00380.dat 533 00381.dat 24783 00383.dat 533 00384.dat : : indexファイル index バックアップ row(行) ファイル enable=1 yyyymmdd=20070608 name=adiary description=マニュアルに移動しました tags=未分類,import link_key=0378 parser=default_p1 update_tm=1478689314 pkey=378 tm=1181315372 title=記事のタイトル priority=0 *text=<<__END_BLK_DATA <section> <p><a href="http://adiary.org/man/mimetex" >マニュアルに移動しました</a></p> </section> __END_BLK_DATA
  54. 54. /77 テキストDB の index 54 4 ←DB Version R14925654360.0138001278793567 ←nonce(更新検出用) 00000199 ←pkeyの最大値 (略) ←テーブル情報 pkey a_pkey a_yyyymmdd enable hidden name num tm 128 401 20130812 1 0 なべ 1 1421354075 135 436 20150515 1 0 なべ 2 1431940142 140 438 20150519 1 0 テスト 1 1432139938 143 438 20150519 1 0 テスト 3 1432140374 193 488 20160107 1 0 abcabc 5 1486476953 195 495 20170107 1 0 quertyxx 1 1486476987 199 437 20170126 1 0 なべ 4 1488528333  TAB区切りの index 一覧  indexはキャッシュされる  検索時のrowロードがほぼ不要
  55. 55. /77 テキストDBの検索(Ver2まで) 55  index を使いonメモリ検索 OUT_LOOP: foreach my $h (@db) { foreach (@mints) { # 数値マッチ if ($h->{$_} != $mint_h->{$_}) { next OUT_LOOP; } } foreach (@mstrs) { # 文字列マッチ if ($h->{$_} ne $mstr_h->{$_}) { next OUT_LOOP; } } foreach (@flags) { # フラグマッチ if ($h->{$_} != $flag_h->{$_}) { next OUT_LOOP; } } push(@ary, $h); ## 全条件にマッチ } 2重ループが遅すぎる!!
  56. 56. /77 テキストDBの新しい検索(Ver3) 56 my $func=<<FUNCTION_TEXT; sub { my ($self, $db) = @_; my @newary; foreach(@$db) { if ($cond) { # ←検索条件 push(@newary, $_); } } return @newary; } FUNCTION_TEXT $func = $self->eval_and_cache($func); $db = &$func($self, $db); 検索関数を動的生成し、 コンパイル(eval)結果をキャッシュ
  57. 57. /77 テキストDBの更新処理  index = 1ファイル  検索(読み込み)が速い  更新(書き換え)が遅い  indexファイルを lock してすべて再生成  更新ごとに、必ずHDDに同期させる  アクセス数に対する更新の発生率  多くて1%程度  数千件ぐらいまでは問題ない 57
  58. 58. /77  更新が大量に発生すると問題になる  記事のインポート  記事の再構築  なぜ問題になる?  1記事ごとにHDDに同期させるから遅い テキストDBの更新問題 58 メモリ中で書き換えを行い 最後に結果を反映させれば良いのでは? つまり トランザクション
  59. 59. /77 RDBMSのトランザクション 59  all or nothing な変更機能 > BEGIN; > INSERT INTO test(name, price) VALUES('りんご', 200); > INSERT INTO test(name, price) VALUES('バナナ', 98); > INSERT INTO test(name, price) VALUES('梨', 398); > ROLLBACK; > SELECT * FROM test; pkey | name | price ------+------+------- > BEGIN; > INSERT INTO test(name, price) VALUES('なす', 99); > INSERT INTO test(name, price) VALUES('きゅうり', 128); > INSERT INTO test(name, price) VALUES('トマト', 298); > COMMIT; > SELECT * FROM test; pkey | name | price ------+----------+------- 4 | なす | 99 5 | きゅうり | 128 6 | トマト | 298
  60. 60. /77 テキストDBのトランザクション 60  更新ファイルをすべて lock  更新データをすべてメモリに展開  commit() 時に一気に書き換える  失敗時は全データ破棄 100倍の高速化(記事が多いとき)
  61. 61. /77 テキストDBまとめ 61  テキストファイルでもDBになる  数百件ならRDBMSより速い  検索の2重ループは遅い  検索関数を動的生成して高速化  大量の更新処理が遅い  トランザクションを実装して高速化
  62. 62. /77 Sec.6 その他の高速化 小さなことでもコツコツと 62
  63. 63. /77 高速化の原則  更新(POST)は表示(GET)の1%以下  表示に必要なデータは更新時に生成し キャッシュする  最近の記事一覧  最近のコメント一覧  タグ一覧  月別の過去ログ  ほか 63
  64. 64. /77 こんなところまでキャッシュ  タグごとに所属する記事のすべて  コンテンツのツリー情報  すべての記事について 「次の記事」「前の記事」 64
  65. 65. /77 クライアントで処理させる  サーバ資源は希少  クライアント資源は余裕あり  ブラウザで処理できることはブラウザで 65
  66. 66. /77 ファイル数を少なくする  処理に必要なファイル数を減らす  いくらキャッシュが効いても……  タイムスタンプ確認も速くはない  ブログ設定は1ファイルに集約  中身はハッシュ(標準データ形式)  Windowsのレジストリみたいなもの 66
  67. 67. /77 Sec.7 計測 そして禁断の果実へ…… 67
  68. 68. /77 ベンチマーク方法  Debian jessie  Core i3-530(2010年製/第1世代)  RAM 4GB  Apache 2.4 + mod_perl2  nginx + FastCGIでも似た結果 68
  69. 69. /77 テスト方法  adiary公式サイトのコピーを作成  テキストDB使用  トップページを表示テスト  ApacheBenchをローカルで発行  ab -n 1000 -c 100 –l  1000アクセス、同時100アクセス  アクセス先は ethernet を設定 69
  70. 70. /77 ベンチマーク 70 Concurrency Level: 100 Time taken for tests: 1.418 seconds Complete requests: 1000 Failed requests: 0 Total transferred: 15058685 bytes HTML transferred: 14823685 bytes Requests per second: 705.04 [#/sec] (mean) Time per request: 141.836 [ms] (mean) Time per request: 1.418 [ms] (mean) Transfer rate: 10368.16 [Kbytes/sec] received 動的生成で 705Req/秒 は驚異的! …… ………………2000Req/秒とは? ……
  71. 71. /77 禁断の果実 ページキャッシュ  ページの表示アクセスをキャッシュ  「W○rdpress 1000倍高速化」では……  キャッシュ用プラグイン  または nginxの fastcgi_cache を使う  fastcgi_cache は 10分~1時間程度、何が あってもキャッシュを返す。  たとえページが消えていても!  表示ごとに変化させたり動作させたり 出来なくなる 71
  72. 72. /77 ページキャッシュ内蔵してます  管理画面から設定をonにするだけ  表示処理で問題ない時のみ動作  ログイン時は動作しない  キャッシュ数、時間に上限設定あり  更新時はキャッシュを破棄 72
  73. 73. /77 ベンチマーク(ページキャッシュ有) 73 Concurrency Level: 100 Time taken for tests: 0.392 seconds Complete requests: 1000 Failed requests: 0 Total transferred: 15059400 bytes HTML transferred: 14824400 bytes Requests per second: 2553.29 [#/sec] (mean) Time per request: 39.165 [ms] (mean) Time per request: 0.392 [ms] (mean) Transfer rate: 37549.87 [Kbytes/sec] received 無事「2500Req/秒」を達成
  74. 74. /77 Sec.8 まとめ 74
  75. 75. /77 高速化のポイント  高速化するのは表示処理  遅い処理を調べる  ファイルアクセスは遅い  RDBMSは高コスト  キャッシュを有効活用  解析済ファイル、コンパイル済コード…  キャッシュは一番効率のよい状態で  ページキャッシュは禁断の果実 75
  76. 76. /77 ひとことで言うと Perlは速い 76
  77. 77. /7777 おわり

×