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だけで高速な集計をしたい場合には有用ではないでしょうか。

すぐに子供の写真を見れるGoogle Chromeの拡張作った。

※元ネタはこちらです
http://soh335.hatenablog.com/entry/2013/02/10/011039
http://hisaichi5518.hatenablog.jp/entry/2013/02/01/003820


soh335さんのリポジトリをforkし、
DropboxAPIを利用してDropbox内の写真を表示するように改造してみました。

この拡張でやれること

  • Dropboxのアプリフォルダにある写真をシャッフルして表示
  • Dropboxの「Camera Uploadsフォルダ」内の写真を時系列に表示(ペンディング中)

最初は後者をやりたくて始めたんですけど、DropBoxAPIの仕様によりペンディング中です。
理由は後述。

準備(アプリ編)

ダウンロード

適当なフォルダで以下のコマンドを実行して下さい。

git clone https://github.com/toritori0318/chrome-tumblr-tile.git
OAuthライブラリダウンロード

oauth.jsとsha1.jsを同じフォルダにダウンロードします。
https://oauth.googlecode.com/svn/code/javascript/

準備(Dropbox編)

Dropboxアプリ登録

以下のURLからCreateAppします
https://www.dropbox.com/developers/apps

App key / App secret 取得

アプリケーション情報から "App key" と "App secret"をメモしておきます

アクセストークン取得

APIを使うためのアクセストークンを取得します。
Web上で取得する方法が見つからなかったので、
今回はPerlで以下のような簡単なスクリプトを作って実行しました。*1
https://gist.github.com/toritori0318/10553050
実行するとOAuth URLが表示されるので、
ブラウザでURLを表示してアプリの認証を行い、
コンソールに戻ってエンターキーを押すとトークンが表示されます。
これをメモっておきましょう。

準備(写真編)

Dropboxフォルダに写真を置く

上記まで完了していると
/アプリ/
という名前でDropboxフォルダが作成されています。
ここにお気に入りの写真をたくさん突っ込んでおきましょう。

準備(Chrome編)

拡張パッケージの設定

「設定」>「拡張」>「パッケージ化されていない拡張機能を読み込む」
で、ダウンロードしたアプリのディレクトリを指定します。

Chrome拡張オプション設定

最低限以下の項目を入力するだけでOKです。他のはデフォルトでよいでしょう。

  • AppKey
  • AppSecret
  • AccessToken
  • AccessSecret

Chromeでタブを開く

毎回写真がシャッフルされて、いい感じに表示されます。yatta!

内部説明

TumblrAPIとDropboxAPI仕様が異なる部分があるので
中でやっていることを少しだけ説明します。

  1. Dropbox metadata APIでフォルダ内のファイル一覧取得
  2. 20ファイル分のURLをDropbox thumbnail APIで取得し、blobデータをimageに変換し、DOMツリーに追加
  3. 下部にスクロールした時に、次の20ファイルに対して 2. を実行


DropboxAPIにはページングという気の利いた機能が無く、
フォルダ内のオブジェクトをまるっと取得する手段しかありません。
そのため、一気に処理してしまうとフォルダ内全部のサムネイルを取得してしまうので
ブラウザに表示している部分だけを少しずつ画像取得するようにしています。

Camera Uploadsをストリームっぽく表示してみたかった

元々の動機は
「奥さんが撮っている写真をリアルタイムでブラウザに表示出来たりしたら面白いかなー」
というので書き始めたんですが、DropBoxAPIにページングが無いため
「Camera Uploadsフォルダ」が膨れ上がると
タブを開くたびに大きなネットワークアクセスが発生してしまいます。
それがイマイチっぽい感じがしたので実装を断念しました。
ただ、今の仕様でも実装してみたら全然満足しているのでこのままでもいいかなーと思ってます。

まとめ

Chromeのタブを開くだけで幸せな気分になれます。最高。
子供でなくても、ペットや恋人の写真など表示しても捗るのではないでしょうか!

*1:cpanm WebService::Dropbox しておきましょう

microインスタンスはlimitかけると大きくパフォーマンスが向上する(※再追記あり)

※2014/07/02 T2インスタンスタイプとの比較 を追記しました。
※2014/03/13 他インスタンスタイプとの比較/m3.mediumの検証 を追記しました。



こちらの記事の二番煎じです。
cgroupで、お手軽CPU使用率制限


なるほど。
リソースにLimitかけるとstealを防げるためパフォーマンスも上がるというわけですね。
どのくらい変わるのか実験してみました。

cgroup前準備

sudo yum install libcgroup
sudo chkconfig cgconfig on
sudo service cgconfig start

cgroup設定

上記参考URLとほぼ同じ設定です。
実行時はcpu.cfs_quota_usを変動させて比較してみました。

sudo vi /etc/cgconfig.conf

# 以下を追加
group limittest {
 cpu {
   cpu.cfs_quota_us  = 250000;   # Max25%
   cpu.cfs_period_us = 1000000;
 }
 cpuacct {
 }
}

# 変更後はリスタート
sudo service cgconfig restart

定常的に使う時は、memory.limit_in_bytes なども設定して
メモリもlimitかけると良いかもしれません。

比較コマンド

perlのビルドの時間を比較してみました。

