一人isuconで遊んでみた

予選出れず泣いていたのですが、AMIが公開されたようなので遊んでみました。


以下条件。

  • 今日の0時-3時で集中してやる
  • マニュアルは事前に読んだ
  • isucon参加者ブログは意識して見ないようにしてた。けどなんとなくヒントっぽい単語は目に入ってた


なので、フェアではないです。

時系列にやったこと

  • AMIからインスタンス起動。
  • とりあえずwebappだけバックアップ。
  • アプリをperlに置き換え。
  • ベンチマーク流してアクセスログをダラ見。
  • 静的ファイルをnginx経由にした
  • engineをStarletにした
  • アプリをUNIXドメインソケットにした
  • too manyなんとかが出始めたので、ついでにsysctlの設定をしたり。
  • topで見ると、mysqlが断トツネックだったのでslowlogの設定やらDevel::KYTProfの設定入れた
  • kytprofの結果から遅いSQL見つけてインデックス貼ったりした
  • この辺りでスコア20000超えてた
  • MySQLの設定いじったりしたけど、何故か遅くなったりして結局元に戻したりした。
  • workloadの最適値を探したり。最終的には10にした。
  • どう考えてもlogin_logのinsert/select(特にinsert)が重いので、どうやってRedis化しようかなーとか考えてた
  • この辺りで停滞
  • ふとMySQLのメモリエンジンを思いつき、試してみたら30000近くになったw(・o・)w
  • アプリに手を入れるの時間的に無理っぽいので、ログを消したりソケットファイルをtmpfsに置いたり悪あがき

最終結果

