weblog of key_amb

主にIT関連の技術メモ

DateTime::Format::Strptime にパッチを送った話 #Perl

CPAN モジュールの DateTime::Format::Strptime は DateTime と同じ Dave Rolsky さんが開発しているもので、日付時刻を含む文字列のパースによく使われているのではないかと思います。

このモジュールの挙動が v1.58 から変わっていました。

#!perl
use feature qw(say);
use DateTime::Format::Strptime;
my $strp = DateTime::Format::Strptime->new(
    pattern => '%Y%m%d',
);
for my $str (qw/20160330 access_log.20160330/) {
    my $dt = $strp->parse_datetime($str);
    say "$str => undef " and next unless $dt;
    say "$str => " . $strp->format_datetime($dt);
}

これを実行すると、下のようになりました。

# v1.57 以前
20160330 => 20160330
access_log.20160330 => 20160330

# v1.58 以降
20160330 => 20160330
access_log.20160330 => undef 

そう。
日付時刻を表す文字列の前に、別の文字列があるようなものはパースできなくなっていたのです。

Changes や差分を見ていたところ、どうやら v1.57 〜 v1.60 の間に互換性を崩す大幅な変更があったようです。
ので、仕様変更かなーと同僚の間で話していたのですが、作者に聞くのが確実だろうと思ったので、GitHub の Issued で聞いてみました。

f:id:key_amb:20160401074307p:plain

なんとバグだったようです。

せっかくなので、直せるかどうか手元でやってみることにしました。

GitHub で fork して、まずはテストを書いて再現させてみました。 テストを書いたところ、どうやら 20160330.log のように、後ろに文字列があるケースはパースできるということに気がつきました。

…で、試行錯誤した結果、正規表現を数文字変えれば問題の挙動が直り、既存のテストも通るということがわかりました。
ので、Pull Request を送りました。

取り込まれる過程で少し修正されていましたが、めでたく修正版の v1.67 がリリースされました。

めでたしめでたし。

Gotanda.pm #8 で memcached-cli について喋ってきた #gotandapm #memcached

既に2回このブログで紹介している memcached-cli について、昨日開催された第8回 Gotanda.pm で LT してきました。

資料はこちらです。今回は Speaker Deck に上げてみました。

Speaker Deck だとスライド中のリンクが無効になる(?)ようなので、いくつか貼っておきます:

残念ながら時間が足りず、デモが幻に終わってしまいました。

デモでは、Memcached の slab reassign, lru_crawler 辺りの機能を memcached-cli で触ってみるつもりでした。
せっかく準備していたので、Qiita に手順と結果をアップしておきました。

ご興味のある方はご覧ください。

感想など

いろいろありましたが、特に DBIx::Class の深い話を聞けたのが面白かったです。

あと、そういえばサイボウズさんの yrmcdsMemcached のテキストプロトコルにも対応しているのでした、というのを思い出しました。
ので、後で memcached-cli で操作してみるというのをやってみたいなと思いました。

今回、LT 枠が埋まった後の、遅いめの参加表明でしたが、柔軟に対処していただきました。

ありがとうございました!

Ruby コード中の "=" や "=>" を整列してくれる "ruby-align" というツールを作った

こちらになります。

github.com

使い方・動作例

ruby-align [-f] ファイル で結果を標準出力に吐きます。
README には vim の設定例を書いてますが、エディタから呼んでその場でコード整形すると捗るかと。

動作例としては、README に挙げたものをこちらにも貼っておきます:

## before
a = 1
dddd ||= (1..3)
bb -= 20
e = { foo: 1, bar: 'Bar' }

stash = {
  foo: 123,
  'bar' => 'Bar',
  "bazz" => %w[ x y z ]
}

## after
a      = 1
dddd ||= (1..3)
bb    -= 20
e      = { foo: 1, bar: 'Bar' }

stash = {
  foo:      123,
  'bar'  => 'Bar',
  "bazz" => %w[ x y z ]
}

一応、ネストしたハッシュや制御構文でのインデント変更も、テストケースに書いたものは対応できているはずです。(CI も通っています。)

これを作ったきっかけ

Perl でコードを書く場合、私はフォーマットのツールとして Perltidy を使っています。
この記事で紹介されているような Vim の設定をして、ハッシュ定義の => 等を揃えるのに多用しています。

…で、Ruby でそういうの無いの、と思って探したところ、どうも RuboCop に行き着くようです。*1

が、実際に試してもみたのですが、どうも思った通りに動いてくれない*2し、求めてもいない警告をたくさん出してくれます。

で、ついカッとなって作ってしまった感じです。

最初は VimScript で書こうとしてみたのですが、初心者すぎてつらかったのと、perltidy のようなインタフェースのものを作った方が他のエディタにも組み込みやすかろうということで Ruby に方向転換しました。*3

