読者です 読者をやめる 読者になる 読者になる

Crystal - Ruby風静的型付きコンパイル言語

Ruby Crystal

Ruby で複数人で大きめのプログラムを作ってると、型が欲しいと思うことが時々あるんですが、最近型つきRuby風言語の Crystal というのがあるのを知ってちょっと触ったりしてました。

YAPC::Asia 2015 で、まつもとさんの「絶対に型を書きたくないでござる」を聞いて、「型がある Ruby ってどんな感じなんだろう?」とあらためて Crystal についてもう少し知りたいと思って、YAPC の開催期間中に発表聞かずに調べたりしてました(発表はちゃんと聞きましょう)。

どっかで発表するかもしれないと思ってスライド形式で書いたんですけど、直近で発表するあてがないので、発表しないまま公開します。

個人的には、型の扱いとオーバーロードがかなり良さそうに感じました。名前付き引数の記法は Ruby よりも自然に思えます。

整数演算はイマイチですね。多倍長整数がないのは別にいいんですけど、0x7FFFFFFF+1 がオーバーフローしちゃったり…。

サブコマンドで、Bundler, YARD, RSpec 風のことが標準でできるのはいい感じです。

Crystal は Ruby に似てますが、あくまでも Ruby風というだけで、Ruby とは互換がない異なる言語ということは認識しておきましょう。

MySQLのタイムゾーン

MySQL

YAPC::Asia 2015 のセッションで、MySQL のタイムゾーンの話が出ていましたが、以前タイムゾーン周りで少しはまったことがあったのを思い出したので書いてみます。

MySQLのデフォルトのタイムゾーンは mysqld 起動時のシステム設定です。TZ 環境変数の値か、変数が設定されていなければ /etc/localtime(Ubuntu の場合) です。

# TZ=Japan /usr/sbin/mysqld
mysql> SHOW VARIABLES LIKE '%time_zone%';
+------------------+--------+
| Variable_name    | Value  |
+------------------+--------+
| system_time_zone | JST    |
| time_zone        | SYSTEM |
+------------------+--------+
mysql> system date; SELECT NOW();
2015年  8月 22日 土曜日 21:39:12 JST
+---------------------+
| now()               |
+---------------------+
| 2015-08-22 21:39:12 |
+---------------------+
# TZ=UTC /usr/sbin/mysqld
mysql> SHOW VARIABLES LIKE '%time_zone%';
+------------------+--------+
| Variable_name    | Value  |
+------------------+--------+
| system_time_zone | UTC    |
| time_zone        | SYSTEM |
+------------------+--------+
mysql> system date; SELECT NOW();
2015年  8月 22日 土曜日 21:39:52 JST
+---------------------+
| now()               |
+---------------------+
| 2015-08-22 12:39:52 |
+---------------------+

MySQL のサーバーとクライアントでタイムゾーンが合っていないと、何かまずいことが起きるのか見てみます。

mysql> CREATE TABLE test (ts TIMESTAMP, dt DATETIME);
mysql> INSERT INTO test (ts, dt) VALUES (NOW(), NOW());
mysql> INSERT INTO test (ts, dt) VALUES ('2015-08-22 21:56:39', '2015-08-22 21:56:39');
mysql> SELECT * FROM test;
+---------------------+---------------------+
| ts                  | dt                  |
+---------------------+---------------------+
| 2015-08-22 12:56:09 | 2015-08-22 12:56:09 |
| 2015-08-22 21:56:39 | 2015-08-22 21:56:39 |
+---------------------+---------------------+

NOW() はサーバー側で実行されるので、サーバー側の時刻になります。クライアントが自分のタイムゾーンの時刻を取得して値を設定するとクライアントの時刻になります。

ここで、タイムゾーンの設定が誤っていることに気づいて、mysqld を起動しなおしました。

# TZ=Japan /usr/sbin/mysqld
mysql> select * from test;
+---------------------+---------------------+
| ts                  | dt                  |
+---------------------+---------------------+
| 2015-08-22 21:56:09 | 2015-08-22 12:56:09 |
| 2015-08-23 06:56:39 | 2015-08-22 21:56:39 |
+---------------------+---------------------+

