Redis のキーにプリフィックスつけるの面倒だなー自動的につけてくれないかなーと思い、 調べてみると Ruby に Redis-Namespace というものがあるらしい。 だけども、Perl では探しても見つからなかったので書いてみた。
レポジトリはこちら→Redis::Namespace
使い方 インターフェースは Perl Redis と一緒。 コマンドのキー名に当たる部分に、自動的にプレフィックスをつけてくれる。
use Redis; use Redis::Namespace; my $redis = Redis->new; my $ns = Redis::Namespace(redis => $redis, namespace => 'fugu'); $ns->set('foo', 'bar'); # $redis->set('fugu:foo', 'bar'); my $foo = $ns->get('foo'); # my $foo = $redis->get('fugu:foo'); 大体のコマンドには対応したつもり。 別のプレフィックスがついたキーには基本的にアクセスできなくなるので、 キー名の管理が少し楽になると思います。
でも、flushdb とか flushall すると全部消えるので気をつけてね!
最近Redis を使ったコードを書くようになったのですが、 キー名を毎回指定するのがだるいです。 Ruby には redis-objects というのがあって、 Redisのキーをオブジェクトとして扱うことができるようです。 きっと、Perl にも似たようなのあるだろ、って思って調べてみました。
ほしいもの 低レベルなRedisのライブラリはたいていメソッドとRedisのコマンドが一対一対応していて、 次のようなコードになると思います。
$redis->set('key-name', 'piyopiyo'); $redis->get('key_name'); でも、Redisに何か操作をしたいわけじゃなくて、 Redisのキーに対して操作をしたいので、 次のように書けるべきだと思うんです。
my $key = key($redis, 'key-name'); $key->set('piyopiyo'); $key->get(); Redis::Hash, Redis::List Redis::Hashと Redis::Listは Perlのハッシュや配列と同じ操作で Redis にアクセスできるようにするライブラリ。
use utf8; use warnings; use strict; use 5.014; use Redis::Hash; tie my %my_hash, 'Redis::Hash', 'hash_prefix', (server => 'localhost:6379'); # set hash_prefix:hogehoge piyopiyo # set hash_prefix:fugafuga fugufugu $my_hash{hogehoge} = 'piyopiyo'; $my_hash{fugafuga} = 'fugufugu'; # get hash_prefix:hogehoge piyopiyo say $my_hash{hogehoge}; # piyopiyo # keys hash_prefix:* say join ',', keys %my_hash; #fugafuga,hogehoge # keys hash_prefix:* # get hash_prefix:fugafuga # get hash_prefix:hogehoge say join ',', values %my_hash; #fugufugu,piyopiyo # del hash_prefix:hogehoge delete $my_hash{hogehoge}; tie とかよくわかない。 Perl の黒魔術を見た気がしました。
数日前に社内IRCで「スマートな非復元抽出の方法はないか」と話題になったので、 ランダムサンプリングのアルゴリズムについて調べたり考えたりしてみた。
復元抽出 非復元抽出の手法って調べてもなかなか出てこない・・・。 ひとまず、復元抽出についてまとめてみましょう。
線形検索 一番簡単な実装方法。 どの区間に入るかを線形検索して求める。 選択肢の個数nとすると計算量はO(n)。
use strict; use warnings; use List::Util qw(sum); sub linear_search_method { my $weights = shift; my $num = shift; my $sum = sum @$weights; my $length = @$weights; my @a; for (1..$num) { my $r = rand($sum); for my $i(0..$length-1) { $r -= $weights->[$i]; if($r < 0) { push @a, $i; last; } } } return \@a; } print join ', ', @{linear_search_method [1,2,3], 100}; バイナリサーチ あらかじめ累積分布表を作っておき、どの区間に入るかをバイナリサーチ。 準備にO(n)、選択に O(log n)かかる。
少し前にGoogle Cloud Messaging for Chrome が発表されました。 Android向けに提供されていた Push 通信の仕組みである GCM の Chrome 版です。 ちょうど GCM for Android に触っていたところだったので、 for Chrome のほうも試してみることにしました。
拡張機能の登録 公式ページの説明にしたがって、 APIを使えるようにします。 GCMはOAuth2.0で認証を行うので、
クライアントIDを作る Refresh Token を作る という2ステップが必要。
クライアントIDを作る まず、新しい OAuth2.0 のアプリを作成。 拡張機能をアップロードする予定のGoogleアカウントで以下の作業して Client IDを作ります。
Google APIs Console にログインする ** Create… ** メニューから新しいプロジェクトを作成 “Services” を開いて ** Google Cloud Messaging for Chrome API ** を有効化 “API Access” を開いて ** Create an OAuth 2.0 cliend ID… ** というボタンをクリック branding information を適当に入力 “Application Type” という項目の “Web application” を選択 “Create client ID”!! Client ID と Client Secret が表示されるのでメモしておきましょう。
RaspberryPi に cron を仕込んで定期実行をやってみようと考えました。 cron の設定自体は crontab -e コマンドを実行すれば簡単にできます。 ただ、これだけだとちゃんと動いているか少し心配なので、 エラーが起きた時に何か通知して欲しい。 普通なら設定ファイルに MAILTO=hogehoge@example.com と書いておくと メールが送られるはずなのですが、 メールサーバが動いてないのでうまくいかない・・・。
そういうわけで、RaspberryPiからメールを送るための設定をしたのでメモ。
MTAをインストールする Raspberry Pi には標準でMTA(Message Transfer Agent)が入ってないようなのでインストール。 今回はPostfixを採用
sudo apt-get install postfix 最初、Sendmailも試してみたんだけど、送信者マスカレードがなぜかうまく行かなったので断念。 後述するように、この設定がないとスパムフィルタに引っかかってしまうのです。
プロバイダのSMTPにリレーしてもらう 実際にメールを送りには以下の条件を満たす必要があるようです。
送信元のドメインを引ける 固定IPからのアクセス 固定IPなんて自前で持ってないし、 cron からのメールは送信元が pi@raspberrypi になってしまいドメインを引けません。 そのためそのままではスパムメールとして扱われてしまい、メールが届きません。
そこで、プロバイダが提供しているSMTPサーバにメールをリレーしてもらいます。 /etc/postfix/main.cfに以下の行を追加します。
sender_canonical_maps = regexp:/etc/postfix/canonical relayhost = [smtp.example.com]:587 smtp_sasl_auth_enable = yes smtp_sasl_password_maps = hash:/etc/postfix/relay_password smtp_sasl_security_options = noanonymous プロバイダにリレーしてもらうには SMTP-Auth で認証する必要があるので、 ユーザ名とパスワードを設定しておきます。
smtp.example.com hogehoge:your-password postmapコマンドを使って、Postfixから扱える形式に変換します。
$ postmap hash:/etc/postfix/relay_password さらに、エンベロープのFromがプロバイダから提供されたメールアドレスでないと メールをリレーしてくれないので、 すべてのメールのFromをすべて書き換えるよう設定します。
RaspberryPiをネットつないでみたので、PythonからいろんなURLを叩いて遊んでいたんだけど、 一部のhttps通信が Connection Timed Out で失敗しちゃう。 プログラムの問題かと思ったけど、curlで叩いてもやっぱりタイムアウト。 Macで全く同じ事をするとうまくいく・・・。 いろいろ調べて、何とかしてみたお話。
原因 接続先がTLSv1にしか対応していないのにSSLv3でアクセスしようとしていたことが問題だったらしい。 明示的にTLSv1を使うように指定して curl を叩いてみるとうまくいった。
$ curl --tlsv3 https://hogehoge なぜRaspberryPiではダメで Macでは成功するのか、という根本的な原因はよくわからなかった。 SSLv3に対応していないなら自動的にフォールバックしてくれてもよさそうなものだけど、 なぜうまく行かないんだろう・・・?
Pythonでの対処 PythonでもTLSv3を使えばうまくいくはずなんだけど、 暗号化方式を指定するオプションは見当たらない(2.7での話)。 どうやら標準ライブラリのファイルを直接書き換えるか、 実行時に中身を入れ替えるかしないとできないみたいだ。 この問題普通のUbuntuでも起こるらしく、 そのフォーラムで置き換えコードを見つけた。
import httplib from httplib import HTTPConnection, HTTPS_PORT import ssl class HTTPSConnection(HTTPConnection): "This class allows communication via SSL." default_port = HTTPS_PORT def __init__(self, host, port=None, key_file=None, cert_file=None, strict=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, source_address=None): HTTPConnection.__init__(self, host, port, strict, timeout, source_address) self.key_file = key_file self.cert_file = cert_file def connect(self): "Connect to a host on a given (SSL) port.
Twitter の API リファレンスを久しぶりに見たら、 Application-only authenticationとかいうのを発見。 特定のユーザと関連付けられない代わりに、普通に認証するより制限が緩いみたい。 3月に追加されてたらしい。
知らなかった・・・。 最近API叩いてなかったからな・・・。
便利そうなので、Python用のTwitterライブラリであるTweepyから使ってみた。
AuthHandler Tweepy用のAuthHandler。 認証部分は TwitterのApplication-only authenticationを試してみた のページからほぼコピペ。
import tweepy import urllib import urllib2 import base64 import json class AppAuthHandler(tweepy.auth.AuthHandler): TOKEN_URL = 'https://api.twitter.com/oauth2/token' def __init__(self, consumer_key, consumer_secret): token_credential = urllib.quote(consumer_key) + ':' + urllib.quote(consumer_secret) credential = base64.b64encode(token_credential) value = {'grant_type': 'client_credentials'} data = urllib.urlencode(value) req = urllib2.Request(self.TOKEN_URL) req.add_header('Authorization', 'Basic ' + credential) req.add_header('Content-Type', 'application/x-www-form-urlencoded;charset=UTF-8') response = urllib2.urlopen(req, data) json_response = json.loads(response.read()) self.
先日、社内 ISUCON(良い感じにスピードアップコンテスト) に参加してきました。 Livedoorで開催されたISUCONのミニ版で、 Webアプリをひたすら高速化するコンテストです。
高速化の対象はNoPaste。 テキストを共有するWebアプリです。 テキストの投稿・投稿の閲覧・投稿にスターをつける の3つの動作ができる簡単なアプリです。
新卒 vs 先輩ということで、それぞれ4チームが参戦。 チームは二人一組で僕は @Maco_Tasu くんと一緒でした。 @Maco_Tasuくんのブログも参照。
Recent Posts 生成クエリの高速化 高速化前のアプリのベンチマークの結果、スコアは77(≒1分あたりの捌いたリクエスト数)。 何も考えずにデータベースの全行を舐めるクエリを書いていたので、まあ、妥当なスコアですね。
重いのはサイドバーに表示される Recent Posts。 Recent Posts は表示回数が多く、 複数の行、複数のテーブルへのアクセスが発生するため、 きっとここがボトルネックになるだろうと予測してました。 そこで最初にこの部分を解決することにしました。
とりあえずインデックスを張る クエリを修正してアクセスする行を最小化 スターのカウントした結果をテーブルに格納 オリジナルのデータベース構成では、スターした回数だけ行が増えてました 必要なのは投稿ごとのスター数なので、独立したテーブルに この時点で早くも重大なバグを組み込んでしまったことに、この時はまだ気がついていなかった・・・ nginxによる静的ファイル配信 僕がクエリをいじっている間、@Maco_Tasuくんには サーバの設定をお願いしました。
ログの様子を眺めてみると、cssとかjsとかの静的ファイルが結構な量ありました。 最初のスクリプトでは静的ファイルの配信もアプリでやってたので、 これをnginxを使って配信するように変更。 その他のリクエストはリバースプロキシを設定してアプリに投げます。
Starlet と Server::Starter リバースプロキシを設定する際にアプリの起動スクリプトを編集する必要があったので、 ついでに起動時の設定を色々変更。 PSGI実行のStarletというのが速いと聞いてこれを採用。 Starlet使い方調べてたら、Server::Starterを使った例が出てきたので一緒にインストール。 ワーカーの数の数は適当に10個にしました。
ここで2回目のベンチマークを実行。 スコア1300程度で、その時点のトップ!
SSIを使ったサイドバーの埋め込み お昼を挟んで、さらなる高速化を目指します。
topコマンドを眺めているとPerlで作ったアプリの負荷が大きい。 ほとんどテンプレートエンジンを呼び出しているだけの単純なコードなので、 ここを高速化するのは面倒くさい。 そこで、前段のnginxでキャッシュする作戦を採用することにしました。
もっともキャッシュが有効なのはサイドバーだろうと予想。 クエリの最適化をしたとは言え、サイドバーには100件程度の投稿が表示されるので、 クエリ実行にもレンダリングにも時間がかかるはず。 さらにすべてのページでサイドバーは共有できるので、大幅な高速化が期待できるはずです。
過去のISUCONの記事にSSI(Server Side Include)を使った例があったのを思い出し、 これを使ってサイドバーのみキャッシュ、nginx内でサイドバーを埋め込むように。
僕が SSIのタグ埋め込み、 @Maco_Tasu くんにnginxのキャッシュ設定を行ってもらうという役割分担で作業を再開しました。
emacsを使って編集している最中にシェル操作をしたくなって, C-z を押してバックグラウンドにしてシェル操作. その後,emacsに戻ってくるには fg コマンドを打つ必要があるんだけど, 間違えてもう一回 emacs を新しく立ち上げるというミスを何度もやってしまう・・・.
これに対し,猫型さんが複数起動しようとすると警告を出してくれるようにしてくれました. (出、出〜〜〜〜wwww emacsをふたつ以上実行奴〜〜〜〜www)
警告してくれるのはありがたいんだけど, これだとシェル操作中に別のファイルの編集をしたいと思っても,警告が返ってくるだけ. emacs をフォアグラウンドに出して,ファイルの指定をやり直さなきゃいけない. 僕はファイルの編集をしたいんだ!! わかったから早く編集させろ!!!
emacsclient 単なる警告じゃなくて, 「裏で動いていたemacsを復帰させ,新しいバッファを開く」 ところまで自動的にやってくれると嬉しいですね.
まず,emacs をデーモンモードで起動しておきます.
emacs --daemon emacsclient コマンドでファイルを開くと, emacs デーモンさんが新しいバッファで開いてくれます. オプションに -nw を指定しておくと現在の端末で閲覧編集することができます.
emacsclient -nw hoge.txt 終了するにはC-x 5 0. C-x C-cでも終了できるけど, デーモンにバッファが残ってしまうみたい.
aオプションでemacs デーモンが起動してないときに 編集に使うエディタを指定できる. 空っぽにしておくと,emacs をデーモンモードで起動してくれる.
emacsclient -nw -a '' hoge.txt emacs デーモンを終了させるのは以下のコマンド.
emacsclient -e '(kill-emacs)' emacsclient に対して alias を作っておけば, 複数起動かどうか意識せずに使えますね.
alias emacs='emacsclient -nw -a ""' 参考 emacsclientを使おう emacsclient の使い方の種類と、便利な使い方 emacsclientを終了する方法
LaTeXで書いた文章を電子書籍にしたくなったので, LaTeX2EPUBを使ってみました.
LaTeX2EPUBはLaTeXMLとReVIEWに依存しているようなので, それぞれインストールしていきます. あと,数式の変換とかにLaTeXを使っているので別途用意する必要あり. いろんなディストリビューションがあるけど, ここでは TeX Live 2012 を使いました.
LaTeXML のインストール LaTeXMLはLaTeXの文章をXML形式に変換するソフト. そこからさらにXSLTを使ってXHTMLへ変換できる. ドキュメントに従って 依存するライブラリをインストール.
perl -MCPAN -e shell cpan> install DB_File, Parse::RecDescent, File::Which cpan> install XML::LibXML, XML::LibXSLT ドキュメントが少し古いらしく,これだけでは不十分だった. 追加でParse::RecDescentとImage::Magickもインストールしておく.
cpan> install Parse::RecDescent cpan> quit yum install ImageMagick-perl 後はソースを取ってきてmakeするだけ. 現時点での最新版0.7.0をインストールした.
wget http://dlmf.nist.gov/LaTeXML/releases/LaTeXML-0.7.0.tar.gz tar zxvf LaTeXML-0.7.0.tar.gz cd LaTeXML-0.7.0 perl Makefile.PL make make test make install ReVIEW のインストール ReVIEWは簡単なマークアップ言語で書かれたテキストから PDFやEPUBを作成するためのスクリプトです. このなかのEPUB作成機能に依存しているようなのでインストールしておきます. ReVIEWはgemで簡単インストール.
gem install review LaTeX2EPUB のインストール LaTeX2EPUB本体をインストール. 本家の日本語化対応が少し不十分だったので 改造版を上げといた. これをダウンロードしてパスの通ったところに置けばインストール完了.