萌えキャラとは何だったのか

ギークにも絵描きにもなれない者の末路

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のコードを書き足しました。以下の処理の速度を比較しています。

  1. hoge/fugaのアクセサを持ったクラスをnew
    • 初期値はfugaに1
  2. hogeに'bbb'を代入
  3. fugaの値を取得
  4. 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 を使って設定しないといけないので、おいおいここは最適化していきたいです。

他にもデーモンを止める話とかもあるので以下とか参考にしつつ最適化していきたいです。

さくらVPSを使うときに最初にやっておきたいこと(CentOS編)