TIMESTAMP カラムに設定した値が変わってしまいました。

TIMESTAMP カラムは内部的には 1970-01-01 00:00:00 UTC からの経過秒数で値を保持していて、その値を現在のタイムゾーンに合わせて日時形式に変換しているためです。 UTC の 12:56:09 と JST の 21:56:09 は TIMESTAMP の内部表現的には同じ値になっています。

DATETIME カラムは指定した値がそのまま保持されているため、タイムゾーンの影響を受けません。

TIMESTAMP カラムに値を登録した後にタイムゾーンを変更するようなことは避けたほうがいいでしょう。 個人的には、時刻を保持するには DATETIME カラムにして、TIMESTAMP は使わないのをおすすめしたいです。

DATETIME カラムの場合でも、NOW() 等の時刻取得関数はタイムゾーンの影響を受けるので、サーバーとクライアントのタイムゾーンは合わせておいた方が無難です。

AWS の RDS のように、システム設定を変更したり、mysqld 起動時の設定をいじることができない場合は、接続後に time_zone 変数を設定することで動きを変更できます。

mysql> SELECT @@system_time_zone, @@global.time_zone, @@time_zone;
+--------------------+--------------------+-------------+
| @@system_time_zone | @@global.time_zone | @@time_zone |
+--------------------+--------------------+-------------+
| UTC                | SYSTEM             | SYSTEM      |
+--------------------+--------------------+-------------+
mysql> SET time_zone = '+09:00';
mysql> SELECT @@system_time_zone, @@global.time_zone, @@time_zone;
+--------------------+--------------------+-------------+
| @@system_time_zone | @@global.time_zone | @@time_zone |
+--------------------+--------------------+-------------+
| UTC                | SYSTEM             | +09:00      |
+--------------------+--------------------+-------------+
mysql> SET global time_zone = '+09:00';
mysql> SELECT @@system_time_zone, @@global.time_zone, @@time_zone;
+--------------------+--------------------+-------------+
| @@system_time_zone | @@global.time_zone | @@time_zone |
+--------------------+--------------------+-------------+
| UTC                | +09:00             | +09:00      |
+--------------------+--------------------+-------------+

system_time_zone は変更できません。time_zone は現在の接続のみで有効です。global.time_zone はすべての接続で有効になります。既に接続中のクライアントには影響しません。global.time_zone の設定は mysqld が終了するまで有効です。

日本の場合は夏時間が無く、UTC との時差は常に +09:00 なので上記のような設定でいいのですが、夏時間があり、UTC との時差が季節によって異なるような地域では固定の値の設定では問題になります。

デフォルト状態では Japan のようなシンボルを time_zone 変数に指定することはできません。

mysql> SET time_zone = 'Japan';
ERROR 1298 (HY000): Unknown or incorrect time zone: 'Japan'

実は MySQL はタイムゾーン用のシステムテーブルを持っています。ただし初期状態では中身は空っぽです。

mysql> USE mysql
mysql> SHOW TABLES LIKE '%time_zone%';
+-------------------------------+
| Tables_in_mysql (%time_zone%) |
+-------------------------------+
| time_zone                     |
| time_zone_leap_second         |
| time_zone_name                |
| time_zone_transition          |
| time_zone_transition_type     |
+-------------------------------+
mysql> SELECT * FROM time_zone;
Empty set (0.00 sec)

mysql_tzinfo_to_sql というコマンドで OS のタイムゾーン情報を、これらのテーブルに格納するような SQL 文に変換することができます。

# mysql_tzinfo_to_sql /usr/share/zoneinfo
TRUNCATE TABLE time_zone;
TRUNCATE TABLE time_zone_name;
TRUNCATE TABLE time_zone_transition;
TRUNCATE TABLE time_zone_transition_type;
INSERT INTO time_zone (Use_leap_seconds) VALUES ('N');
SET @time_zone_id= LAST_INSERT_ID();
INSERT INTO time_zone_name (Name, Time_zone_id) VALUES ('Africa/Abidjan', @time_
zone_id);
INSERT INTO time_zone_transition (Time_zone_id, Transition_time, Transition_type
_id) VALUES
 (@time_zone_id, -2147483648, 0)