まとめ

  • Devel::KYTProf最高〜(^q^
  • isucon超楽しい〜(^q^
  • やっとisucon参加者ブログが見れるぞい〜(^q^

YAPC::Asia2014 レポート #yapcasia

YAPC、今までに何度か参加していますが
今年は念願のスピーカーとして参加することが出来ました。
話した内容についてはこちらをご参照ください。


たぶん言いたいこと全部書くとグダグダになるのでシュバッと。

最高でした

  • 無限コーヒー
  • 無限かき氷
  • 無料ランチ
  • 無料懇親会/巨大プリン
  • 無料HUB
  • ネットワーク

辛かった点

大人の事情もあると思いますが、会場のキャパシティ(特に多目的室)は辛かったです。
おじさんには立ち見は苦行…

印象に残ったトーク

どちらも愛に溢れた、熱い想いが伝わってくるトークでした。
deeetさんのトーク、自分もCLI好きなのでよくツール書くのですが
あまりこういう思想を持って書いているわけではなかったのでとても参考になりました。
songmuさんのトークはPerl愛がひしひしと感じられました。
さらにPerl言語自体の話からコミュニティ、文化に至るまで話されていて
非常に勉強になるトークでした。

Perlに関するトークが減ってきていることについて

自分はPerl好きなので個人的にPerl使いたいと思うシーンは多いんですが、
年々少しずつ使いづらいな〜と思っていて、
特に一番辛いのが世の中のサービスが大抵Perl対応してくれないのが辛くて*1
そういう理由から
Perlを選択肢に入れたいけど入れることが出来なくなってきている
ところがあるのかなと(自分に限らず)。
なのでトークが減ってきているのも仕方がないことなのかな〜という気もしています…
これ打開するには真面目にPerl6が(ry


ただ、Perlのトークが少なくなってきているからといって参加者的には文句があるわけではなく
みなさん楽しめていたようなので特に問題視することもないのかなとも思いました:)

typesterさんのキーノート

刺さりまくって意識最高潮に高まったところで、
最後にお子さんと一緒に壇上に上がったところをみて
なんでかわからないんですけどすごくホロりときました。
感動。

まとめ

運営のみなさま、自分のトークを聞きに来て下さったみなさま、ありがとうございました!
今年も最高に楽しかったです!
来年もまたスピーカーとして参加できるように日々精進します!!

*1:AWSとか

YAPC::Asia2014で儚いクラスタ運用話をしました


http://www.slideshare.net/tsuyoshitorii5/yapcasia2014-2-public
(※一部素材を変更しています)


TV連動サービスの運用ってこんな特徴があって
それに合わせていろいろ運用改善したぜ〜、
という話をしてきました。


結構ボリュームが多くなってしまって
限られた時間でうまく説明できたかどうかいまいち自信がないのですが、
「へぇ〜、そんな感じで運用してるんだ〜」
というニュアンスだけでも伝われば嬉しいです。


足を運んで聞いて下さったみなさま、ありがとうございました!!!


明日もまだまだYAPCは続くので存分に楽しみたいと思います〜


ちなみに株式会社バスキュールは
フロントエンド/バックエンド問わずエンジニアを随時募集しているようです。
もしご興味がある方いれば是非お声がけください!

GitLabとRedmineを連携してみるの巻

はてなブログさんの開発フローのお話

少し前の話になるんですが、GitHubKaigiで
はてなブログさんの開発フローについて発表がありました。
その中で

当初はGitHubとRedmineを併用していたが、
両ツールの連携がイマイチだったので
Redmineを止めてホワイトボードでタスク管理するようにした

という内容がありました。
https://speakerdeck.com/shibayu36/hatenaburogutimufalsekai-fa-hurotogithub


「ふむふむ、なるほどなぁ」という感じで非常に勉強になったのですが、
もしかしたら
「"GitLab+Redmine" ならツール自体が連携機能を持っている
のでもう少しマシになるのかも?とも思いました。


そこで、GitLabとRedmineを使うと
一体どういうことが出来る様になるのか?といったところを紹介してみます。*1

弊社でも連携機能は一応有効にしているんですが、
まだまともには運用していないので
再確認の意味でメモを残しておこう、という意図もありますw



TL;DR

これらの設定を行うと…

  • GitLabのIssuesタブがRedmineのIssuesタブとリンクされます
  • GitLabのコミットメッセージ内のissue番号がRedmineのissueとリンクされます(※6.9.2時点ではパッチが必要)
  • Redmineのチケット連携機能がそのまま使えます
  • その他、Redmineの便利プラグインとも連携できます

事前設定: Redmine

Github Hookプラグインを導入しておく

GitHubという名前ですが、GitLabでも同じように使えます。
インストールは、リポジトリダウンロードして bundle install するだけでOKです。
https://github.com/koppen/redmine_github_hook

事前設定: GitLab

Issue TrackerでRedmine連携が選択できるようにしておく

gitlab.ymlにRedmine連携するための記述をしておく必要があります。
この設定をしておくと、Settingで IssueTracker にRedmineを選択できるようになります。
RPMインストールしているのであれば、
/etc/gitlab/gitlab.rb に以下の記述を追記するだけでOKです。

gitlab_rails['issues_tracker_redmine'] = true
gitlab_rails['issues_tracker_redmine_title'] = "Redmine"
gitlab_rails['issues_tracker_redmine_project_url'] = "http://<redmine_url>/projects/:issues_tracker_id/issues"
gitlab_rails['issues_tracker_redmine_issues_url'] = "http://<redmine_url>/issues/:id"
gitlab_rails['issues_tracker_redmine_new_issue_url'] = "http://<redmine_url>/projects/:issues_tracker_id/issues/new"
RedmineからGitLabリポジトリにアクセス出来るようにしておく

これは環境によってやることが異なるのですが、
もしもGitLabとRedmineが同じサーバであれば
GitLabリポジトリへのPermissionが適切に設定されていればよいでしょう。
RPMインストールしているのであれば、
/var/opt/gitlab/git-data/repositories 以下にリポジトリの実体があります。
Redmineを実行しているユーザから、このディレクトリが見えるかどうか確認しておきましょう。


GitLabとRedmine別々のサーバの場合、詳細は割愛しますが
GitLabのwebhookを駆使してRedmineリポジトリに反映すれば良いでしょう。



プロジェクト発足時、GitLabとRedmineを連携するための最低限の設定

まず、プロジェクトを立ちあげた時に
最低限するべきことをリスト化してみます。

  • Redmine
  • GitLab
    • プロジェクト作成
    • b) Settingでissue_trackerを"Redmine"に変更する
    • c) Settingでリポジトリの更新を通知するためのwebhookを設定する
a) Redmine: Gitリポジトリ情報を設定

プロジェクト→設定→リポジトリ から、通常通りgitリポジトリの場所を設定します。

b) GitLab: Settingでissue_trackerを"Redmine"に変更する

