ISUCON4の予選問題をやってみる。(その3)


[その1](http://qiita.com/hiyuzawa/items/dfeb4362916732f4d067)
[その2](http://qiita.com/hiyuzawa/items/f67711458887a1b7137c)

前回Redisを導入しましたが、Sessionの部分だけ使ってみたが、本体の方のMySQLに依存する部分もRedis化すべきなのでそちらも変更する。

## Redis利用における仕様

ログイン失敗の管理をRedisで管理する

* keyを user_[userid] としてvalueは失敗毎に数値をインクリメント
* ログイン成功時にkeyを削除する

ログイン失敗時のIPの管理をRedisで管理する

* keyを ip_[ip] としてvalueは失敗毎に数値をインクリメント
* ログイン成功時にkeyを削除する

もともとMySQL管理のlogin_logは成功時のみのログを記録する
(ログイン後のページの前回ログイン成功の表示を取り出すのにはMySQLが都合がいいので)

## 追加で要実施の作業

ベンチマークスタート時に init.sh を起動しMySQLの初期化および初期データの投入が行われるが、そこでロードされるlogin_log (dummy_log.sql) もRedisに上記の仕様通り状態であらかじめ保存しておく必要がある. 
login_logに投入された初期状態のデータをTSVに吐き出してこれを以下のperlに読み込ませてredisの初期状態を作る

```perl
# sql/set_initlog_to_redis.pl
use strict;
use warnings;
use Data::Dumper;
use Redis;

my $redis = Redis->new();

while(<STDIN>) {
    chomp;
    my @item = split "\t";

    if ($item[5] eq 1) {
        $redis->del("user_". $item[3]);
        $redis->del("ip_". $item[4]);
    } else {
        $redis->incr("user_". $item[3]);
        $redis->incr("ip_". $item[4]);
    }
}
```

init.shに以下を追加してベンチマークスタート時に実行されるようにする

```bash
# init.sh
...
redis-cli KEYS "*" | xargs redis-cli DEL
cat sql/dump_login_log.tsv | perl sql/set_initlog_to_redis.pl
```

## web.pmの変更

#### redisを初期化

```perl
sub redis {
  my ($self) = @_;

  $self->{_redis} ||= do {
    Redis->new();
  };
};
```

#### MySQLのlogin_logを記録してたところをRedis化

login_logは成功時のみ書き込むように修正

```perl
sub login_log {
  my ($self, $succeeded, $login, $ip, $user_id) = @_;

  if($succeeded) {
    $self->redis->del("user_". $login);
    $self->redis->del("ip_". $ip);
  } else {
    $self->redis->incr("user_". $login);
    $self->redis->incr("ip_". $ip);
  }

  if ($succeeded) {
    $self->db->query(
      'INSERT INTO login_log (`created_at`, `user_id`, `login`, `ip`, `succeeded`) VALUES (NOW(),?,?,?,?)',
      $user_id, $login, $ip, 1)
    );
  }
};
```

### banのチェックの所をRedisでやる

長ったらしいSQLでチェックしてたのがシンプルになる

```perl
sub user_locked {
  my ($self, $user) = @_;

  my $fauluers = $self->redis->get("user_".$user->{login});
  $fauluers ? $self->config->{user_lock_threshold} <= $fauluers : 0
};

sub ip_banned {
  my ($self, $ip) = @_;

  my $fauluers = $self->redis->get("ip_".$ip);
  $fauluers ? $self->config->{ip_ban_threshold} <= $fauluers : 0
};
```

### banされたIPリスト Userリストを取る部分もRedisで

こちらも複雑なSQLからシンプルに redisのkeysを使う

```perl
sub banned_ips {
  my ($self) = @_;
  my @ips;
  my $threshold = $self->config->{ip_ban_threshold};

  for my $key ($self->redis->keys("ip_*")) {
    if($self->redis->get($key) >= $threshold) {
       push @ips, substr($key, 3)
    }
  }

  \@ips;
};

sub locked_users {
  my ($self) = @_;
  my @user_ids;

  my $threshold = $self->config->{user_lock_threshold};

  for my $key ($self->redis->keys("user_*")) {
    if($self->redis->get($key) >= $threshold) {
       push @user_ids, substr($key, 5)
    }
  }

  \@user_ids;
}
```

## benchmark走らせる

```
$ ./benchmarker bench --workload 10
15:27:05 type:info      message:launch benchmarker
15:27:05 type:warning   message:Result not sent to server because API key is not set
15:27:05 type:info      message:init environment
15:27:22 type:info      message:run benchmark workload: 10
15:28:22 type:info      message:finish benchmark workload: 10
15:28:27 type:info      message:check banned ips and locked users report
15:28:29 type:report    count:banned ips        value:638
15:28:29 type:report    count:locked users      value:4083
15:28:30 type:info      message:Result not sent to server because API key is not set
15:28:30 type:score     success:155490  fail:0  score:33591
```

ReverseProxy側 でアクセスログを吐いていた部分を消してもう一度

```
$ ./benchmarker bench --workload 10
15:31:47 type:info      message:launch benchmarker
15:31:47 type:warning   message:Result not sent to server because API key is not set
15:31:47 type:info      message:init environment
15:32:04 type:info      message:run benchmark workload: 10
15:33:04 type:info      message:finish benchmark workload: 10
15:33:09 type:info      message:check banned ips and locked users report
15:33:11 type:report    count:banned ips        value:645
15:33:11 type:report    count:locked users      value:4124
15:33:12 type:info      message:Result not sent to server because API key is not set
15:33:12 type:score     success:165440  fail:0  score:35739
```

35739点. ヒカリエまであと2000点足りない。。。

あと何ができるかもうちょっと考えて見る。

[続きはこちら](http://qiita.com/hiyuzawa/items/986dcda2aa0c5ca0cf08)