,(@time_zone_id, -1830383032, 1)
;
〜後略〜

このクエリを次のようにして mysql データベースで実行するとタイムゾーンが登録されます。

# mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -uroot mysql

これで Japan のようなシンボルで指定することができるようになります。

mysql> set time_zone = 'Japan';
mysql> select @@system_time_zone, @@global.time_zone, @@time_zone;
+--------------------+--------------------+-------------+
| @@system_time_zone | @@global.time_zone | @@time_zone |
+--------------------+--------------------+-------------+
| UTC                | SYSTEM             | Japan       |
+--------------------+--------------------+-------------+

なお、time_zone が SYSTEM の場合はシステムのタイムゾーンが使われるので、夏時間も正しく処理されます。

自分は夏時間が絡むようなシステムは作ったことないのであまり知見がないのですが、時刻は UTC で登録しておいて、アプリ側で必要に応じて変換する方がいいと思っています。なんとなく…。

誰も掴んでない TCP ポートを使うことができない

Linux

たとえば、次の例では 12345 ポートは netstat や lsof でも出てこないし、クライアントから接続することもできません。

# netstat -a | grep 12345
# lsof -i :12345
# telnet localhost 12345
Trying 127.0.0.1...
telnet: Unable to connect to remote host: Connection refused

が、12345 ポートで LISTEN するサーバーを起動すると Address already in use になってしまいます。

