LoginSignup
0
1

More than 5 years have passed since last update.

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

Last updated at Posted at 2015-10-06

その1
その2

前回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の初期状態を作る

# 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に以下を追加してベンチマークスタート時に実行されるようにする

# 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を初期化

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

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

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

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

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でチェックしてたのがシンプルになる

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を使う

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点足りない。。。

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

続きはこちら

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1