また、Ruby で書くにあたっては、始め RuboCop でも使われている whitequark/parser を使おうとしました。 が、求めている機能に対して牛刀感がして、やめてしまいました。

…で、結局ゴリゴリと正規表現を書いてます。

これはこれで、やはりつらみはある*4のですが、ある程度のパターンについてはテストを書いたので、ぶっ壊れることはそんなに無いかと思います。

自分のために作ったという部分が大きいですが、もしお気に召しましたらご自由にお使いくださいというところです。

(ただし、コードが壊れても責任は負えませんので、バックアップはお忘れなく。。)

参考

Vim で使える汎用の整列ツールとしては、昔から Align を使っているのですが、調べ直してみたところ Aligntavim-easy-align というのが見つかりました。

こういう整列ツールでも、だいたいやりたいことはできそうだなと思いました。

key: "value" 形式のエントリと "key" => "value" のものが混ざってるとつらそうですが、まあ、両者が混ざったハッシュってそんなに無い(?)のかなと思ったりもします。

*1:あと ruby-beautifyを試したと思います。

*2:特に key: "value" なパターンを揃えるオプションは見つかりませんでした。

*3:VimScriptの残骸がここにあります。

*4:1ヶ月後には書いた正規表現の意図を忘れてそう。

memcached-cli の v0.9.4 までの追加機能の紹介

3/24 の記事で、最近作った memcached-cli というツールの紹介をしました。

keyamb.hatenablog.com

別に困ってはいないのですが、もっと便利にしたので、Changes から抜粋して追加機能を紹介しておきます。

昨日か一昨日に手元に入れて試してみた、という方はぜひアップデートしてみてください。

gets, add, replace, append, prepend, cas, touch, incr, decr の実装

v0.6.0 以前は get/set/delete しかできませんでしたが、これで memcached/doc/protocol.txt に書かれている Retrieval, Storage コマンド + α のデータ操作が全てできるようになりました。

なんとなくコマンドの解説:

  • add は該当キーのデータが無いときのみ成功します。
  • replace は該当キーのデータが有るときのみ成功します。
  • cas は gets で取得した cas 値に基いて更新を試み、cas 値が異なっていたら更新しません。

append, prepend はこんな感じですね:

memcached@127.0.0.1:11211> add foo Foo
memcached@127.0.0.1:11211> append foo .extension
memcached@127.0.0.1:11211> prepend foo prefix-
memcached@127.0.0.1:11211> get foo
       key:    foo
     value:    prefix-Foo.extension
    length:    20B
     flags:    0

get, gets で複数キーの同時取得に対応

元々 get, gets は複数キーを渡せたので、 memcached-cli でもできるようにしました。

で、複数データ取得した時には下のように LTSV で表示するようにしました。

memcached@SERVER:PORT> gets foo bar
Key:foo Value:Foo       Length:3B       Flags:0 Cas:219
Key:bar Value:Bar       Length:3B       Flags:0 Cas:220

cachedump コマンドで VALUE も取得して表示

以前の cachedump コマンドは stats cachedump の結果をそのまま出力していましたが、上のような LTSV にして、かつデータの中身も合わせて表示できたら便利だな、と思ったので、実装しました。

memcached@SERVER:PORT> cachedump 1
Key:foo Value:Foo       Length:3B       Expire:2016-03-26 00:30:27      Flags:0 Cas:219
Key:bar Value:Bar       Length:3B       Expire:2016-03-26 09:53:19      Flags:0 Cas:220
Key:baz Value:Baz       Length:3B       Expire:2016-03-26 10:49:05      Flags:123       Cas:221

実運用だと、 display でざっと全体の様子をつかんで、気になるスラブ・クラスを cachedump してみて、場合によっては中身を見る、という調査の流れがよくあるかと思いますが、そんなときにこれを使うとすごく捗りそうです。

stats, settings の結果を正規表現でフィルタ可能に

小ネタですが、よく見る指標がある人にはちょっと便利かと。

memcached@127.0.0.1:11211> stats (get|set)
# stats
#                Field          Value
               cmd_get            248
               cmd_set            515
              get_hits             46
            get_misses            202
memcached@127.0.0.1:11211> settings ^lru
# stats settings
#              Field           Value
         lru_crawler              no
   lru_crawler_sleep             100
 lru_crawler_tocrawl               0

ランダムデータ生成機能を実装しました

> randomset [<NUMBER> [<MAX_LENGTH> [<MIN_LENGTH> [<NAMESPACE>]]]]
> randomset                     # 100 個生成
> randomset 50                  # 50 個生成
> randomset 50 1024             # データ長 1-1024B の範囲で生成
> randomset 50 1024 256         # データ長 256-1024B の範囲で生成
> randomset 50 1024 256 sample1 # キーの prefix を 'sample1:' にする