リミットなしバージョン
time perlbrew install perl-5.18.2
リミットありバージョン
time cgexec -g cpu:limittest /root/perl5/perlbrew/bin/perlbrew install perl-5.18.2

結果

- real user sys
リミットなし 186m59.721s 127m54.352s 51m52.487s
リミットあり(Max25%) 90m8.645s 15m4.317s 5m31.501s
リミットあり(Max30%) 75m3.421s 15m1.468s 5m24.844s
リミットあり(Max40%) 87m26.738s 23m40.645s 8m50.737s

リミット無しでsteal発生しまくりの時はおよそ3時間かかっていたのが、
リミットあり(Max30%)だと1時間15分ほど。2倍以上速い!!


※以下引用

いくつかの調査をした結果,
 ・直近の100秒のCPU使用率が20%を超えている場合は低速モードに移行する
 ・低速モードはCPU使用率の97%が使えない状態(steal)になる

Amazon EC2 Microインスタンスの挙動について

とあるので、20%ギリギリ超えるくらいがちょうどいいかなと予想していましたが
実際には30%が一番パフォーマンスが良かったです*1


vmstatでstealの値も見ていたんですが、
Max40%だと結構stealが発生していて、
Max30%の場合ごくごくわずかにstealが発生するくらいでした。
常にCPUMaxをつかう処理があるわけではないと思うので、
20%よりも少しばかり高めに設定したほうが
効率が良いのかもしれませんね。

まとめ

cgroupで制限するとmicroインスタンスでは大幅なパフォーマンス向上が見られました。
大きいビルドなど行うときには必須ではないでしょうか!






※追記 他インスタンスタイプとの比較

smallとの比較があったらよいのでは、というコメントを頂いたので
ついでにインスタンスタイプ別に比較してみました。
m1.smallだけでなく、グループ毎の下位クラスも一緒に比較してみました。

- real user sys
t1.microリミットありver 75m3.421s 15m1.468s 5m24.844s
m1.small 46m3.623s 29m54.188s 10m54.853s
m1.medium 25m33.974s 15m17.969s 5m0.599s
m3.medium 31m42.804s 18m45.818s 7m47.473s
c1.medium 25m1.527s 15m16.129s 5m34.785s
c3.large 16m41.832s 9m31.712s 3m25.633s


c3は予想通りの早さですね。
他のインスタンスも軒並み予想通りですが、一つだけ気になる結果があります。
m3.mediumインスタンスです。

m1.mediumとm3.medium

公式のインスタンスタイプを見ると
以下のようになっています。

- ecu vcpu
m1.medium 2 1
m3.medium 3 1


あれれ、数値上はm3.mediumの方が良さそうですね*2
なぜm1.mediumの方が速いのでしょうか?

ビルド時のスナップショット

m3.mediumのビルド時のvmstatのスナップショットです。

stealが40-50%ほど発生しています。
m1.mediumはというと、stealの発生はありませんでした。

予想:CPU共有タイプ?

公式ドキュメントからは確認出来なかったのですが、
おそらくm3.mediumもm1.smallと同じように
「CPU共有型なのではないか」と予想しました。
詳しくは以下のブログをご覧頂くと良いです。
AmazonEC2 m1.smallのCPU配分
http://php6.jp/linux/2012/01/20/amazonec2-m1-small/


vmstatの推移がm1.smallとm3.mediumで同じような挙動をしているので
ほぼ間違いないのではないかと思います。

m3.largeも試してみる

本当は上記インスタンスだけ比較して終わろうと思ったのですが、
m3のグレードを一つ上げたらstealしなくなるか、を確認してみました。

- real user sys
m3.large 17m1.667s 10m1.062s 3m21.857s
ビルド時のスナップショット


stealが消え、ほぼ公式と同じような性能が出ているように見えます*3
やはり、stealが発生するのはm3.mediumだけのようです。

追記:まとめ

単に性能比較して終わる予定だったのですが、
m3.mediumインスタンスで新たな発見がありました。
m1.mediumと比べて、
「m3.mediumの方が安いし性能もいいしこれでいいじゃんヒャッハー!!!」
と思っていましたが、mediumだけでみると「m1.mediumの方が速い」という結果でした。
ほげぇ。


匿名さん、コメントありがとうございました。


再追記:T2インスタンス

バーストありとバーストなしで追試しました。
バーストあり時は t2.micro/t2.small/t2.medium であまり変わらなかったので
どちらもt2.microの値です。

- real user sys
t2.micro(バーストあり) 14m46.383s 8m53.064s 0m26.884s
t2.micro(ベースライン:10%) -(※注1) -(※注1) -(※注1)

バースト時にはc3インスタンスと遜色ないですね。


また、以下はt2.microのバーストあり時の残高クレジットです。

30-22で、およそ8クレ消費しているようですね。


※注1
【悲報】ベースラインビルド、なんと
 2週間回し続けても一向に終わる気配がありませんでした!!!
 (途中で強制終了しました)
 もしかしたら実測としてはベースラインを大きく下回っているのかも…

*1:途中ネットワークアクセスなども発生していたり、というのもあると思います

*2:自分もこの辺りの性能比較を過去記事に書いています http://d.hatena.ne.jp/toritori0318/20140203/1391445479

*3:c3.largeと比較して