Scalaクラスタの怒りが聞こえる…
Scalaクラスタの怒りに満ちたTLが見えて当該セッションのUstを見れなかったのが悔しいです。何があったかはTogetterからある程度察する事が出来ます。
参考:2011/07/22_Advanced_Tech_Night No.2 「ジャバラーが知っておくべき最近の開発言語のこと」(#atn2011 )
Play framework + Scala に入門してみる
そんな流れがあったのでScalaを触りたい気持ちが強くなりPlay frameworkの勉強と一緒にScalaをやってみようかと思い立ちました。Play frameworkというのははJava版Ruby on Railsといった感じでJavaだけれど非常に効率に良い開発が出来るフレームワークです(らしいです)Wicketとはまた違う爽快感のあるフレームワークのようです。
インストール
インストールは非常に簡単です。zipを落として適当に配置してPATHを通すだけ。ただしplayのコマンドはpythonで書かれているのでpythonが必要になるかもしれません。[play]とだけ打つとセットアップが出来ていれば以下のように結果が返ってくるでしょう。
$ play
~ _ _
~ _ __ | | __ _ _ _| |
~ | '_ \| |/ _' | || |_|
~ | __/|_|\____|\__ (_)
~ |_| |__/
~
~ play! 1.2.2, http://www.playframework.org
~
~ Usage: play cmd [app_path] [--options]
~
~ with, new Create a new application
~ run Run the application in the current shell
~ help Show play help
~
セットアップの確認が出来ればさっそく動作確認してみましょう。
プロジェクトの作成、アプリケーションの実行
以下のように打つとプロジェクトが作成出来ます。作成途中アプリケーションの名前を聞かれます。基本デフォルトのままで良いでしょう。スケルトンが作成されます。
$ play new [project_name] --with scala
~ _ _
~ _ __ | | __ _ _ _| |
~ | '_ \| |/ _' | || |_|
~ | __/|_|\____|\__ (_)
~ |_| |__/
~
~ play! 1.2.2, http://www.playframework.org
~
~ The new application will be created in /path/to/[project_name]
~ What is the application name? [project_name]
~
~ OK, the application is created.
~ Start it with : play run project_name
~ Have fun!
~
以下のようにして作成されたアプリケーションを起動する事が出来ます。9000番ポートで待っているようなのでhttp://localhost:9000/にアクセスしましょう。いかにもな導入ページが返って来たらOKです。
$ play run [project_name]
~ _ _
~ _ __ | | __ _ _ _| |
~ | '_ \| |/ _' | || |_|
~ | __/|_|\____|\__ (_)
~ |_| |__/
~
~ play! 1.2.2, http://www.playframework.org
~
~ Ctrl+C to stop
~
Listening for transport dt_socket at address: 8000
01:35:10,384 INFO ~ Starting /path/to/[project_name]
01:35:11,399 WARN ~ You're running Play! in DEV mode
01:35:11,609 INFO ~ Listening for HTTP on port 9000 (Waiting a first request to start) ...
01:43:01,925 INFO ~ Application '[project_name]' is now started !
Scalaで動作するプロジェクトを作成してみる。
今度はScalaのプロジェクトを作成してみます。ちなみに以下のように conf/dependencies.yml にScalaに依存することを追記して $ play dependencies [project_name] とすることでScala対応のプロジェクトにすることも出来ますが、スケルトンコードがJavaのままなので作り直したいと思います。
$ diff -u [project_name]/conf/dependencies.yml.orig [project_name]/conf/dependencies.yml
--- project_name/conf/dependencies.yml.orig 2011-07-24 02:04:56.000000000 +0900
+++ project_name/conf/dependencies.yml 2011-07-24 02:04:46.000000000 +0900
@@ -2,3 +2,4 @@
require:
- play
+ - play -> scala 0.9.1
$ play dependencies [project_name]
~ _ _
~ _ __ | | __ _ _ _| |
~ | '_ \| |/ _' | || |_|
~ | __/|_|\____|\__ (_)
~ |_| |__/
~
~ play! 1.2.2, http://www.playframework.org
~
~ Resolving dependencies using /path/to/[project_name]/conf/dependencies.yml,
~
~ play->scala 0.9.1 (from playLocalModules)
~
~ Installing resolved dependencies,
~
~ modules/scala-0.9.1 -> /path/to/[play]/modules/scala-0.9.1
~
~ Done!
~
Scala対応のプロジェクトを作成して動作確認する
Scala対応のプロジェクトを作成するにはまず以下のようにしてScalaモジュールをインストールする必要があります。
$ play install scala
~ _ _
~ _ __ | | __ _ _ _| |
~ | '_ \| |/ _' | || |_|
~ | __/|_|\____|\__ (_)
~ |_| |__/
~
~ play! 1.2.2, http://www.playframework.org
~
~ Will install scala-0.9.1
~ This module is compatible with: 1.2.2
~ Do you want to install this version (y/n)? y
~ Installing module scala-0.9.1...
~
~ Fetching http://www.playframework.org/modules/scala-0.9.1.zip
~ [--------------------------100%-------------------------] 60034.6 KiB/s
~ Unzipping...
~
~ Module scala-0.9.1 is installed!
~ You can now use it by adding it to the dependencies.yml file:
~
~ require:
~ play -> scala 0.9.1
~
インストールが済んだら実際にScala対応のプロジェクトを作成してみましょう。
$ play new [project_name] --with scala
~ _ _
~ _ __ | | __ _ _ _| |
~ | '_ \| |/ _' | || |_|
~ | __/|_|\____|\__ (_)
~ |_| |__/
~
~ play! 1.2.2, http://www.playframework.org
~
~ The new application will be created in /path/to/[project_name]
~ What is the application name? [project_name]
~
~ Resolving dependencies using /path/to/[project_name]/conf/dependencies.yml,
~
~ play->scala 0.9.1 (from playLocalModules)
~
~ Installing resolved dependencies,
~
~ modules/scala-0.9.1 -> /path/to/[play]/modules/scala-0.9.1
~
~ Done!
~
~ OK, the application is created.
~ Start it with : play run project_name
~ Have fun!
~
動作確認はJavaの時と同様に $ play run [project_name] で出来ます。
$ play run project_name/
~ _ _
~ _ __ | | __ _ _ _| |
~ | '_ \| |/ _' | || |_|
~ | __/|_|\____|\__ (_)
~ |_| |__/
~
~ play! 1.2.2, http://www.playframework.org
~
~ Ctrl+C to stop
~
Listening for transport dt_socket at address: 8000
02:15:51,036 INFO ~ Starting /path/to/[project_name]
02:15:51,067 INFO ~ Module scala is available (/path/to/[play]/modules/scala-0.9.1)
02:15:54,598 INFO ~ Scala support is active
02:15:54,598 WARN ~ You're running Play! in DEV mode
02:15:54,734 INFO ~ Listening for HTTP on port 9000 (Waiting a first request to start) ...
Compiling:
/path/to/[project_name]/app/controllers.scala
/path/to/[play]/modules/docviewer/app/controllers/PlayDocumentation.java
/path/to/[project_name]/tmp/generated/views.defaults.html.welcome.scala
/path/to/[project_name]/tmp/generated/views.Application.html.index.scala
/path/to/[play]/modules/docviewer/app/DocViewerPlugin.java
/path/to/[project_name]/tmp/generated/views.html.main.scala
/path/to/[play]/modules/docviewer/app/helpers/CheatSheetHelper.java
Traversing /path/to/[project_name]/app/controllers.scala
Traversing /path/to/[project_name]/tmp/generated/views.Application.html.index.scala
Traversing /path/to/[project_name]/tmp/generated/views.defaults.html.welcome.scala
Traversing /path/to/[project_name]/tmp/generated/views.html.main.scala
API phase took : 0.503 s
02:16:21,114 INFO ~ Application '[project_name]' is now started !
プロジェクト構成
Playのプロジェクトは以下のような構成となっています。アプリケーションを構築して行く際は主にapp以下を弄って行く事になるでしょう。
- app
- コントローラ、モデル、ビューなど。
- conf
- アプリケーションの設定。ルーティングの設定もここ。
- lib
- jarファイルなどのlib関係。
- modules
- プロジェクトが依存しているものがこの下に書き出されるのかな?
- public
- 静的ファイルなど。
- test
- テスト関係など。
- tmp
- 生成されたclassファイルの配置やキャッシュなど。
とりあえず中身を眺めてみる
生成されるスケルトンは以下のようになっています。ルーティングとコントローラ/アクションの対応は直感的で分かりやすいですね。Controllerを継承したApplicationがコントローラ、その中で定義したメソッドがアクションのようです。
ただアクションで何を返すかが非常に分りづらいですね(もちろん基本はテンプレートですが)テンプレートそのものも@が出て来て関数定義っぽいし非常に分りづらいです。Webデザイナー殺しですね死ねば良いと思います^^
$ cat [project_name]/conf/routes
# Routes
# This file defines all application routes (Higher priority routes first)
# ~~~~
# Home page
GET / Application.index
# Ignore favicon requests
GET /favicon.ico 404
# Map static resources from the /app/public folder to the /public path
GET /public/ staticDir:public
# Catch all
* /{controller}/{action} {controller}.{action}
$ cat [project_name]/app/controllers.scala
package controllers
import play._
import play.mvc._
object Application extends Controller {
import views.Application._
def index = {
html.index("Your Scala application is ready!")
}
}
$ cat [project_name]/app/views/Application/index.scala.html
@(title:String)
@main(title) {
@views.defaults.html.welcome(title)
}
$ cat [project_name]/app/views/main.scala.html
@(title:String = "")(body: => Html)
<!DOCTYPE html>
<html>
<head>
<title>@title</title>
<link rel="stylesheet" media="screen" href="@asset("public/stylesheets/main.css")">
<link rel="shortcut icon" type="image/png" href="@asset("public/images/favicon.png")">
<script src="@asset("public/javascripts/jquery-1.5.2.min.js")" type="text/javascript"></script>
</head>
<body>
@body
</body>
</html>
アクションとビュー
とりあえずスケルトンでは以下のような感じで動いているようです。
- views/[レイアウト].scala.html
- レイアウトを定義
- views/[コントローラ]/[アクション].scala.html
- @レイアウト(引数) { HTML }
- レイアウトとHTMLをガッチャンコして返す
HTMLの部分が@views.defaults.html.welcome(title)となってこれが何を差しているのかが分りづらくしています。ここを<h1>@title</h1>とでもすればもう少し見通しが良くなるのではないかと思います。具体的には以下のような感じ。
$ diff -u [project_name]/app/views/Application/index.scala.html.orig [project_name]/app/views/Application/index.scala.html
--- project_name/app/views/Application/index.scala.html.orig 2011-07-24 03:45:31.000000000 +0900
+++ project_name/app/views/Application/index.scala.html 2011-07-24 03:47:38.000000000 +0900
@@ -2,6 +2,6 @@
@main(title) {
- @views.defaults.html.welcome(title)
+ <h1>@title</h1>
-}
\ No newline at end of file
+}
$ diff -u project_name/app/controllers.scala.orig project_name/app/controllers.scala
--- project_name/app/controllers.scala.orig 2011-07-24 03:49:42.000000000 +0900
+++ project_name/app/controllers.scala 2011-07-24 03:48:58.000000000 +0900
@@ -8,7 +8,11 @@
import views.Application._
def index = {
- html.index("Your Scala application is ready!")
+ html.index("index")
}
+ def sample = {
+ html.index("sample")
+ }
+
}
アクションも増やしてみました。これでルーティング設定も確認出来ます。$ play run [project_name] として以下へアクセスするとそれぞれindex、sampleが<h1>で表示されることが確認出来ると思います。
なんとなくではありますがアクションからテンプレートが呼ばれ、引数を使ってレイアウトを解決して表示していることが見えてきた感じがします。
まとめ
とりあえず一旦ここまで。何をしているか理解出来たらまたコントローラ/アクション/テンプレートの関係を文字に起こしてみたいと思います。
Play frameworkですが面白そうではあるもののやはりテンプレート周りが気に入らないですね。テンプレートの中にコードを埋め込んだり、独自の構文でロジックを埋め込んだりというのはどうしても慣れないです。どんなに簡単な構文でそれが可能だとしてもデザイナーとの連携の時にはやはり困ると思うのです。「ここは動的な情報が入るのでこういう書き方をして下さい」なんて絶対混乱します。テンプレートをHTMLで書くならそのテンプレートはプレーンなHTMLであるべきだと自分は思います。
健康診断
健康診断へ行って血を取ってきました。前回はチューブ通して採血の容器に取ってたのが今回は針から直結するようになって、それのおかげか何なのかは分りませんが採血スピードが向上して地味に嬉しかったです。痛いのは嫌いなのです…
blessとMouseの速度比較
すこし気になってblessとMouse、その他の速度比較。いろんなところでやられてるので今更自分でする事でもないでしょうが。なおblessで作るクラスは手を抜いて型制限などの実装はせずアクセサを用意して値を操作するだけにしました。適当適当。
いまさら聞けない「Moose」超入門を参考にblessとMouseのコードを書き足しました。以下の処理の速度を比較しています。
- hoge/fugaのアクセサを持ったクラスをnew
- 初期値はfugaに1
- hogeに'bbb'を代入
- fugaの値を取得
- hogeの値を取得
cost.pl
# おまじないしない場合
package MooseTest;
use Moose;
has 'hoge' => ( is => 'rw', isa => 'Str' );
has 'fuga' => ( is => 'rw', isa => 'Int' );
no Moose;
1;
# おまじないする場合
package MooseTest_mi;
use Moose;
has 'hoge' => ( is => 'rw', isa => 'Str' );
has 'fuga' => ( is => 'rw', isa => 'Int' );
__PACKAGE__->meta->make_immutable; # おまじない
no Moose;
1;
# おまじないしない場合
package MouseTest;
use Mouse;
has 'hoge' => ( is => 'rw', isa => 'Str' );
has 'fuga' => ( is => 'rw', isa => 'Int' );
no Mouse;
1;
# おまじないする場合
package MouseTest_mi;
use Mouse;
has 'hoge' => ( is => 'rw', isa => 'Str' );
has 'fuga' => ( is => 'rw', isa => 'Int' );
__PACKAGE__->meta->make_immutable; # おまじない
no Mouse;
1;
# Class::Accessor を使った場合
package ClassAccessorTest;
use strict;
use warnings;
use base 'Class::Accessor';
__PACKAGE__->mk_accessors(qw/hoge fuga/);
1;
# Class::Accessor::Fast を使った場合
package ClassAccessorFastTest;
use strict;
use warnings;
use base 'Class::Accessor::Fast';
__PACKAGE__->mk_accessors(qw/hoge fuga/);
1;
# perl5 OOP の場合
package Bless;
use strict;
use warnings;
sub new {
my $class = shift;
my $self = shift || {};
return bless $self, $class;
}
sub hoge {
my $self = shift;
if (@_) {
$self->{hoge} = shift;
} else {
$self->{hoge};
}
}
sub fuga {
my $self = shift;
if (@_) {
$self->{fuga} = shift;
} else {
$self->{fuga};
}
}
1;
package main;
use strict;
use warnings;
use Benchmark qw(cmpthese timethese);
cmpthese timethese(100000, {
moose => sub {
my $moose = MooseTest->new( fuga => 1 );
$moose->hoge('bbb');
$moose->fuga;
$moose->hoge;
},
moose_mi => sub {
my $moose = MooseTest_mi->new( fuga => 1 );
$moose->hoge('bbb');
$moose->fuga;
$moose->hoge;
},
mouse => sub {
my $mouse = MouseTest->new( fuga => 1 );
$mouse->hoge('bbb');
$mouse->fuga;
$mouse->hoge;
},
mouse_mi => sub {
my $mouse = MouseTest_mi->new( fuga => 1 );
$mouse->hoge('bbb');
$mouse->fuga;
$mouse->hoge;
},
accessor => sub {
my $class = ClassAccessorTest->new({ fuga => 1 });
$class->hoge('bbb');
$class->fuga;
$class->hoge;
},
accessor_fast => sub {
my $class = ClassAccessorFastTest->new({ fuga => 1 });
$class->hoge('bbb');
$class->fuga;
$class->hoge;
},
bless => sub {
my $class = Bless->new({ fuga => 1 });
$class->hoge('bbb');
$class->fuga;
$class->hoge;
},
});
結果はこんな感じになりました。何回か実行するとばらつくので3つほど結果を書き出しています。
Benchmark: timing 100000 iterations of accessor, accessor_fast, bless, moose, moose_mi, mouse, mouse_mi...
accessor: 2 wallclock secs ( 1.19 usr + 0.00 sys = 1.19 CPU) @ 84033.61/s (n=100000)
accessor_fast: 0 wallclock secs ( 0.58 usr + 0.00 sys = 0.58 CPU) @ 172413.79/s (n=100000)
bless: 1 wallclock secs ( 0.41 usr + 0.00 sys = 0.41 CPU) @ 243902.44/s (n=100000)
moose: 21 wallclock secs (21.26 usr + 0.02 sys = 21.28 CPU) @ 4699.25/s (n=100000)
moose_mi: 2 wallclock secs ( 1.67 usr + 0.00 sys = 1.67 CPU) @ 59880.24/s (n=100000)
mouse: 0 wallclock secs ( 0.51 usr + 0.00 sys = 0.51 CPU) @ 196078.43/s (n=100000)
mouse_mi: -1 wallclock secs ( 0.51 usr + 0.00 sys = 0.51 CPU) @ 196078.43/s (n=100000)
Rate moose moose_mi accessor accessor_fast mouse_mi mouse bless
moose 4699/s -- -92% -94% -97% -98% -98% -98%
moose_mi 59880/s 1174% -- -29% -65% -69% -69% -75%
accessor 84034/s 1688% 40% -- -51% -57% -57% -66%
accessor_fast 172414/s 3569% 188% 105% -- -12% -12% -29%
mouse_mi 196078/s 4073% 227% 133% 14% -- 0% -20%
mouse 196078/s 4073% 227% 133% 14% 0% -- -20%
bless 243902/s 5090% 307% 190% 41% 24% 24% --
Benchmark: timing 100000 iterations of accessor, accessor_fast, bless, moose, moose_mi, mouse, mouse_mi...
accessor: 1 wallclock secs ( 0.87 usr + 0.00 sys = 0.87 CPU) @ 114942.53/s (n=100000)
accessor_fast: 0 wallclock secs ( 0.56 usr + 0.00 sys = 0.56 CPU) @ 178571.43/s (n=100000)
bless: 1 wallclock secs ( 0.43 usr + 0.00 sys = 0.43 CPU) @ 232558.14/s (n=100000)
moose: 18 wallclock secs (18.30 usr + 0.00 sys = 18.30 CPU) @ 5464.48/s (n=100000)
moose_mi: 2 wallclock secs ( 1.57 usr + 0.00 sys = 1.57 CPU) @ 63694.27/s (n=100000)
mouse: 0 wallclock secs ( 0.55 usr + 0.00 sys = 0.55 CPU) @ 181818.18/s (n=100000)
mouse_mi: 1 wallclock secs ( 0.65 usr + 0.00 sys = 0.65 CPU) @ 153846.15/s (n=100000)
Rate moose moose_mi accessor mouse_mi accessor_fast mouse bless
moose 5464/s -- -91% -95% -96% -97% -97% -98%
moose_mi 63694/s 1066% -- -45% -59% -64% -65% -73%
accessor 114943/s 2003% 80% -- -25% -36% -37% -51%
mouse_mi 153846/s 2715% 142% 34% -- -14% -15% -34%
accessor_fast 178571/s 3168% 180% 55% 16% -- -2% -23%
mouse 181818/s 3227% 185% 58% 18% 2% -- -22%
bless 232558/s 4156% 265% 102% 51% 30% 28% --
Benchmark: timing 100000 iterations of accessor, accessor_fast, bless, moose, moose_mi, mouse, mouse_mi...
accessor: 1 wallclock secs ( 0.90 usr + 0.00 sys = 0.90 CPU) @ 111111.11/s (n=100000)
accessor_fast: 0 wallclock secs ( 0.75 usr + 0.00 sys = 0.75 CPU) @ 133333.33/s (n=100000)
bless: 1 wallclock secs ( 0.47 usr + 0.00 sys = 0.47 CPU) @ 212765.96/s (n=100000)
moose: 22 wallclock secs (21.62 usr + 0.01 sys = 21.63 CPU) @ 4623.21/s (n=100000)
moose_mi: 1 wallclock secs ( 1.61 usr + 0.00 sys = 1.61 CPU) @ 62111.80/s (n=100000)
mouse: 1 wallclock secs ( 0.45 usr + 0.00 sys = 0.45 CPU) @ 222222.22/s (n=100000)
mouse_mi: 0 wallclock secs ( 0.45 usr + 0.00 sys = 0.45 CPU) @ 222222.22/s (n=100000)
Rate moose moose_mi accessor accessor_fast bless mouse_mi mouse
moose 4623/s -- -93% -96% -97% -98% -98% -98%
moose_mi 62112/s 1243% -- -44% -53% -71% -72% -72%
accessor 111111/s 2303% 79% -- -17% -48% -50% -50%
accessor_fast 133333/s 2784% 115% 20% -- -37% -40% -40%
bless 212766/s 4502% 243% 91% 60% -- -4% -4%
mouse_mi 222222/s 4707% 258% 100% 67% 4% -- 0%
mouse 222222/s 4707% 258% 100% 67% 4% 0% --
Mouseが爆速でblessより速いこともあるようです。にわかに信じられないのですが。何か最適化されているものでもあるのでしょうか?ちゃんと中身を見ないといけませんね。
思う事
速度というのは最近気にしているテーマで、Webアプリの1リクエストのレスポンスタイムが10msと100msとあったとして、それの時間差が90msと何となく微々たる物のように思う物でも分間10リクエストとして合計100msと1000msになり差が900msとどんどん大きくなっていくのが恐ろしいなぁと思ったりしています。もしもこれが分間じゃなくて秒間だったら?10リクエストじゃなくて100リクエストだったら?1000リクエストだったら?…まだ実感の伴わない話なのでアレですが今後直面する問題だろうなと感じています。モダンだ流行だと色々なものに飛びつくのも勉強になって良いですが、ただ実際それが実用に堪えうるのかどうかちゃんと検証することも覚えないといけないなと。そんなことを考えています。
MySQLで大容量のデータを扱うと削除処理が重くて涙目になることがあるので残さないデータを扱うテーブルはパーティショニングしてあげるのが良いようです。ログデータは典型的かもしれませんね。メリットとしては以下のような感じだと思います。
- DROP TABLE のようにパーティションのデータをサクっと消せる
- 刈り込みが出来る(検索範囲を限定出来る場合が有り、各クエリのパフォーマンスが良くなる可能性がある)
使う場合の注意として漢のコンピュータ道にてこんな感じに挙げられています。
- インデックスをつけるだけでカバー出来る場合が多い。
- パーショニングを使わずに、単にテーブルを分けてしまえばいい。
- テーブルが巨大にならないとあまり効果を実感できない。
- 使い方を間違えると性能が落ちてしまう。
以下、日別にパーティションを区切っていくサンプルになります。
テーブル作成
DROP TABLE IF EXISTS partition;
CREATE TABLE partition (
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
created_at DATETIME NOT NULL,
PRIMARY KEY (id, created_at)
) ENGINE=InnoDB;
ベースとなるパーティションの作成
ALTER TABLE partition PARTITION BY RANGE (TO_DAYS(created_at))
(PARTITION p20110720 VALUES LESS THAN (TO_DAYS('2011-07-21 00:00:00')) COMMENT = '2011-07-21 00:00:00');
mysql_partition_cron.sh
cronで順次パーティションを追加していきます。以下では毎日次の日の1日分を追加していますが1週間毎に纏めて追加とかしても良いでしょう。実際に運用する際はログを取ってメールを投げる等監視しておく方が良いですね。
#!/bin/bash
DAYSTR=`date --date '1 day' +"%Y%m%d"`
DAY_2=`date --date '2 day' +"%Y-%m-%d 00:00:00"`
QUERY="ALTER TABLE partition ADD PARTITION (PARTITION p$DAYSTR VALUES LESS THAN (TO_DAYS('$DAY_2')) COMMENT = '$DAY_2')"
echo $QUERY
echo $QUERY | mysql -u root partition
確認
INSERT
mysql> insert into partition value (null, '2011-07-20');
Query OK, 1 row affected (0.00 sec)
mysql> insert into partition value (null, '2011-07-21');
Query OK, 1 row affected (0.00 sec)
mysql> insert into partition value (null, '2011-07-22');
ERROR 1526 (HY000): Table has no partition for value 734705
存在しないパーティションへのINSERTはエラーになるので、パーティションの追加忘れ、追加ミスには注意。
mysql> insert into partition value (null, '2011-07-19');
Query OK, 1 row affected (0.01 sec)
パーティション分布
mysql> SELECT table_schema, table_name, partition_name, partition_ordinal_position, table_rows FROM information_schema.partitions WHERE table_name = 'partition';
+--------------+------------+----------------+----------------------------+------------+
| table_schema | table_name | partition_name | partition_ordinal_position | table_rows |
+--------------+------------+----------------+----------------------------+------------+
| partition | partition | p20110720 | 1 | 2 |
| partition | partition | p20110721 | 2 | 1 |
+--------------+------------+----------------+----------------------------+------------+
2 rows in set (0.00 sec)
刈り込み
mysql> explain partitions select * from partition where created_at < '2011-07-21 00:00:00';
+----+-------------+-----------+------------+-------+---------------+---------+---------+------+------+--------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-----------+------------+-------+---------------+---------+---------+------+------+--------------------------+
| 1 | SIMPLE | partition | p20110720 | index | NULL | PRIMARY | 16 | NULL | 2 | Using where; Using index |
+----+-------------+-----------+------------+-------+---------------+---------+---------+------+------+--------------------------+
1 row in set (0.00 sec)
mysql> explain partitions select * from partition where created_at < '2011-07-22 00:00:00';
+----+-------------+-----------+---------------------+-------+---------------+---------+---------+------+------+--------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-----------+---------------------+-------+---------------+---------+---------+------+------+--------------------------+
| 1 | SIMPLE | partition | p20110720,p20110721 | index | NULL | PRIMARY | 16 | NULL | 3 | Using where; Using index |
+----+-------------+-----------+---------------------+-------+---------------+---------+---------+------+------+--------------------------+
1 row in set (0.01 sec)
前者だとp20110720からのみの検索になってるので刈り込み出来ているようです。これが大容量のデータに対してだと本来インデックがメモリに乗りきらずスワップしちゃうという状況でも、刈り込みによってメモリに収まる可能性が出て来るので、そうなった場合にパーティショニングの威力が発揮されるというのが想像出来ますね。
まとめ
削除のコストは思いの外大きくて、レプリケーション環境下でしこしこ古いデータを削除するだけのバッチを走らせたときに削除間隔を大きく取らないと同期ズレがずんどこずんどこ溜まっていく、というのを経験したことがあります。パーティションを使うとそういう苦労はせずに済みそうなので有効活用したいですね。
と言っても特に何かしたとかは無かったり。
- sshのポートを変えた
- iptablesを設定した
- logwatchを導入した
- pacoを導入した
sshのポート変更
セキュリティ的にはsshを公開鍵認証にしておくだけで基本大丈夫だとは思うのですが、アタックされることには変わりなく気持ち悪い物は気持ち悪いので一応変えておきました。
iptablesの設定
何処でも見かける設定ですが以下のような基準でiptablesを設定するスクリプトを組みました。
- 内から外への通信以外は拒否
- ただし内から外への通信の応答は通す
- SSH用の穴は用意する
iptables-init.sh
#!/bin/sh
# ルールクリア
iptables -F
iptables -X
# 内から外への通信以外は破棄
iptables -P INPUT DROP
iptables -P OUTPUT ACCEPT
iptables -P FORWARD DROP
# ループバックは全て許可
iptables -A INPUT -i lo -j ACCEPT
iptables -A OUTPUT -o lo -j ACCEPT
# プライベートネットワークなんて無かったんや…
iptables -A INPUT -s 10.0.0.0/8 -j DROP
iptables -A INPUT -s 127.16.0.0/12 -j DROP
iptables -A INPUT -s 192.168.0.0/16 -j DROP
# SSH用の穴
iptables -A INPUT -p tcp --dport {SSHのポート} -j ACCEPT
# 内から外への通信の応答は通す
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
logwatchの導入
ログも監視しないと意味ないのでlogwatchなるログを定期的に(デフォルトはcron.dailyを利用した毎日)メールで知らせるプログラムを導入してみました。yumで入れるとバージョンが結構古かったりするのでsourceforgeから落としてインストールしました。rpmで入れるとデフォルトの設定ファイルが/usr/share/logwatch/default.conf以下となるようです。/etc/logwatch/conf以下に設定ファイルを置くと設定を上書きするようです。
とりあえずSSHのログとDiskスペースのログを監視するようにしました。デフォルトではService = Allで全部を監視するようにしてその後除外を設定しているのでその設定をコメントアウトしてSSHのログとDiskスペースのログに決め打ちしました。
/usr/share/logwatch/default.conf/logwatch.conf
#Service = All
#Service = "-zz-network"
#Service = "-zz-sys"
#Service = "-eximstats"
/etc/logwatch/conf/logwatch.conf
MailTo = 送信先メールアドレス
MailFrom = 送信元メールアドレス
Service = sshd
Service = zz-disk_space
rpmで入れているならばおそらく/etc/cron.daily/0logwatchにlogwatchスクリプトへのシンボリックリンクが貼られているので毎日定期的に実行されメールが届くようになるハズです。ただしデフォルトでは前日のログを送信する設定(Range = yesterday)になっていてログが記録されていなければ送信されません。
pacoの導入
pacoはmake installしたソフトを管理するものです。yumなどのパッケージ管理ソフトで管理されている以外のものについてもきちっと管理したい場合は導入するととても幸せになれそうです。
pacoのインストール自体はconfigure make make installで出来ます。またpaco自身をpacoで管理するためにmake installの後make logmeを実行します。これ以降make installするときはpaco -D make insatllというふうにすることでそのソフトをpacoの管理下に置いてくれます。
注意
pacoはデフォルトで/usr/local/bin以下にインストールされるのですがsudoで実行するとPATHが引き継がれずpacoが見つからないためmake logmeに失敗してしまいました。./configure prefix=/binなどインストール場所を設定してsudoでも実行出来るようにするか、sudo時の環境変数の引き継ぎをするなどして回避しましょう。
なおsudo時の環境変数の引き継ぎ方法は色々あるようです。
bashrcで設定
alias sudo="sudo env PATH=$PATH"
visudoで設定
Defaults:{run_user} !env_reset
or
Defaults env_keep+="PATH"
sudo -s で実行
次回は
Nginx+plackでなんか適当に外から見れる環境を作りたいかな。
さくらVPS借りてみた
インフラエンジニアを目指す宣言したは良いものの、自分で管理する外部に晒されている環境すら持ってないのはどうかという話があるのでまずはさくらVPSを借りてみることに請求が来る前に試用出来るようですぐに使えるのは凄いですねぇ。さくら様様です。(ちなみに一応自宅サーバ用のマシンは手元にあるのですが外部ディスプレイ手に入れてからとか考えてたら結局こっち来てから起動することはありませんでした…)
インフラやりたいと言いつつミドルウェアの知識はまったく0に近い状態なので、ミドルウェアの勉強用に使って行きたいかなと思います。
とりあえず入れて最初にやったこと
- ユーザの設定
- rootのパスを変更する
- 一般ユーザを追加する(sudoで管理者の権限を得るためにグループはwheelに入れる)
- 一般ユーザのパスを変更する
- visudoでwheelグループがsudoで管理者権限で実行できるように変更
- SSHのログインを公開鍵に変更
- /etc/ssh/sshd_config を修正
- キーペアを作成してログイン
- system-config-securitylevel-tui でssh以外を閉じる
- git,perlbrew,cpanmを入れる
大体こんな感じ。SSHは以下のような感じで設定。コメントアウト外したりyes,no変えたり。
PermitRootLogin no
AuthorizedKeysFile .ssh/authorized_keys
PasswordAuthentication no
ChallengeResponseAuthentication no
「ChallengeResponseAuthentication no」はデフォルトでnoだけれどこれがyesになってると公開鍵の認証に失敗したらパスワード認証も試すといった挙動をするので、公開鍵に限定する場合はちゃんとnoになってることをチェック。
ファイアーウォールの設定は一旦 system-config-securitylevel-tui 使ってやりましたが、細かく設定する場合は当然 iptables を使って設定しないといけないので、おいおいここは最適化していきたいです。
他にもデーモンを止める話とかもあるので以下とか参考にしつつ最適化していきたいです。