# ruby -rsocket -e 'TCPServer.new(12345)'
-e:1:in `initialize': Address already in use - bind(2) for nil port 12345 (Errno::EADDRINUSE)
        from -e:1:in `new'
        from -e:1:in `<main>'

どうやら bind(2) した後 listen(2) しなければ、このようなプロセスを作ることができるようです。

# ruby -rsocket -e 'Socket.new(:INET, :STREAM).bind(Addrinfo.tcp("0.0.0.0", 12345)); sleep'

ちなみに lsof でこのプロセスを見ると、問題の Socket ファイルは can't identify protocol になってました。

# lsof -p 11210
〜(中略)〜
ruby    11210 tommy    7u  sock    0,8      0t0 1105451 can't identify protocol

このような状況になったら、どうやればこのプロセスを探し出すことができるんでしょうねぇ…。

アジャイルジャパン2015 長野サテライト / NSEG 62

NSEG

4/25 に NSEG 第62回勉強会として アジャイルジャパン2015 の長野サテライトを開催しました。

場所は株式会社ケイケンシステムさんの会議室をお借りしました。いつもありがとうございます。

長野サテライトは去年は開催されなかったので、二年ぶりです。参加者は21人だったので、コミュニティが開催する長野でのイベントとしては盛況な方だったと思います。

前半はアジャイルジャパンの本会の基調講演二本の録画ビデオを流し、休憩を挟んで長野独自の講演を行いました。


基調講演の1本目は、Janet Gregory さんで、チームのコミュニケーションの話でした。講演は英語でしたが、平鍋さんが逐次通訳してくれたので英語はさっぱりな私でも理解出来ました。

www.slideshare.net

基調講演2本目は、横塚裕志さんで、どちらかというとアジャイルを知らない経営者に向けたような話でした。参加者は現場の技術者が多かったのでちょっと期待とずれていたかもしれません。


後半1本目は、岩佐和紀さんの「ICONIXプロセス × FileMaker アジャイルプロジェクト実践事例」でした。お客さんとのコミュニケーションやプログラマの教育に動画をうまく活用してるのが印象的でした。契約満了の3週前にほぼ開発完了とのこと。

Q&Aで「いつもこんなホワイトなんですか?」という質問に対して「ぼく炎上プロジェクトって経験したことないのでこれが普通です」という回答でした。すばらしい。

www.slideshare.net

後半2本目は、中島祐樹さんの「大手Sierで見た!ウォーターフォール×下請け構造の闇」でした。ホワイトの話のあとはブラックな話で、絶妙なバランスでしたがこの構成は偶然です。

https://prezi.com/hs_b7hqtnwht/sier/

みんなの心がいい感じにダークサイドに染まりそうになったところで、時間となりました。

はじめは1時間くらい時間があまりそうだったので、こっそりスライドを用意したりしてたのですが、出番がなくてよかったです。


終了後、ビストロクーというお店で懇親会を行いました。美味しゅうございました。

www.deli-koma.com


当日のツイートをまとめました。

togetter.com

また来年もアジャイルジャパンのサテライトができれば良いと思います。

MyNA(日本MySQLユーザ会)会 2015年4月

MySQL

4/22(水) に MyNA会が開催されたので久々に参加しました。 というか、私の東京出張に合わせて開催されたみたいなので強制参加です。

ちょっと前に話題になった「🍣=🍺問題」についてしゃべってきました。

スライド:

www.slideshare.net

他の発表者のスライドも貼っときます。

www.slideshare.net

www.slideshare.net

ツイートまとめ:

togetter.com

そういや、今年は日本MySQLユーザ会設立15周年みたいです。

「理論から学ぶデータベース実践入門 ― リレーショナルモデルによる効率的なSQL」

理論から学ぶデータベース実践入門 ~リレーショナルモデルによる効率的なSQL (WEB+DB PRESS plus)

理論から学ぶデータベース実践入門 ~リレーショナルモデルによる効率的なSQL (WEB+DB PRESS plus)

ひょんなことから著者の奥野さんから頂きました。読み終えたのは3月頭なのに、2ヶ月も経ってからブログを書くという遅筆ぶりです。

この本はもともと2月末に発売予定だったらしいのですが、3/10に販売延期になりました。

『理論から学ぶデータベース実践入門』 発売延期及びテスト販売購入のお客様への書籍交換対応のお詫びとお知らせ:重要なお知らせ|技術評論社

テスト販売という限られた部数とはいえ、一旦販売したものを正誤表対応ではなく刷り直して交換対応というのは、電子書籍ならともかく、紙の書籍でやるとはすばらしいです。神対応と言ってもいいのではないでしょうか。紙だけに。

ちなみに、これは「∀」が「∨」になってしまっているという問題だったのですが、みつけたのは自分です。

前から思っていたのですが、実は自分の誤字脱字検出能力は結構高いような気がします。書籍販売前に誤字脱字等のチェックをして欲しいという人はご相談ください :-)

本書の構成は、前半はほとんどSQLが登場せず、リレーショナルモデルの理論が解説されています。後半ではその理論を元にSQLを解説しています。

また、リレーショナルモデルになくてSQLにあるもの(NULLやトランザクションなど)や、リレーショナルモデルに適合しない現実世界のデータの扱い(履歴、グラフ、ツリー等)についても解説されています。

第1章の中で、自分が気になったキーワード:

  • 「実は、SQLにおいてリレーションに相当するものは、テーブルです!」
  • 「リレーションにNULLを含めることができない」
  • 「実はリレーショナルモデルに更新という概念は存在しません」

自分はRDBを触り始めてから二十数年くらいで、高度情報処理データベース試験も受かってたりするんですけど、実はリレーショナルモデルについてはほとんど知りませんでした。この前まで「リレーションってテーブル間の結合のことなんじゃないの?」と思ってましたし。

「はじめに」の中に次のようにあります:

SQLを改めて勉強し直したいと考えている、またはリレーショナルモデルについてよく知らないといった中級者が主なターゲットです。

自分だ…。

第1章の中に次のようにあります:

リレーショナルモデルを知らなくても、何となく困らずに済みます。そのため「SQLは知っているけれど、リレーショナルモデルを知らない」という状況に陥りがちです。

自分だ…。

ということで、そういう人に本書はおすすめです。

あと、5/13 から長野で本書の読書会が開催されます。長野市近辺の方で興味ある人は参加してみるのもいいと思います。

nseg.doorkeeper.jp

Ruby + jemalloc でメモリ使用量が増える場合

Ruby

Ruby でスレッドを1000個ほど作るとプロセスサイズが 4GB ほどになります。

% ruby -e 'system "ps -o vsz -p #$$"; 1000.times{Thread.new{sleep}}; system "ps -o vsz -p #$$"'
   VSZ
 46392
   VSZ
4151572

jemalloc 使うと 2.5GB くらいになります。jemalloc すばらしい。

% LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so.1 ruby -e 'system "ps -o vsz -p #$$"; 1000.times{Thread.new{sleep}}; system "ps -o vsz -p #$$"'
   VSZ
 55672
   VSZ
2509080

ところが jemalloc を使ったほうが時間がかかります。

% time ruby -e '1000.times{Thread.new{sleep}}'
ruby -e '1000.times{Thread.new{sleep}}'  0.10s user 0.06s system 115% cpu 0.132 total
% time LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so.1 ruby -e '1000.times{Thread.new{sleep}}'
LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so.1 ruby -e   0.23s user 0.81s system 109% cpu 0.941 total

jemalloc を使わない場合は 0.13秒ですが、jemalloc を使うと 0.94秒かかってます。

プロセスサイズではなく OS の使用メモリを見てみます。

% ruby -e 'system "free"; 1000.times{Thread.new{sleep}}; system "free"'
             total       used       free     shared    buffers     cached
Mem:       8056184    7719868     336316      97356     587492    5373632
-/+ buffers/cache:    1758744    6297440
Swap:      4194300       2800    4191500
             total       used       free     shared    buffers     cached
Mem:       8056184    7766988     289196      97356     587492    5373632
-/+ buffers/cache:    1805864    6250320
Swap:      4194300       2800    4191500

jemalloc を使わない場合は、プロセスサイズは 4GB くらいでしたが、実際に使われているメモリは 47MB くらいです (前後 の used の差分)。

% LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so.1 ruby -e 'system "free"; 1000.times{Thread.new{sleep}}; system "free"'
             total       used       free     shared    buffers     cached
Mem:       8056184    7721732     334452      89052     587520    5365352
-/+ buffers/cache:    1768860    6287324
Swap:      4194300       2800    4191500
             total       used       free     shared    buffers     cached
Mem:       8056184    7780264     275920      87260     587520    5363568
-/+ buffers/cache:    1829176    6227008
Swap:      4194300       2800    4191500

jemalloc を使った場合は 60MB くらいで、逆に多く使用してしまっています。

さらに 10個ほど fork してみます。

% ruby -e 'system "free"; 1000.times{Thread.new{sleep}}; 10.times{fork{sleep}}; sleep 1; system "free"'
             total       used       free     shared    buffers     cached
Mem:       8056184    2053828    6002356      68744     372796     613836
-/+ buffers/cache:    1067196    6988988
Swap:      4194300          0    4194300
             total       used       free     shared    buffers     cached
Mem:       8056184    2246508    5809676      68744     372804     613856
-/+ buffers/cache:    1259848    6796336
Swap:      4194300          0    4194300

メモリ使用量の増加分は 192MB 程度です。

% LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so.1 ruby -e 'system "free"; 1000.times{Thread.new{sleep}}; 10.times{fork{sleep}}; sleep 1; system "free"'
             total       used       free     shared    buffers     cached
Mem:       8056184    2084772    5971412      67856     372892     613568
-/+ buffers/cache:    1098312    6957872
Swap:      4194300          0    4194300
             total       used       free     shared    buffers     cached
Mem:       8056184    4041740    4014444      67856     372892     613568
-/+ buffers/cache:    3055280    5000904
Swap:      4194300          0    4194300

jemalloc 使うと 2GB 程メモリを食ってしまっています。

なんでこういうことになるのか理屈は全然わかってませんが、jemalloc 使うことでメモリに悪影響を及ぼすこともあるという例でした。

まあ fork なんて使うのが悪いのかもしれませんけどね。