これを使って、前回も話題に触れたネームスペースごとの統計取得・表示を簡単に試すことができます。

memcached@127.0.0.1:11211> settings detail  # 設定を絞込み表示
# stats settings
#              Field           Value
      detail_enabled              no
memcached@127.0.0.1:11211> detail on
OK - Enabled stats collection for detail dump.
memcached@127.0.0.1:11211> settings detail
# stats settings
#              Field           Value
      detail_enabled             yes
memcached@127.0.0.1:11211> \dd  # まだデータが無い
memcached@127.0.0.1:11211> detail on
OK - Enabled stats collection for detail dump.
memcached@127.0.0.1:11211> randomset 1000 0 0 sample1
Random Generate. [....................]
Complete.
memcached@127.0.0.1:11211> randomset 1000 0 0 sample2
Random Generate. [....................]
Complete.
memcached@127.0.0.1:11211> randomset 1000 0 0 sample3
Random Generate. [....................]
Complete.
memcached@127.0.0.1:11211> \dd
PREFIX sample3 get 0 hit 0 set 1000 del 0
PREFIX sample2 get 0 hit 0 set 1000 del 0
PREFIX sample1 get 0 hit 0 set 1000 del 0

(これで、別に作っていたデータ生成ツールは要らなくなりました。)

memcached-tool の "dump" コマンドをインポート

memcached-cli dump_all というコマンドにしました。

memcached-tool の "dump" は expire 0 のデータが正しく dump できない不具合があるのですが、こちらでは直しました。*1

ついでに dump から restore できるようにした

restore_dump <dumpファイル> でできます。
cat <dumpファイル> | telnet SERVER PORT で行けるかなと思ったのですが、やってみたら CLIENT_ERROR の嵐になってしまいましたので、このコマンドを使うといいと思います。

実運用上も、Memcached サーバを再起動しないといけないときに dump して別サーバに restore して IP アドレスを付け替える、みたいな使い方ができるかもしれません。

実装していないコマンドも実行できるようにしました。

call <command> [<arguments>...] という形式で、任意のコマンドを直接 Memcached サーバに投げれるようにしました。

これで、まだ実装していない stats connsslabs reassign <src class> <dest class> のようなコマンドも実行できます。

memcached-tool の sizes コマンドも実装していませんが、 call stats sizes で OK です。

今後 memcached に新しいコマンドが追加されてもなんとかなりそうです。

終りに

memcached 初心者の学習用にも良いかもなー、とも思いました。

もうたぶん、あまりいじることはないと思いますが、何かあれば GitHub のイシューなりでお知らせください。

それでは、Have fun!

*1:本家では修正 PR が出ていますが、まだ取り込まれていません。

Memcached に対話的にコマンドを実行できる "memcached-cli" を作った

Redis*1 には redis-cli というツールがあって、Redis サーバに接続して対話的にコマンドを発行して、結果を得ることができます。

Memcached*2 の場合、いい感じに使えるツールがなくて*3、必要なときはいつも TELNET でつないでコマンドを実行していました。

それもたまにしかやらないので、 set コマンドの打ち方とか、よく忘れて調べていました。(難易度高い)

…というわけで、そういう操作を簡単に対話的に実行できるものを作りました。

github.com

CPAN にも公開していますので、 cpanm App::Memcached::CLI でインストールできます。
これに script/memcached-cli というスクリプトを同梱しています。

まだすべてのコマンドを網羅できたわけではないですが((執筆時点の App-Memcached-CLI のバージョンは v0.5.3))、現時点でも、特にサーバ運用管理者にとってはそこそこ便利なものになっているのではないかと思います。

以下、memcached-cli の使い方の紹介です。

対話モード

接続

接続先を -a|--addr オプションか、第1引数で渡してください。

% memcached-cli memd.foo.local:11211
% memcached-cli -a memd.foo.local    # デフォルト 11211 ポート
% memcached-cli                      # localhost に接続

接続すると下のように対話モードに移行します。

memcached@127.0.0.1:11211> 

get/set

特に違和感なく使えるかと思います。

memcached@127.0.0.1:11211> get foo
Not found - foo
memcached@127.0.0.1:11211> set foo WHAAH!!!
OK
memcached@127.0.0.1:11211> get foo
       key:    foo
     value:    WHAAH!!!
     flags:    0
    length:    8

expire (exptime)を指定する場合、value の後に秒数で渡してください。 デフォルトでは expire 0 で set するので、 expire しません。

memcached@127.0.0.1:11211> set short WRYYY!!! 5
OK
memcached@127.0.0.1:11211> get short
       key:    short
     value:    WRYYY!!!
     flags:    0
    length:    8
# 5秒後
memcached@127.0.0.1:11211> get short
Not found - short