プロジェクト→Settingからissue_trackerを"Redmine"に設定します。
project_idには、Redmineのproject_idを入力しましょう。

c) GitLab: Settingでリポジトリの更新を通知するためのwebhookを設定する

この設定を行うと、GitLabリポジトリにpushしたタイミングで
Redmineリポジトリ情報がリアルタイムで更新されます。
Hook URLのフォーマットはこちら↓

<redmine_url>/github_hook?project_id=<RedmineのプロジェクトID>



一体何が連携されるの?

GitLabのIssueタブがRedmineのIssueタブとリンクする

GitLabサイト上の "Issuesタブ" がRedmineのIssuesに移動します。

GitLabコミットメッセージのissue番号がRedmineのissueとリンクする

コミットメッセージの「#????」という部分が、RedmineのIssueにリンクします。
ただ、少し前のバージョンでは普通にRedmineチケットへのリンクされていたのですが、
現バージョン(6.9.2)ではRedmineのチケットのリンクが上手く作成されないようです。
リンクを復活させるには $RAILS_ROOT/lib/gitlab/markdown.rbのこの行
以下のようなモンキーパッチを当てます。*2

--- a/lib/gitlab/markdown.rb
+++ b/lib/gitlab/markdown.rb
@@ -178,7 +178,7 @@ module Gitlab
     end

     def reference_issue(identifier, project = @project)
-      if project.used_default_issues_tracker? || !external_issues_tracker_enabled?
+      if true
         if project.issue_exists? identifier
           url = url_for_issue(identifier, project)
           title = title_for_issue(identifier)

これおそらくJIRA連携を追加した時のバグだと思うのですが
英語力が足りなくて伝えられる気がしない…

Redmineのチケット連携機能がそのまま使える

たとえば、Issueに紐づくコミットの表示や、ステータスの変更/進捗率の変更なども
コミットメッセージから行うことが出来ます。
また、作業時間の連携も行えますので
コミットメッセージに設定した作業時間がチケットに反映され、
そのまま何もしなくてもプロジェクト毎/ユーザ毎の集計結果に反映されます。
実績管理も行っているプロジェクトではとても便利ですね。*3



ダッシュボート/スクラム開発 プラグイン

先のスライドではかんばんについても言及されていました。
そこで、Redmineでも同じようなことを実現するためのプラグインを紹介してみます。

といった機能拡張を提供するものです。
他にも同じようなプラグインがいくつか存在しますが、更新が活発なものを厳選しました。

Redmine Backlogs v1.0.6

Redmineスクラム開発を支援するためのプラグインです。
Redmineユーザの中では割とメジャーな気がします。
以下、RedmineのBacklogを利用した記事です。

Redmine Dashboard2

プロジェクト毎のタスクボードを提供します。
導入もかなり楽ですし、見た目/操作もわかりやすいので
単純にチケットのかんばんが欲しいだけならオススメ。
※以下、Gifzoで操作感を動画にしてみました
http://gifzo.net/l4gxyO45rI.mp4

Redmine ekanban

こちらもタスクボードを提供するプラグイン
デモサイトあり。
http://ekanban-demo.herokuapp.com/
account: guest
password: redmine



その他連携プラグイン

Redmine OmniAuth

GitLabのOmniAuthでRedmineを追加します。
こちらを導入すると、GitLabのアカウント管理をRedmineアカウントに集約することが出来ます。
設定手順としては以下の通りです。

弊社でも設定済みですが、導入に結構手間取った記憶が…(※後で追記するかもしれない)

Redmine_Gitlab_Merge_Request

Redmineからmerge_request出来るリンクがチケット詳細に追加されます。
ちょっと微妙かも。



まとめ

GitLabとRedmineを連携するための設定と、
連携するとどういったことが出来るか、
を書いてみました。
もちろん「この機能を使えば何でも解決します!」ということではなく、
こういうやり方もあるんだなぁ、というゆるい感じで
選択肢の一つにしていただければと思います。

*1:GitHubではなくて恐縮ですが…

*2:RPMインストールしているのであれば、こちらのファイルになります > /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/markdown.rb

*3:Redmineの設定で、リポジトリユーザとRedmineユーザの紐付けを忘れずに! http://blog.redmine.jp/articles/new-feature-1_1/automatic-spent-time-logging/

