萌えキャラとは何だったのか

ギークにも絵描きにもなれない者の末路

最近 DBIC を使ってコードを書いてるけどオブジェクトの永続化の功罪について想いを馳せることが多いmix3です

CREATE TABLE user_item (
  id           BIGINT  UNSIGNED NOT NULL AUTO_INCREMENT,
  user_id      INTEGER UNSIGNED NOT NULL DEFAULT 0,
  item_id      INTEGER UNSIGNED NOT NULL DEFAULT 0,
  is_equiped   TINYINT UNSIGNED NOT NULL DEFAULT 0,
  is_protected TINYINT UNSIGNED NOT NULL DEFAULT 0,
  INDEX idx_1 (user_id, item_id, is_protected),
  INDEX idx_2 (user_id, item_id, is_protected, is_equiped),
  INDEX idx_3 (user_id, item_id, is_protected, id),
  INDEX idx_4 (user_id, item_id, is_protected, is_equiped, id),
  PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARACTER SET utf8mb4;

こんなテーブルがあったとして、以下の優先順位で抽出したい

  • is_protected = 1 は保護されているので除外
  • id 順に SELECT
    • ただし is_equiped = 1 は SELECT の対象だが装備してるので優先度を低く

このとき user_id = 1, item_id = 1 のものを 2個 SELECT する場合

SELECT * FROM user_item
WHERE user_id = 1
AND   item_id = 1
AND   is_protected = 0
ORDER BY is_equiped, id ASC LIMIT 2;

多分こんなクエリになると思う 実際にINSERTして試してみる

INSERT into user_item
  (user_id, item_id, is_equiped, is_protected)
VALUES
  (1, 1, 1, 1), -- 装備中 & 保護されてる
  (1, 1, 1, 0), -- 装備中
  (1, 1, 0, 0),
  (1, 1, 0, 0),
  (1, 1, 0, 0),
  (1, 2, 0, 0);

mysql> SELECT * FROM user_item WHERE user_id = 1 AND item_id = 1 AND is_protected = 0 ORDER BY is_equiped, id ASC LIMIT 2;
+----+---------+---------+------------+--------------+
| id | user_id | item_id | is_equiped | is_protected |
+----+---------+---------+------------+--------------+
|  3 |       1 |       1 |          0 |            0 |
|  4 |       1 |       1 |          0 |            0 |
+----+---------+---------+------------+--------------+

期待通り id:1 は除外 id:2 は優先度低いので id:3,4 が帰ってきている

select for update

トランザクション内で select for update するとロックしてくれる

BEGIN;
SELECT * FROM user_item WHERE id = 1 FOR UPDATE;

ターミナルを二つ開いてそれぞれで上記SQLを流すと片方の SELECT が待たされる

ところで select for update に ORDER BY LIMIT が含まれる場合はどうなるのだろうか?

SELECT * FROM user_item FORCE INDEX (idx_N)
WHERE user_id = 1
AND   item_id = 1
AND   is_protected = 0
ORDER BY is_equiped, id ASC LIMIT 2 FOR UPDATE;

したあとに

SELECT * FROM user_item WHERE id = N FOR UPDATE;

を idx_1~4, id = 1 ~ 6 まで試してみた

id=1 id=2 id=3 Id=4 id=5 id=6
idx_1 × × × ×
idx_2 × ×
idx_3 × × × × ×
idx_4 × ×

こんな感じになった

ORDER BY や LIMIT などを含む select for update も index を張っていればロック範囲を最小限に抑えらえるようだ

ただ idx_3 は id=1 までロックされて変な index を張るとロックの範囲も変な感じになるようだ

idx_2 が idx_4 と同じ最小範囲のロックをしているのは id が primary key だからだろうか?

結論

index は大事

洒落になっていない勢いで無能を晒して首が飛びそうになってます 皆さんいかがお過ごしでしょうか mix3 です。

今回は「クエリ飛びすぎなので where in して prefetch しましょう」と言われてwhere inはわかるけどprefetchはなんぞ?となったのでとりあえずサンプル書いてみました。

例えば

erd

  • ユーザがいる
  • 装備がある
  • ユーザは装備を複数持っている
  • ユーザはアバターを複数持っている
  • アバターは複数の装備セットを持っている
  • 装備セットは頭,体,足の装備を持っている

こんな感じでなんかアバターがあって着せ替えが出来る、着せ替えさせやすいようにセットが作れるようになってるみたいな。

で、このとき50人のユーザの現在装備している装備のidを持ってこいとなったとき愚直にやると多分こんな感じ

subtest simple => sub {
    my @sqls = trace_sqls {
        for my $user_id ( 1 .. 50 ) {
            my $user_row = schema->resultset('User')->find($user_id);
            $user_row->current_ua;
            $user_row->current_ua->current_uae_set;
            $user_row->current_ua->current_uae_set->head_uae;
            $user_row->current_ua->current_uae_set->body_uae;
            $user_row->current_ua->current_uae_set->leg_uae;
            $user_row->current_ua->current_uae_set->head_uae->ae;
            $user_row->current_ua->current_uae_set->body_uae->ae;
            $user_row->current_ua->current_uae_set->leg_uae->ae;
        }
    };
    pass sprintf "count: %d", scalar(@sqls);
};

450クエリ飛ぶ

where in を使ってユーザを取ってくるところを1クエリに絞ると

subtest "where in" => sub {
    my @sqls = trace_sqls {
        my @ids = 1 .. 50;
        my $user_rs = schema->resultset('User')->search( { id => { -in => \@ids } } );
        while ( my $row = $user_rs->next ) {
            $row->current_ua;
            $row->current_ua->current_uae_set;
            $row->current_ua->current_uae_set->head_uae;
            $row->current_ua->current_uae_set->body_uae;
            $row->current_ua->current_uae_set->leg_uae;
            $row->current_ua->current_uae_set->head_uae->ae;
            $row->current_ua->current_uae_set->body_uae->ae;
            $row->current_ua->current_uae_set->leg_uae->ae;
        }
    };
    pass sprintf "count: %d", scalar(@sqls);
};

401クエリ飛ぶ where in で50クエリほど減る。

ここでprefetchを使うと

subtest "where in + prefetch" => sub {
    my @sqls = trace_sqls {
        my @ids = 1 .. 50;
        my $user_rs = schema->resultset('User')->search(
            { 'me.id' => { -in => \@ids } },
            { prefetch => {
                    current_ua => {
                        current_uae_set => {
                            head_uae => 'ae',
                            body_uae => 'ae',
                            leg_uae => 'ae',
                        },
                    },
                },
            }
        );
        while ( my $row = $user_rs->next ) {
            $row->current_ua;
            $row->current_ua->current_uae_set;
            $row->current_ua->current_uae_set->head_uae;
            $row->current_ua->current_uae_set->body_uae;
            $row->current_ua->current_uae_set->leg_uae;
            $row->current_ua->current_uae_set->head_uae->ae;
            $row->current_ua->current_uae_set->body_uae->ae;
            $row->current_ua->current_uae_set->leg_uae->ae;
        }
    };
    pass sprintf "count: %d", scalar(@sqls);
};

1クエリになる

dbic-prefetch-sample

prefetchの前にjoinというそのままのものもあるけど、joinはjoinしたテーブルの情報は返ってこないので今回のようにjoinした先のデータも必要だったらprefetchを使うと良いっぽい

してしまったmix3だよ!

そして意識高いのか社畜なのか良くわからないこと呟いたけど

当然オフィスに誰も居なかったので一人で仕事とか寂しくて死ぬので速攻で帰ったよ!とんでもないクズ野郎だね!

こんな悲しみ繰り返さないために

切実に祝日リマインダーが必要だと感じたので以前作ったリマインダアプリ(github)に祝日も自動で通知するみたいなの追加したいなぁとか思った

で、golangで祝日を取得する方法無いかなぁと適当にググったら見つからない(本当に適当にググっただけなのでちゃんと探したらあるかもしれない)ので国民の祝日の名称を取得するモジュール(coderepos)をgolangに移植したんだけど、それだけで一日潰してしまって祝日リマインダー出来ませんでした!

また祝日に出勤してしまうかもしれない! やばい!!!

追記

githubへのリンク貼ってなかったよ!おっちょこちょいだね!

go-holiday

無駄にDriverの仕組みを入れてるので日本の祝日以外の実装も追加しやすいよ!多分ね!

YAPC童貞卒業だよ!mix3です。

YAPC1日目だけ参加してきました。2日目は本番化業務があったのでおとなしく自宅作業してました。

聞いたのは以下の通り。ライブコーディングはイベントホールで勝手にやってる奴だったので、休憩がてら途中から見てましたが非常にgdgdで楽しかった。

Perl meets Real World 〜ハードウェアと恋に落ちるPerlの使い方〜

@mackee_wさんによる発表

perlとハードウェアの話っぽいタイトルだけど、perl全く関係ないしネギ振るハズだったのに振れなくて「後でイベントホールに来てください本物のネギ振りをお見せしますよ」的な感じで終わったのが面白かった。

ソフトウェアに比べてハードウェアは現物が無いといけないので単純にハードルが高いのだが、Raspberry PiやらArduinoやらのおかげでその辺りのハードルが下がってきてる感じが伝わってくる内容だった。

あとはテストが難しいというのをどう解決してくかというのが課題のようだ。(個人的な理解だが、おそらく動かして試すしか無いからというのがあるのだと思う)

完成されたシステムなどない。完成された人間もいない。あるのは成長し続ける未完成なシステムと、それを支える未完成な人間だけだ

@kenjiskywalkerさんによる発表

「リハーサルでは20分で終わったのでゆっくり話します」と言って始めたのに結局20分で終わって、残り20分のうち10分をトークショーで乗り切るという

「さすがけんじさんだ、俺たちに出来ない事を平然とやってのける!そこに痺れる憧れる〜!」

な感じの発表だった。さすがです。

内容自体はなんというか、理想とされるシステムの成長の姿を追体験形式で語るみたいな感じだった。

根底としてイミュータブルな世界が来てて理想とされてるシステムの姿(適切に分割されて継続しやすい状態)を実現しやすくなってるから頑張りましょうということなのかなと思っている。

お待たせしました。Perl で BDD を簡単に実践する最高にクールなフレームワークができました

@tokuhiromさんによる発表

Test::Builder2がポシャったようなので、仕方ないからTest::Kantanを作った!使ってね!

作られては消えていく、泡のように儚いクラスタの運用話

@toritori0318さんによる発表

テレビの配信でクラスタの運用をしていて、辛くなるたびに対応してきたという話。

Webとは違って明確にトラフィックが増えるタイミングが分かるので、クラスタ作りまくってその日のうちに消すみたいなのが当たり前に行われているらしい。知らない世界がそこには広がってるなぁと技術的なところは全く分からないので漫然とそんなふうに思いながら聞いていた。

本番怖い。

Git を使ったツール開発

@motemenさんによる発表

申し訳ないけど内容覚えていない…

git-unifyを作ってくっそ便利だけど、リポジトリぶっ壊れる可能性あるので使う時は気を付けてね」というのだけ覚えてる。

ライブコーディング 2014

@songmuさんによるライブコーディング。

@moznion「進捗、どうですか?」

@songmu「進捗、ダメです」

とかやっててくっそ笑ってた。gdgd。楽しかった。

O2O/IoT/Wearable時代におけるWeb以外のネットワーク技術入門

@lyokatoさんによる発表

ネットワークの話興味あって聞きに行ったけど、発表が物理的に早すぎたのと、内容難しすぎて全く理解出来ずに終わった。

Web以外の知識無さ過ぎてダメだなぁってなった。精進しないといけない。

Mojoliciousを使ったwebアプリケーション開発 実践編

@torii704さんによる発表

ネットワークの話で頭がパーンしたので休憩がてら軽めの内容と思われるものを…、ということで聞いてみた。

Webアプリ開発入門みたいな感じでMojoliciousをベースにざざっと説明するような感じの発表。Containerの説明、永続化する話を後に持ってきてたせいで「この説明を真に受けたら危険では?」みたいな感じてハラハラしたり、MVCのMの説明っぽいところで「ん?」てなったりしたけど概ね「まあそんな感じよね」と頷きながら聞いていた

Java For Perl Mongers

@Yappoさんによる発表

型があるってのは良いこと。冗長になるが同時にそれは変な記述をさせない制限になるので余計なバグを仕込みにくい(例えば型名ミスなどのtypoとか発生しない)

Javaは面白みが無い普通の言語だけど型の良さをちゃんと持ってるので、LLで育ってた人はちゃんとそういうの理解してJavaをdisったほうが良いよ。

という感じでJava賛美っぽい話のまま終わって面白かった。Java好きな人間なので聞いてて終止うんうん頷いてた。

Lightning Talks Day 1

印象に残ったのがまかまかさんには家族離散にならない程度にAcme大全続けていって頂きたいなぁというのと、@gugodさんかな?英語で発表しててすっごい大真面目なビジュアライゼーションの話してたと思うんだけど、みんな「すげーすげー」言いまくってるなか自分全く英語が分からなくてぽかーんってして聞いてた。英語頑張ろう…ほんと…

まとめ

「次もあれば行きたい」と思うぐらいには楽しかった。

ただ部屋の移動時間が一切考慮されていないスケジューリングぽかったのと、居座り組と後は早い者勝ちみたいな感じになってたのはなんか微妙な感じなのと、「もう部屋がぱんぱんで聞けないしイベントホール行って暇潰すか」ということが少なからずあったのがちょっと残念だった。部屋を単純に大きいのにするわけにもいかないと思うので改善するのは難しいと思うけどなんとかしてほしいなと思う。

そして個人的に翻訳聞くレシーバが初日1機遭難してたやつ、その後救助されたのか気になっている(1機4万とか言ってたので紛失とか運営としては正直笑えない話)

衣玖さんが好きすぎて、最近は衣玖さんの人としてチームメンバーからは認識されているようです。(衣玖さん結婚しよ)mix3です。

サンプルアプリ

社内的にgolangを使って行こうか、という流れが出来ていてその流れの中で @acidlemon さん作のWAF、rocketが出てきたので使い方の勉強もかねて早速それのサンプルアプリを作ってみました

リマインダアプリ

うちの社内IRCではお願いすると指定日時にメンションを飛ばしてくれるremember君というbotが生息してるのですが、それのメール版のようなイメージで作りました

指定アドレスにメールして登録、指定日時にメールが飛んでくるだけの簡単なwebアプリです

世の中便利なもので、指定アドレスにメールが来ると指定URLにPOSTしてくれるサービス(つまりSMTPいらない)があったり、1日10000通までメール送信出来るサービス(つまりSMTPいらない)があって、herokuとかと組み合わせることで簡単な物なら無料でメールを使ったサービスが作れてしまうようです

物はこちら go-rocket-sample-app http://secure-sierra-2582.herokuapp.com/

リマインダ登録までの流れ

一応登録されたメールアドレス以外のリマインダ登録は受け付けないようにしたかったので、

  • アドレス登録のアドレスに対して空メールすると、仮登録され本登録のURLが返信される
  • URLを踏むと本登録が完了する
  • リマインド登録のアドレスに対して、件名に日時指定、本文にリマインドの内容を書いて送ると登録される(認識する日時指定はだいたい以下の通り)
    • 12:34
    • 07-01 12:00
    • 2014-07-01 23:59

な感じで動く様になっています アドレス登録後はリマインド登録用のアドレスを保存しておいて使うことを想定しています

ということで

rocketのサンプルアプリでした