expire の後ろに flags (任意の非負整数)を渡すこともできます。

delete

KEY 指定で delete できます。

memcached@127.0.0.1:11211> delete foo
OK
memcached@127.0.0.1:11211> get foo
Not found - foo

memcached-tool からの移植機能

Memcached 付属の memcached-tool *4はスラブのクラスごとの統計情報表示など、運用管理者にとって便利な機能を備えています。

一部を除いて memcached-cli に移植しましたので、対話的に実行することができます。

memcached@127.0.0.1:11211> display
  #  Item_Size  Max_age   Pages   Count   Full?  Evicted Evict_Time OOM
  1      96B         0s       1       0     yes        0        0    0
 18     4.4K         0s       1       0     yes        0        0    0
 20     6.9K         6s       1       2     yes        0        0    0
 22    10.8K         6s       1       1     yes        0        0    0
 24    16.9K         6s       1       1     yes        0        0    0
 26    26.5K         0s       1       0     yes        0        0    0
  :
memcached@127.0.0.1:11211> \d # 同じ

display はよく打ちそうだなと思ったので、 \d というエイリアスを作ってみました。

対話モードの終了

\q または quit または exit で終了します。

その他の機能

help または \h で実装されているコマンドが表示されます。
\h <command> で一部のコマンドは、より詳しい情報が見れます。

memcached@127.0.0.1:11211> \h

[Available Commands]
\h, help                    Show help (this)
\v, version                 Show server version
\q, quit, exit              Exit
\d, display                 Display slabs info
\s, stats                   Show stats
\c, settings, config        Show settings
\cd, cachedump, dump        Show cachedump of specified slab
\dd, detaildump             Show detail dump
detail                      Enable/Disable detail dump
get                         Get data of KEY
set                         Set data with KEY, VALUE
delete                      Delete data of KEY
flush, flush_all            Invalidate whole data

Type \h <command> for each.

memcached@127.0.0.1:11211> \h cachedump
(略)

memcached-tool から移植したのは display に加えて stats, settings あたりですね。

その他、運用管理向けの機能として、cachedump を実装しました。 cachedump <CLASS> [<SIZE>] で特定スラブのアイテムのキーと expire を <SIZE> の個数だけ一覧できます。デフォルトの <SIZE> は20にしました。

また、下の記事で紹介されていたネームスペースごとの統計も detaildump で表示できるようにしました。

Cache::Memcached(::Fast) のネームスペースは最後に区切り文字をいれた方が良い話とネームスペース毎に統計を取る方法 - blog.nomadscafe.jp

この統計情報を起動時に有効化してない場合、有効化するには stats detail on を叩かないといけません。
ので、それも detail on で、できるようにしました。
ついでに detail off で無効化できるようにしました。

バッチモード

更に、これらを対話モードだけでなく、バッチで実行できたら、より便利だなと思ったので、 v0.4 でできるようにしました。
redis-cli もバッチ実行できますし。

これによって、 memcached-tool から移植した機能については memcached-tool と同じようにコマンドラインから実行できるようになりました。

$ memcached-cli localhost display
  #  Item_Size  Max_age   Pages   Count   Full?  Evicted Evict_Time OOM
  1      96B         0s       1       0     yes        0        0    0
 18     4.4K       164s       1       1     yes        0        0    0
 24    16.9K       164s       1       2     yes        0        0    0

# ※ `\` で始まるエイリアスは適当にエスケープなりしてあげる必要があります。
$ memcached-cli localhost \\cd 18
ITEM memcached-roaster:random-generate1:data54 [3724 b; 1458806126 s]

flush_all または flush を実行すると、全てのデータが無効化される(次回GET時に expired 扱いになる)ので、気をつけて下さいね。

$ memcached-cli localhost flush_all
OK

実装したコマンドは全て、対話モードでもバッチモードでも、どちらでも実行できます。

今後

もうあまり困ってはいないのですが、Memcached のその他のコマンドも少しずつ追加していこうかなと思っています。

あと、ランダムにデータ生成するようなコマンドも作ろうかなと思っていたりします。*5

何かあれば GitHub 上で Issue や PR をいただければ、歓迎いたします。

余談

もっと早くこれを作っておけば良かったという感はあります。

なんで Perl なのかというと、memcached-tool が Perl で書かれていて、それを参考にしながら移植したからです。*6

脚注

*1:http://redis.io/

*2:http://memcached.org/

*3:最近探したところ、いくつか見つかりましたが、ほしい感じのはなかったです。

*4:memcached/memcached-tool at master · memcached/memcached · GitHub

*5:テスト用に https://github.com/key-amb/perl5-App-Memcached-Roaster というチープなツールを作りました。

*6:https://github.com/key-amb/perl5-App-Memcached-Tool という memcached-tool のポーティングも作りました。