続・GitHubクローンのGitLabを5分でインストールした

前回の記事(GitHubクローンのGitLabを5分でインストールした)
思いのほかブクマされてしまったので補足記事を書くことにしました。
インストールした後に設定したほうが良い項目とミドルウェア競合検証、
その他Tipsなど書いてみました。

公式ドキュメント

以下、記事で書いていることはほぼすべてドキュメントにも書いてあります。
ご参照下さい。
https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md#configuration

コンフィグファイル準備

事前に /etc/gitlab/gitlab.rb を用意しておきます。
このファイルはGitLabをインストールする前に手動で作ってしまって構いません。*1

sudo mkdir -p /etc/gitlab
sudo touch /etc/gitlab/gitlab.rb
sudo chmod 600 /etc/gitlab/gitlab.rb

そして、コンフィグ設定後に

sudo gitlab-ctl reconfigure

すると、GitLabに反映するようです。



設定すべきコンフィグ値

GitLab URL

きちんとサイトのURLと合わせておきましょう。

external_url "http://gitlab.example.com/"
SMTP

メール通知などに使います。

gitlab_rails['smtp_enable'] = true
gitlab_rails['smtp_address'] = "smtp.server"
gitlab_rails['smtp_port'] = 456
gitlab_rails['smtp_user_name'] = "smtp user"
gitlab_rails['smtp_password'] = "smtp password"
gitlab_rails['smtp_domain'] = "example.com"
gitlab_rails['smtp_authentication'] = "login"
gitlab_rails['smtp_enable_starttls_auto'] = true

その他コンフィグ値

HTTPS設定

URLのHTTPS化/証明書パスなどを追記します。

external_url "https://gitlab.example.com"

nginx['redirect_http_to_https'] = true
nginx['ssl_certificate'] = "/etc/gitlab/ssl/gitlab.crt"
nginx['ssl_certificate_key'] = "/etc/gitlab/ssl/gitlab.key"
Google, Twitter, GitHub login設定

Omniauth設定を行うことにより、外部認証することが可能です。

gitlab_rails['omniauth_enabled'] = true
gitlab_rails['omniauth_providers'] = [
  {
    "name" => "google_oauth2",
    "app_id" => "YOUR APP ID",
    "app_secret" => "YOUR APP SECRET",
    "args" => { "access_type" => "offline", "approval_prompt" => "" }
  }
]

ちなみに弊社では、Redmine Omniauth
Redmineアカウントでログイン出来るようにしています。



ミドルウェア混在環境での検証結果

※ 2014/10/10追記分:既存のミドルウェアを利用する方法
そもそも、omnibus-installでミドルウェアをインストールせずに、
既存の環境を使いまわすことも出来るようです。
たとえば、Nginxが既にインストール済みであれば
omnibus-installではインストールせずに
既存のNginxコンフィグを追加するだけで動かすことが出来ます。

ex) 既にインストール済みのPostgresqlを利用する場合

1. /etc/gitlab/gitlab.rb に以下を追記します。

# postgresインストールを無効にする
postgresql['enable'] = false
# 他のミドルウェアをインストール回避する時はこんな感じ
#nginx['enable'] = false
#redis['enable'] = false
#postgresql['enable'] = false

# postgresの情報を入力
gitlab_rails['db_adapter'] = 'postgresql'
gitlab_rails['db_encoding'] = 'unicode'
gitlab_rails['db_database'] = 'gitlabhq_production'
gitlab_rails['db_host'] = '127.0.0.1'
gitlab_rails['db_port'] = '5432'
gitlab_rails['db_username'] = 'git' # Database owner.
gitlab_rails['db_password'] = 'git' # Database owner's password.

2. sudo gitlab-ctl reconfigure を実行してgitlabに反映
3. sudo gitlab-rake gitlab:setup を実行して、初期データを投入


これで、既存の環境にgitlabのDB環境の設定が完了です。*2

ボート競合を回避する方法

前回の記事でも触れましたが、GitLabをRPMインストールすると
NginxやらRedisやらも一緒に同梱されます。
ロケーションは別*3になるので問題ないのですがポートは競合してしまいます。

