初めてのYAPC

Perlを始めて2ヶ月が経とうとしています。

昨日はYAPC::Asia Tokyo 2011に参加してきました。


僕がYAPCに行って得たものは

  • Perlの世界に触れられたこと
  • Perlを用いたサービス運用のTips
  • 周りの先輩方の偉大さを改めて実感できたこと

です。


今の自分が恵まれた環境にいることを改めて実感、

そしてその環境を活かしきれていないことを反省しています。

積極性、これが自分に足りていないものだと感じました。


LT後のManaging A Band Of Hackersでは

若い人はコミュニティに参加してほしい
人脈は大事

ということをおっしゃっていました。

これまでコミュニティに参加するのが苦手でしたが、これからはもう少し積極的になろうと思います。

単に人脈を広げるのではなく、人望のある人間を目指して。

ものを作って結果を残す。


watch your logの発表の際に引用されていた

Shut the fuck up and write some code.

この言葉を胸に刻んでおきます。


今はまだperlでの成果物がない状態ですが、来年にはちゃんと結果を残していたい。

言葉足らずの部分が多いですが、YAPCに参加できたことに感謝しています。

Perlのことがもっと好きになれた1日でした。

スピーカーの皆様、スタッフの皆様、スポンサーの皆様、そして参加者の皆様、本当にありがとうございました。



おまけ
スイーツエリアで販売していた『Acme大全2011』をGET!!

「なんかおもしろそう」と、お土産感覚で購入しましたが、第一章は普通にPerl(Perl界隈のことも)の勉強になりました。

なんでもありのAcmeモジュールおもしろい!

「はじめてのPerl」とセットで読むと楽しくPerlを始められると思います。

以下、講演メモ

運用しやすいアプリケーション構築手法
  • ログの重要性
    • 障害発生時に最初にみる
    • ログがないと時間がかかる
  • 適切なログに含まれる情報
    • 時間
    • ログレベル
      • ログを見た人に何をしてほしいかが明確になっているように
  • ログ管理モジュール
  • 設計思想
  • DBI
    • 負荷は急上昇する
      • 原因となっているクエリを探す
        • show log
      • 何のクエリなのかアプリケーションコードを確認
        • killしていいのか?
        • チューニング、アプリケーションの変更で対処できるか?
    • ORMはSQLとコードが一致しない
      • ORM使う際にはSQL生成するモジュールが散らばらないように
        • クエリコメントを書く
  • DBIx:Sunny
    • DBI派向けDBIサブクラス
  • DBI接続
    • connect_cacheは接続の滞留が発生する
      • 最大接続数に達し、接続エラー
  • Scope::Container
  • memcached
    • 課題1
      • Session::Store::Memcached
        • メリット
          • 高速
          • 簡単
          • expiresの自動化
        • デメリット
          • 永続性なし
          • メンテナンスでの再起動は不可
    • 課題2
      • 特定キャッシュへの集中
    • 課題3
      • Cache thundering herd problem
        • 消えた瞬間、dbに負荷集中で落ちる
    • 解決モジュール
      • Cache::Memcached::IronPlate
        • cacheの冗長性確保
      • Cache::Isolator
        • expireの時間をずらす
  • Metrics
    • Paralell::Scoreboard
watch your log
  • 必要に応じたエンジニアリングのすすめ
  • DevOps
    • 運用を考えられるDevにならないといけない
      • Devも一時対応は行えるようにしておかないとダメ
      • 仕様変更等は簡単でもいいから情報共有を行う
    • 運用を丸受けしないOps
    • うまくいかないのはDevとOpsのコミュニケーション濃度の問題
      • 障害に特化したコミュニケーションを
  • 障害には監視が必要
    • 何を監視すべきか?
      • 死活監視
      • リソース監視
      • ログ監視
        • ログ監視のソリューションがあまりない
        • ログの出し方をOpsと決める
          • 開発者は数週間したら仕様を忘れる
          • Opsは日々の運用で手一杯
          • ログの出し方はWebDBPress最新号を参照
  • ログ?
    • access_log
    • error_log
      • エラーコードでエラーのレベルわけができる
    • syslog
      • DevOpsのコミュニケーションを活かすところ
      • 重要度の切り分けを行えるログを
    • mysql show log
      • 大規模でボトルネックになるのはdb
      • チューニングの際には必ず見る
    • アプリログ、dbログは日単位でローテーション
  • ログの収集方法を紹介
    • Komainu
      • accesslogの集計
      • アプリケーションログの集計
  • まとめ
    • サービスクオリティの維持
    • 問い合わせベースで障害に気付くのは情けない
    • 攻めの運用
    • Shut the fuck up and write some code.
    • コードの良し悪しはとりあえずどうでもいい、結果が全て
    • 問題意識を持って行動する
    • 密なコミュニケーションを

データの重複登録回避方法は?