ただし、それを回避する設定をコンフィグに追記すれば大丈夫そうです。
以下にサンプルを用意しました。
nginx(88)/postgresql(15432)/redis(16379)/unicorn(18080) のポートを指定しています。

redis['port'] = 16379
postgresql['port'] = 15432
unicorn['port'] = 18080

external_url "http://gitlab.example.com:88/"

実際に検証してみましたが、
元からインストールされているミドルウェアに影響させずに
GitLabを起動することが出来ました。



まとめ

取り急ぎですが、前回の補足記事でした。
コンフィグに指定しているキーは
このattributesを変更しているようなので
このファイルを見てキーを確認すれば好きにカスタマイズできそうですね。
では良いGitLabライフを!

*1:というか reconfigure する前には作っておきましょう

*2:参考: http://stackoverflow.com/questions/23580268/gitlab-omnibus-configuration-for-postgres

*3:/opt/gitlab/配下

GitHubクローンのGitLabを5分でインストールした

※2015/6/22 最新版の手順に更新


※2015/1/7 アップグレードについての記事を書きました
http://d.hatena.ne.jp/toritori0318/20150106/1420558625


※2014/5/24 補足記事書きました
http://d.hatena.ne.jp/toritori0318/20140524/1400955383

で、お決まりのパターンでOSSに流れて、
GitLabとかやってみたんだけど、むっちゃムズいのねあれ。
まともにインストールできん。

http://d.hatena.ne.jp/rela1470/20140520

「GitLab インストール」
ググるとたいていまともにインストールしようとしている記事が見つかって
なにこれ使うまで面倒すぎ!
ってなりますよね。かつての自分もそうでした。


しかし最近のGitLabはRPMが提供されているので
これを使うと超絶簡単にインストールすることができます。



公式インストールドキュメント

https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md

素のAmazon Linuxにインストールしてみる

上記手順、ほぼそのままで進めてみます。

RPMダウンロード

ここからRPMダウンロードしましょう。
https://www.gitlab.com/downloads/

以下インストールコマンド

面倒なので一気に貼り付け。
(以下ubuntuバージョンです。他のディストリビューション手順も上記リンクから選択すると表示されます)

# 依存パッケージ
sudo apt-get install curl openssh-server ca-certificates postfix

# gitlab install
curl https://packages.gitlab.com/install/repositories/gitlab/gitlab-ce/script.deb.sh | sudo bash
sudo apt-get install gitlab-ce
# 反映
sudo gitlab-ctl reconfigure

# 確認
sudo gitlab-ctl status;

はい、インストール完了です。
http:/// にアクセスしてみましょう。

※初期ユーザ/パスワードはこちら

  • Username: root
  • Password: 5iveL!fe


bundlerやDBマイグレーションが実行されるので
元記事のように10秒とは行きませんが、
5分程度で何の問題も発生せずGitLabをインストール/実行することが出来ました。
アップグレードの際もドキュメントに手順が書いてあるので
特に詰まることなく進められると思います。
https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/update.md

GitLabの良いと思うところ

GitHubクローン」というところを強調されがちですが、
実は「Gistクローン(Snippets)」もついてくるので
社内Gistが欲しいときにも有用だと思います。
また、APIの豊富さHookの種類Redmine連携のしやすさ といった点もよいですね。
開発がとても活発なのもプラスポイントです*1

注意点

Nginx/PostgreSQL/Redisなどもインストールされるので、
元々環境があるときには
競合しないように素の環境にインストールするのが良いと思います。*2
Dockerなどに構築するのも良いですね。

まとめ

GitLabを簡単にインストールする方法を紹介しました。
かわいそうなのでもう
GitLabのインストールは苦行
とか言わないであげて下さい><
この記事が検索でGoogleトップにくるといいなぁ…(むりぽ)


※2014/7/5 いつの間にかGoogleトップ来てました!みなさまありがとうございます!

*1:毎月下旬、必ずバージョンアップしている

*2:補足記事にも追記しました http://d.hatena.ne.jp/toritori0318/20140524/1400955383

Redisのビットコマンドを用いた高速集計

Redisは様々な型をもっているので
単純な集計(increment)は結構柔軟にできたりします。


ですが、後から条件付きでクロス集計したい時があると思います。
Redisにはビット演算するコマンドがあるので
それを使っていろいろな集計をしてみました。