perlに限ったことではありませんが、DB処理を行う際、データの重複登録を回避するにはどういう方法をとるのがよいのでしょうか?

WEBアプリケーションの場合、ページリロードによるデータの重複登録が発生することがあり、それをうまく回避する方法はないかと模索中です。

ぱっと思いついたのは、

  • データ登録を行う前にチェック用のSQLを実行し、データが存在しなければ登録する方法
  • 登録の際に例外処理を行い、意図的に例外を握りつぶす方法

の2通りです。

サンプルとして、id(主キー)とnameを持つmemberテーブルを作ってみます。

テーブル作成
mysql> show create table member\G
*************************** 1. row ***************************
       Table: member
Create Table: CREATE TABLE `member` (
  `id` bigint(20) NOT NULL,
  `name` varchar(64) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
1 row in set (0.00 sec)


mysql> desc member;
+-------+-------------+------+-----+---------+-------+
| Field | Type        | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id    | bigint(20)  | NO   | PRI | NULL    |       |
| name  | varchar(64) | YES  |     | NULL    |       |
+-------+-------------+------+-----+---------+-------+
2 rows in set (0.00 sec)
1.データ登録の前にチェック用SQLを実行して重複登録を回避する方法

以下は、コマンドラインから引数(idとname)を受け取りデータ登録を行うサンプル

#usr/bin/perl
use strict;
use warnings;
use DBI;
use Data::Dumper;

my ($id, $name) = @ARGV;
die unless (@ARGV == 2);

my $dbh = DBI->connect(
        'dbi:mysql:dbname=test',
        'test_user',
        'test',
        { RaiseError => 1, PrintError => 0, AutoCommit => 0 }
       );

# データの存在チェック
my $sth = $dbh->prepare("SELECT * FROM member WHERE id = ?");
$sth->execute($id);
my $data = $sth->fetchrow_hashref;

# データが存在しなければ新規登録
if (!$data) {
    $sth = $dbh->prepare("INSERT INTO member (id, name) VALUES(?, ?)");
    $sth->execute($id, $name);
    $dbh->commit;
}

$sth = $dbh->prepare("SELECT * FROM member");
$sth->execute();
print Dumper($sth->fetchall_arrayref);
$dbh->disconnect;
サンプル実行
新規登録
perl sample.pl 1 test1
$VAR1 = [
          [
            '1',
            'test1'
          ]
        ];

新規登録
perl sample.pl 2 test2
$VAR1 = [
          [
            '1',
            'test1'
          ],
          [
            '2',
            'test2'
          ]
        ];

重複登録
perl sample.pl 1 test1
$VAR1 = [
          [
            '1',
            'test1'
          ],
          [
            '2',
            'test2'
          ]
        ];


登録の前にチェック用SQLを実行することで、重複登録が回避できています。

なお、チェックなしにプログラムを実行すると、重複登録になりエラーが発生します。

perl sample.pl 1 test1
DBD::mysql::st execute failed: Duplicate entry '1' for key 'PRIMARY' at exception_sample2.pl line 23.
Issuing rollback() due to DESTROY without explicit disconnect() of DBD::mysql::db handle dbname=test at exception_sample.pl line 23.
2.例外処理を使って重複登録を回避する方法

処理内容自体は1.の方法と同じですが、重複登録の回避方法としてevalを用いています。

#usr/bin/perl
use strict;
use warnings;
use DBI;
use Data::Dumper;

my ($id, $name) = @ARGV;
die unless (@ARGV == 2);

my $dbh = DBI->connect(
        'dbi:mysql:dbname=test',
        'test_user',
        'test',
        { RaiseError => 1, PrintError => 0, AutoCommit => 0 }
       );

my $sth = $dbh->prepare("INSERT INTO member (id, name) VALUES(?, ?)");
eval {
    # 重複登録の場合、例外発生
    $sth->execute($id, $name);
    $dbh->commit;
};

# 何もしない(意図的に例外を握りつぶす)
# if ($@) {
#
# }

$sth = $dbh->prepare("SELECT * FROM member");
$sth->execute();
print Dumper($sth->fetchall_arrayref);
$dbh->disconnect;

実行結果は1.と同じです。

evalを使って例外処理を行い、意図的に例外を握りつぶすことで、重複登録になってもプログラムは途中終了せず、データの表示が行われます。

自分の中では1.の方法よりも2.の方法のほうがスッキリしていると思うのですが、実際のところどういう方法がいいのか分からず悩んでいます。

他にももっと良い方法があるのでしょうか?

Perlでの例外処理について

Perlで例外処理をしたい場合は、try-catchではなくevalを使うんですね。
(モジュールを使えばtry-catchも使えるそうですが)

初めて見た時、「evalってなんだろう?」と悩んだので残しておきます。

evalでくくって(try)、ifで例外処理(catch)

例外処理のサンプル
#usr/bin/perl
use strict;
use warnings;

eval {
    die "this is exception"; # 強制的に例外発生
};
if ($@) {
    print "ERROR:$@"; # 例外内容を表示
}
実行結果
ERROR:this is exception at eval.pl line 6.

evalを使う際はセミコロン(;)を忘れないよう注意する。

perlでのswitch文

9/8追記

先輩から、Switchモジュールはバグが多く現在では非推奨モジュールになっているというアドバイスいただきました。

以下、5.12.0ドキュメント(perl5120delta - perldoc.perl.org)より引用


You can silence these deprecation warnings by installing the modules in question from CPAN. To install the latest version of all of them, just install Task::Deprecations::5_12 .

Class::ISA
Pod::Plainer
Shell
Switch

Switch is buggy and should be avoided. You may find Perl's new given/when feature a suitable replacement. See Switch statements in perlsyn for more information.


Switchモジュールを用いたサンプルは残しておきますが、

5.10以降はgivenを用いたほうがいいそうなのでそちらをお使いください。


私の環境では5.8.8だったので、if-else文に置き換えました。

追記終わり



perlではモジュールを利用しないとswitch文が使えないということを知りました


これまで使ってきた言語ではデフォルトでswitch文が使えたのでこれは少し意外でした


switch文を使うにはSwitchモジュールを呼び出す必要があります


以下、case文を使いたいだけの意味のないサンプル


forで回して無理やりswitch文を使ってます


phpでは、caseに該当しないものは「default」で表していましたが、perlのSwitchモジュールでは「else」を用います

サンプルコード
#usr/bin/perl
use strict;
use warnings;
use Switch;

for (1..10) {
    switch ($_) {
        case 1 {
            print "hello\n";
        }
        case 2 {
            print "good\n";
        }
        else {
            print "this value is $_\n";
        }
    }
}
結果
hello
good
this value is 3
this value is 4
this value is 5
this value is 6
this value is 7
this value is 8
this value is 9
this value is 10


また、perl 5.10以降は標準で組み込まれている「given」を用いることで同様の処理が可能なようですね

givenを用いたサンプルコード
#!usr/bin/perl
use strict;
use warnings;
use 5.10.0;

for (1..10) {
    given ($_) {
        when (1) {
            say "hello";
        }
        when (2) {
            say "good";
        }
        default {
            say "this value is $_";
        }
    }
}


「say "xxx"」を使うことで「print "xxx\n"」の際に必要だった改行コードの省略が可能です

参考にさせていただいたサイト

ベストプラクティスなコーディングを


Perlベストプラクティス』はまだ読めていないのですが、Perl::Tidyというベストプラクティスなコーディングをサポートしてくれる便利なモジュールがあるとのこと

とりあえず試してみました

  • まずはモジュールインストール
cpanm --local-lib=~/perl5 Perl::Tidy
  • .perltidyrcを作成し、ベストプラクティスな設定を追加(pbp = Perl Best Practice)
vi ~/.perltidyrc
-pbp
  • vim用のショートカットを追加
vi ~/.vimrc
"選択された部分のソースコードを整形
map ,ptv :! perltidy
"ソースコード全体を整形
map ,pt :%! perltidy


これでvimで編集中に

  • 「,pt」と入力するとファイル全体を
  • vで範囲選択した後に、「,ptv」と入力すると選択範囲を

整形してくれます


これからはベストプラクティスに沿ったコーディングを心がけたいと思います

本もちゃんと読もう

JSONを使ってみる

perlJSONを扱う方法が分からなかったので調査


JSON::XSというモジュールを入れればよさそう


早速、モジュールインストール

cpanm JSON::XS

サンプル

#usr/bin/perl
use strict;
use warnings;
use JSON::XS;
use Data::Dumper;

# ecode
my $json_text = encode_json { scalar => 'text', array => ['data1', 'data2'], hash => { key1 => 'value1', key2 => 'value2' }, complex => ['array1' => { key1 => 'value1', key2 => 'value2' }, 'array2' => { test => 'value'}] };

# decode
my $scalar = decode_json $json_text;

# dump
print Dumper($json_text, $scalar);

実行結果

$VAR1 = '{"hash":{"key2":"value2","key1":"value1"},"array":["data1","data2"],"scalar":"text","complex":["array1",{"key2":"value2","key1":"value1"},"array2",{"test":"value"}]}';
$VAR2 = {
          'scalar' => 'text',
          'array' => [
                       'data1',
                       'data2'
                     ],
          'hash' => {
                      'key2' => 'value2',
                      'key1' => 'value1'
                    },
          'complex' => [
                         'array1',
                         {
                           'key2' => 'value2',
                           'key1' => 'value1'
                         },
                         'array2',
                         {
                           'test' => 'value'
                         }
                       ]
        };


サンプルデータが適当なので見づらいですが、正しくJSONエンコード/デコードが行えているようです


今日はJSONとDumperの使い方を学びました