ビット関連コマンドリファレンス

GETBIT/SETBIT

Redis 2.2.0以降が必要。
ビットのオフセットに対してフラグを取得/設定します。

GETBIT key offset
SETBIT key offset value(1/0)
BITCOUNT

Redis 2.6.0以降が必要。
キーに対するビットカウントを取得します。

BITCOUNT key [start] [end] 
BITOP

Redis 2.6.0以降が必要。
複数のキーに対してビット操作を行うことが出来ます。
集計にはこの操作を使います。

BITOP operation(AND/OR/XOR) destkey key [key ...]
BITOP operation(NOT) destkey
BITPOS

Redis 2.8.7以降が必要。今回は利用しません。

BITPOS key bit [start] [end] 
簡易Example
127.0.0.1:6379> SETBIT keyA 1 1
(integer) 0
127.0.0.1:6379> SETBIT keyA 2 1
(integer) 0
127.0.0.1:6379> SETBIT keyA 3 1
(integer) 0
127.0.0.1:6379> SETBIT keyB 1 1
(integer) 0

127.0.0.1:6379> BITCOUNT keyA
(integer) 3  # keyA のカウント
127.0.0.1:6379> BITCOUNT keyB
(integer) 1  # keyB のカウント

127.0.0.1:6379> BITOP AND keyAandB keyA keyB
(integer) 1
127.0.0.1:6379> BITCOUNT keyAandB
(integer) 1  # keyAandB のカウント

127.0.0.1:6379> BITOP OR keyAorB keyA keyB
(integer) 1
127.0.0.1:6379> BITCOUNT keyAorB
(integer) 3  # keyAorB のカウント

127.0.0.1:6379> BITOP XOR keyAxorB keyA keyB
(integer) 1
127.0.0.1:6379> BITCOUNT keyAxorB
(integer) 2  # keyAorB のカウント

ユースケース1:日別集計

ここから具体的なシナリオを考えてみます。
2014/4/1-2014/4/3 に訪れたユーザ数をいくつかの軸で集計してみます。
キーを日別に分けておいて、後でBITOPで演算して集計します。
ビットのoffsetにはユニークユーザID(Integer)を指定します。

登録スクリプト:キーを「2014/4/1〜2014/4/3」に分け、1000ユーザを適当に登録
use strict;
use warnings;
use Redis;
use 5.10.0;

my $redis = Redis->new;

my @date = qw/
    visit:2014-04-01
    visit:2014-04-02
    visit:2014-04-03
/;


sub choice { $_[int(rand(scalar(@_)))] }

sub clear {
    $redis->del($_) for (@date);
}

sub setbit_all {
    for my $uid (1..1000) {
        $redis->multi();
        # 適当な確率でvisitする
        {
            # 4/1: 9割の確率でvisitさせる
            if(rand() < 0.9) {
                $redis->setbit($date[0], $uid, 1, sub {});
            }
            # 4/2: 5割の確率でvisitさせる
            if(rand() < 0.5) {
                $redis->setbit($date[1], $uid, 1, sub {});
            }
            # 4/3: 3割の確率でvisitさせる
            if(rand() < 0.3) {
                $redis->setbit($date[2], $uid, 1, sub {});
            }
        }
        $redis->exec();
    }
}

clear();
setbit_all();
集計スクリプト:日付でユーザ集計

A: では日別の結果をORで演算し、3日間全体のユニークユーザ数を集計しています。
B: では4/1と4/3をANDで演算し、2日間どちらも訪れたユニークユーザ数を集計しています。

use strict;
use warnings;
use Redis;
use 5.10.0;

my $redis = Redis->new;

# 集計する日付
my @date = qw/
    visit:2014-04-01
    visit:2014-04-02
    visit:2014-04-03
/;


sub p {
    my ($key) = @_;
    printf ("%s %d\n", $key, $redis->bitcount($key));
}

sub display {
    # 全部のbitcount
    p($_) for (@date);
    say '';

    # bitop
    # A: 3日間に訪れたユニークユーザ数合計
    $redis->bitop('OR', 'visit:total', @date);
    p('visit:total');
    say '';

    # B: 2014-04-01 / 2014-04-03 両方に訪れたユーザ数合計
    $redis->bitop('AND', 'visit:2day:total', 'visit:2014-04-01', 'visit:2014-04-03');
    p('visit:2day:total');
    say '';
}

display()

ユースケース2:クイズのユーザ集計

クイズを出題し、回答/正解したユーザ数などを集計します。
先程と同じく、ビットのoffsetにはユニークユーザID(Integer)を指定します。

登録スクリプト:キーを「クイズ+選択肢」に分け、1000ユーザを適当に登録

1ユーザに対して性別を登録し、さらに3つの問題をランダムで回答させています。

use strict;
use warnings;
use Redis;
use 5.10.0;

my $redis = Redis->new;

# 性別
my @genders = qw/man woman/;
# 質問に対する回答群
my @option1 = qw/
    answer:question1:optionA
    answer:question1:optionB
    answer:question1:optionC
/;
my @option2 = qw/
    answer:question2:optionD
    answer:question2:optionE
    answer:question2:optionF
/;
my @option3 = qw/
    answer:question3:optionG
    answer:question3:optionH
/;




sub choice { $_[int(rand(scalar(@_)))] }

sub clear {
    $redis->del($_) for (@genders, @option1, @option2, @option3);
}

sub setbit_all {
    for my $uid (1..1000) {
        $redis->multi();
        {
            # gender
            $redis->setbit(choice(@genders), $uid, 1, sub {});
            # 3つの問題にランダムで回答
            $redis->setbit(choice(@option1), $uid, 1, sub {});
            $redis->setbit(choice(@option2), $uid, 1, sub {});
            $redis->setbit(choice(@option3), $uid, 1, sub {});
        }
        $redis->exec();
    }
}

clear();
setbit_all();
集計スクリプト:正解したユーザの集計

各問題の正解を定義し、正解したユーザを集計します。
C: では全問正解したユーザを集計しています。
D: では男性の中で全問正解したユーザを集計しています。

use strict;
use warnings;
use Redis;
use Time::HiRes qw/gettimeofday tv_interval/;
use 5.10.0;

my $redis = Redis->new;

# 性別
my @genders = qw/man woman/;
# 質問に対する回答群
my @answers = qw/
    answer:question1:optionA
    answer:question1:optionB
    answer:question1:optionC
    answer:question2:optionD
    answer:question2:optionE
    answer:question2:optionF
    answer:question3:optionG
    answer:question3:optionH
/;

# 以下を正解とする
#   question1 > optionA
#   question2 > optionF
#   question3 > optionH
my @collect = qw/
    answer:question1:optionA
    answer:question2:optionF
    answer:question3:optionH
/;

sub p {
    my ($key) = @_;
    printf ("%s %d\n", $key, $redis->bitcount($key));
}

sub display {

    # 全部のbitcount
    p($_) for (@genders, @answers);
    say '';

    # bitop
    # C: 全問正解したユーザ
    $redis->bitop('AND', 'answer:all:correct', @collect);
    p('answer:all:correct');
    say '';

    # D: 男性の中で全問正解したユーザ
    $redis->bitop('AND', 'answer:man:all:correct', @collect, 'man');
    p('answer:man:all:correct');
    say '';
}

display()

性能評価

BITOPで集計した時の性能評価をしてみました。

環境
サーバ EC2
インスタンスタイプ c3.large
Redis 2.8.9
シナリオ:3000万人の集計

先ほどのユースケース2で「3000万人」登録し、BITOPで集計してみました。

結果ログ(Intervalを表示するように改修)
$ perl answer_summary.pl

------------------------------------
man count:14996246
woman count:15003754
answer:question1:optionA count:10003730
answer:question1:optionB count:10001089
answer:question1:optionC count:9995181
answer:question2:optionD count:9999537
answer:question2:optionE count:10002139
answer:question2:optionF count:9998324
answer:question3:optionG count:14998858
answer:question3:optionH count:15001142
------------------------------------

------------------------------------
answer:all:correct count:1667467
---- 0.006544(sec)

------------------------------------
answer:man:all:correct count:833621
---- 0.006825(sec)

BITOPの結果だけ見ると 6-7ms で終わっています。一瞬ですね。

まとめ

Redisのビットコマンドについて調査してみました。
単純なビット操作なので使い所は限られると思いますが、
Redisだけで高速な集計をしたい場合には有用ではないでしょうか。