gorogoro.rb を読んだ

gorogoro.rb とは

大江戸Ruby会議08でぺんさんが最後に再生していた頭のおかしい (褒め言葉) Quine。

コードはこちら

https://gist.github.com/tompng/45d93b3386b5986a94b9c3c8beecba69

実装を見ていく

アスキーアートプログラミング構文

まず見慣れた (毎年 Ruby 会議に参加しているうちになぜか見慣れてしまった) アスキーアートプログラミング構文が使われている。

eval((c=%{
  ...
}).split*'')

詳しくは あなたの知らない超絶技巧プログラミングの世界 の第 2 章を参照だけど、%{} の中がプログラムを好きな形に整形したもの。スペースや改行を取り除いた上で、セミコロンで改行してあげると人間が読める程度に復元できる。

Quine

c=(w*26+';eval((c=%{'+c+"}).split*'')").split$/

の辺りが Quine。

eval c= を文字列として再構成して、自身を埋め込んでいる。

メインループ

0.upto(90) {|tm|
  ...
  puts(...)
  sleep(0.05)
}

sleep を入れながら 91 回 puts している。つまりこのプログラム全体は 91 枚のパラパラ漫画。

tm (タイマーかな) を固定すると任意のコマを描画できる。

キャンバス

m = 96
...
cn = (1..m).map { [0] * m }
...
puts(
  (0..47).map {|i|
    m.times.map {|j|
      l, j = [cn[2*i+1][j], cn[2*i][j]]
      %['".:Y,L#])[3*l+j]
    } * ''
  } * $/
)

96 * 96 の 2 次元配列を変数 cn (多分キャンバス) に入れてあり、puts している。

(0..47).map {|i| ... } は最後に $/ つまり "\n"* (Array#join) しているので m.times.map {|j| ... } が空文字で join しているので

各画素は

%['".:Y,L#])[3*l+j]

で、3*l+j を出力してみると

000000000000000000000034100000011100000000000000000000000000000000000000000000000000000000000000
000000000000000000000041000000000000000000000000000000000000000000000000000000000000000000000000
000000000000000000000440000000000000000000000000000000000000000000000000000000000000000000000000
000000000000000000004400000000000000000000000000000000000000000000000000000000000000000000000000
000000000000000000004100000000000000000000000000000000000000000000000000000000000000000000000000
000000000000000000044000000000000000000000000000000000000000000000000000000000000000000000000000
000000000000000000044000000000000000000000000000000000000000000000000000000000000000000000000000
000000000000000033343000000000000000000000000000000000000000000000000000000000000000000000000000
000000000003010000000011330000000000000000000000000000000000000000000000000000000000000000000000
000000000000668888888866014300000000000000000000000000000000000000000000000000000000000000000000
000000001062200008888888660143000000000000000000000000000000000000000000000000000000000000000000
000000000066660008888888806014300000000000000000000000000000000000000000000000000000000000000000
000000400888888600288882008004400000000000000000000000000000000000000000000000000000000000000000
000000008888888800000000008004400000000000000000000000000000000000000000000000000000000000000000
000000302888888200688660688004400000000000000000000000000000000000000000000000000000000000000000
000000100888800008888888882034400000000000000000000000000000000000000000000000000000000000000000
000000010028860008888888820344100000000000000000000000000000000000000000000000000000000000000000
000000001300286602288822003441000000000000000000000000000000000000000000000000000000000000000000
000000000013300002200033444100000000000000000000000000000000000000000000000000000000000000000000
000000000000111444444441100000000000000000000000000000000000000000000000000000000000000000000000
000000000000000000001430000000000000000000000000000000000000000000000000000000000000000000000000
000000000000000000000140000000000000000000000000000000000000000000000000000000000000000000000000
000000000000000000000044000000000000000000000000000000000000000000000000000000000000000000000000
000000000000000000000004400000000000000000000000000000000000000000000000000000000000000000000000
000000000000000000000000440000000000000000000000000000000000000000000000000000000000000000000000
000000000000000000000000044000000000000000000000000000000000000000000000000000000000000000000000
000000000000000000000000001430000003333333000000000000000000000000000000000000000000000000000000
000000000000000000000000000143034111111111443300000000000000000000000000000000000000000000000000
000000000000000000000000000034100666686666001443000000000000000000000000000000000000000000000000
000000000000000000000000000310062222288888860014300000000000000000000000000000000000000000000000
000000000000000000000000003106200000088888888000430000000000000000000000000000000000000000000000
000000000000000000000000004006888860028888880080040000000000000000000000000000000000000000000000
000000000000000000000000000088888888000222200080040000000000000000000000000000000000000000000000
000000000000000000000000000088888882000660000880040000000000000000000000334444444433000000000000
000000000000000000000000004028888220068888868880040000000000000000000344441100000011143000000000
000000000000000000000000001302888000688888888200400000000000000000004441006688888866001430000000
000000000000000000000000000030028600088888882034144330000000000000044400622222288888866013000003
000000000000000000000000000001300222066222003410000114433300000000444002060000088888880600334441
000000000000000000000000000000001033333333110000000000001114443333440068888860028888820060110000
000000000000000000000000000000000000000000000000000000000000000111440088888880000020000680000000
000000000000000000000000000000000000000000000000000000000000000000440088888880006666006880000000
000000000000000000000000000000000000000000000000000000000000000000143028882200688888888800100000
000000000000000000000000000000000000000000000000000000000000000000014302886000888888888000000000
000000000000000000000000000000000000000000000000000000000000000000000130028600088888820010000000
000000000000000000000000000000000000000000000000000000000000000000000001330022000020001000000000
000000000000000000000000000000000000000000000000000000000000000000000000001100000000000000000000
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

のようになっていて、例えば 0 に置換することで各画素を出力している。目を細めてみるとこれだけでも絵が見えますねw

':. を使って 1 画素を更に上下に分割していたり (そのために行の倍のサイズのキャンバスがある)、#: を使い分けることで色の濃さ(質感)を表現していたりする。

カメラ

x = 1.62 * t + 2
y = -1.44 * t - 2
z = 10 - 7 * t - 4.1 * [tm - 6, 0].max / 9

の辺りがカメラ。

x = 0, y = 0, z = 3 とかに固定すると、その場で画像がクルクル回る。

3D 描画

rn が数式を 3D 描画しているところ?で、ピタゴラスの定理的なものが見えるねフンフンナルホドとか言ってみるけどまだ理解していない。

rn = ->(x, y, z, th) {
  xx = n + m * x / z
  yy = n + m * y / z
  rr = 144 / z
  r1 = 2**2i
  r2 = E**th.i
  tr = ->(x, y, z) {
    x, y = ((x + y.i) * r1).rect
    x, z = ((x + z.i) * r2).rect
    [*((x + y.i) / r1).rect, z]
  }
  ([0, xx - rr].max.ceil..[m - 1, xx + rr].min).each {|ix|
    ([0, yy - rr].max.ceil..[m - 1, yy + rr].min).each {|iy|
      vx, vy, vz = tr[ix / n - 1, iy / n - 1, -2]
      cx, cy, cz = tr[-x, -y, z]
      vv = vx * vx + vy * vy + vz * vz * 3
      vc = vx * cx + vy * cy + vz * cz * 3
      h = 0.3
      d = vc * vc - vv * (cx**2 + cy**2 + cz**2 * 3 - 1 - h * h * 3)
      d < 0 && next

      x2 = cx - vx * s = (h / vz).abs + cz / vz
      y2 = cy - vy * s
      x2**2 + y2**2 > 1 && next

      ...
    }
  }
}

各オブジェクト

lambda が一つのオブジェクトになっていて、例えば雷は tn

tn = ->(i,j) {
  a = 1 - j/52
  b = j>40 ? 0 : 1
  j = n - j/2.0
  (j/2 - a*16) < i && i < (j - b*16)
}

これを描画するとこうなる。

                                                ,#######################
                                                #######################
                                               ,######################
                                               ######################
                                              ,#####################
                                              #####################
                                             ,####################
                                             ####################
                                            ,###################
                                            ###################
                                           ,##################
                                           ##################
                                          ,#################
                                          #################
                                         ,################
                                         ################
                                        ,###############
                                        ###############
                                       ,##############
                                       ##############
                                      ,#############,,,,,,,,,,,,,,,,
                                      #############################
                                     ,############################
                                     ############################
                                    ,###########################
                                    ###########################
                                                   ,##########
                                                   ##########
                                                  ,#########
                                                  #########
                                                 ,########
                                                 ########
                                                ,#######
                                                #######
                                               ,######
                                               ######
                                              ,#####
                                              #####
                                             ,####
                                             ####
                                            ,###
                                            ###
                                           ,##
                                           ##
                                          ,#
                                          #
                                         ,

gt が雷門。

gt = ->(x, y) {
  y -= 0.2
  i = n + x * 168
  j = 24 + y * 84

  # 屋根
  next(x * (-y)**0.5 * 16 % 1 < 0.5 ? 1 : 2) if y < -0.4 && y > -0.9 - x**6 && y > -0.4 / x**2
  # 梁
  next(x.abs * 2.6 % 1 > 0.6 || y > -0.35 || x.abs < 0.1 ? 2 : 0) if y >= -0.4 && y < -0.32 - x**4 / 10
  # 提灯の中の「雷門」って文字列
  next(2) if (0 <= j && j < n && 0 < i && (c[j][i] || w) != w)
  # 提灯
  next(1) if 2 * x**4 + y**4 < 0.01
  # 風神雷神
  x = (x.abs - 0.5).abs
  y < -0.4 || y > 0.55 || x > 0.2 ? 0 : x > 0.12 || y > 0.47 || (y - 0.2) % 0.5 < 0.1 ? y > 0.45 && x > 0.12 ? 2 : 1 : 2 * x * x + y * y < 0.01 + y % 0.05 / 5 ? 1 : 0
}

雷門は特に提灯の中の「雷門」文字部分の実装が意味分かんなくて、

next(2) if (0 <= j && j < n && 0 < i && (c[j][i] || w) != w)

なんと c つまりこのプログラム自身の文字列を参照している。

AA になっているので提灯の中に「雷門」が出現するのだよね。プログラムを左寄せにすると提灯の中も左に寄る。

     ::Y#L::##::##''""'''""''""''""'''""''""''""'''""''""''""''"""''""''""''"""::##::##::##Y
    L::##::##::Y##::##::##L::##::##::###::##::##:::##::##::##Y::##::##::L##::##::##Y:L##::##'
   #::##::Y##::##::Y#L::##::##L::##::##:::##::##:::##::##::L##::##::L##::##::##Y::##::##Y::##'
  "::##L::##::##L::##::###::##::Y##::##:::##::##:::##::##:::##::##Y::##::##Y::##::L##::##::L##'
 "::Y#L::##::Y##::##L::##::Y##::##L::##::###::##:::##::###::##::L##::##Y::##::L##::##Y::##::L#Y
"::Y##::##L::##::Y##::Y##::##L::##::Y##::###::##:::##::###::##Y::##:::##::###::##Y::##::L##::##Y
::Y##::Y##::##L::##L::##:::##::Y##::###::###::##:::##:::##::###::###::##Y::##:::##::L##::L##::##
:Y##::Y##::Y##::###::###::###::##L::##L::##:::##:::##:::##:::##:::##::L##::L##::L##::L##::##Y::#
Y##::Y##::Y##::Y##::Y##:::##:::##:::##:::##:::##:::##:::##Y::##Y::##Y::##Y::##Y::##Y::##Y::##Y::
##::Y##::Y##:::##L::##L::###::###::Y##:::##:::##:::##Y::###::###::L##::L##:::##Y::##Y::##Y::L##:
L::Y##::Y##L::##L::###::Y##:::##L::###::###:::##:::###::###:::##:::##Y::###::L##:::##Y::##Y::L##
::###::Y##L::###::Y##:::##L::###:::##L::###:::##:::###::L##:::###::L##:::##Y::###::L##Y::###::L#
:##L::Y##L::###::Y##L::###:::##L::###:::###:::##:::###:::##Y::###:::###::L##:::###::L##Y::L##:::
##L::Y##:::###::Y##L::Y##L::###:::###:::###::Y##:::###:::###:::##Y::L##Y::###:::###::L##Y::L##Y:
L::Y##L::Y##L::Y##L::Y##L::Y##L::Y##L::Y##:::###:::###:::###:::###:::###:::###:::###:::###:::##Y
::Y##L::Y##L::Y##L:::###:::###:::###:::###:::###:::###Y::L###:::###:::###:::###:::###:::L##Y::L#
Y##L:::###L::Y##L::Y###:::###:::Y###:::###:::###::::###:::###:::###Y::L###:::###:::L##Y::L##Y:::
########               ##########         #############        ###########               #######
,,######"""""""""""""""##########"""""""""#############""""""""###########"""""""""""""""######,
   :::::    ,,,,,,,,,,,LLLLLL,,,,,,,,,,,,,LLLLLLLLLLLLL,,,,,,,,,,,,,LLLLL,,,,,,,,,,,,    :::::
   ::::::::::::::::::::::::::      '':::YYYY::::::::::::::::''      ::::::::::::::::::::::::::
   :::::::::::::::::::::::::: ####:::::::::::::::::::::::::::::'    ::::::::::::::::::::::::::
   :::::::::::::::::::::::::: ##":::::::::::::::::::::::::::::::'   ::::::::::::::::::::::::::
   :::::...............:::::: ####YYYYYYYYYY:::::::::::::::::::::   :::::................:::::
   :::::               :::::: ####LL:::::::::::::::::::::::::::::   :::::                :::::
   :::::               :::::: ####YYYYYYYYYYYYYYYYYYYYYY::::::::::  :::::                :::::
   :::::   '':::::''   :::::: ###########LLLLL::::::::::::::::::::  :::::    '::::::''   :::::
   :::::   ':::::::'   :::::: ###LLLL:::::::::::::::::::::::::::::  :::::   '':::::::'   :::::
   :::::  .:::::::::.  :::::: ##LLLLL:::::::::::::::::::::::::::::  :::::  .::::::::::.  :::::
   ::::: ':::::::::::' :::::: #YYYYYYY::::::::::::::::::::::::::::  :::::  ':::::::::::  :::::
   :::::  '::::::::::  :::::: ####LLLLL:::::::::::::::::::::::::::  :::::   ::::::::::'  :::::
   :::::  ..::::::::.  :::::: ###Y::::::::::::::::::::::::::::::::  :::::  ..::::::::..  :::::
   :::::   :::::::::   :::::: ########LLLLL:::::::::::::::::::::::  :::::    ::::::::'   :::::
   :::::   .........   :::::: ########LLLLLLLLLLLLLLLLLLLLLLLLLLLL,,LLLLL,,,,,........   :::::
   :::::               :::::: ##LLLLLLL:::::::::::::::::::::::::::  :::::                :::::
   :::::               :::::: ####LLLLLLLLLLLL:::::::::::::::::::   :::::                :::::
   :::::''''''''''''''':::::: ####LLLLLLLLLLLLLLLLL::::::::::::::   :::::'''''''''''''''':::::
   :::::::::::::::::::::::::: #######:::::::::::::::::::::::::::.   ::::::::::::::::::::::::::
   :::::::::::::::::::::::::: ##########LLL::::::::::::::::::::.    ::::::::::::::::::::::::::
   :::::::::::::::::::::::::: ,,,, .::::::::::::::::::::::::..      ::::::::::::::::::::::::::
   :::::               ::::::           .................           :::::                :::::
   :::::               ::::::                                       :::::                :::::
   :::::               ::::::                                       :::::                :::::
   :::::               ::::::                                       :::::                :::::
   :::::               ::::::                                       :::::                :::::
   #####'''''''''''''''######                                       #####''''''''''''''''#####
   #####:::::::::::::::######                                       #####::::::::::::::::#####
   #####:::::::::::::::######                                       #####::::::::::::::::#####

他のオブジェクトも全部数式で表現されている辺りも十二分に凄いんだけど、その凄さが霞んでしまうぐらいビックリした。

読み終わった感想

いやー、意味分かんなかった。

頑張って読んだらそれなりに理解はできるんだけど、こんなオシャレにカメラを動かせるセンスはまったく無いなー。

今年観た映画2019

劇場で観た奴。

ちょうど 24 回行っていて、月 2 回ぐらい観てるんだなってのが分かる。

マレフィセントもアナ雪もターミネータースターウォーズも観なかったので、後半は失速したなー。シティーハンタールパン三世も行けてない……。

全部 TOHO シネマズ 二条で観ている。徒歩圏内だし、最悪タクれば 5 分後に着けるのがめちゃくちゃ便利。

HELLO WORLD は観た直後に外に出たら聖地 (伏見稲荷から二条駅まで戻ってきて先生に捕まるところ) が目の前にあって、ずっと世界に没入してる感じで体験がめちゃくちゃ良かった。

今年買ったもの2019

買った順。

  • バタフライボード2 A4 サイズ
    • かなり活躍した。持ち歩くホワイトボードはあると良い
    • 裏紙が無限にあるなら無くても良いなとは思う
    • nu board ホワイトボードマーカー 6mm 3色セット
    • 付属のホワイトボードマーカーだけだと細さが足りないので、細い色違いを買い足した
  • ニトリ セミダブルマットレス Nスリープ プレミアムP3-CR
    • 前のは無印で適当に買った奴だと思う (記憶無し)
    • 10 年ぐらい使っていたのでヘタっていて、買い換えた瞬間から寝ていて腰が痛くならなくなって最高
    • 惰眠をむさぼれるようになってしまった
  • LGエレクトロニクス 曲面ディスプレイ (34WK95C-W)
    • Nota に遊びに行ったところ、曲面ディスプレイを見かけて欲しくなったので購入
    • Retina ディスプレイではないので MacBook Pro と一緒に使ってるとちょっと違和感はある
    • スイーベルが無いので、他人と一緒にモニタを覗き込むときに不便
    • PC 1 台しか繋がないと実力を発揮できない感じがしている
  • カードケータイ KY-01L
    • 洗濯して壊したので 2 個目…… :sob:
  • AKRacing premium レイブン
    • 何かの広告?で「180度までリクライニングできる!」って見て、寝られるじゃんって思って購入
    • 普段使いのワーキングチェアとして、想像の 7000 倍ぐらい快適に活用している
      • 買ったけどイマイチだったね、となると想像していた
    • 社で椅子を何にしようか迷っている人が居ればオススメしたい
    • フットレストも買わないと 180° リクライニングの力は活かせないことが分かっている
  • Mistel MD650L BAROCCO
    • 買った 1 週間後ぐらいに UHK (去年の12月に購入) が届いたのでほぼ使ってない
    • 普段の指使いそのままで移行できるのがメチャクチャ良くて、ただ分割されただけになっている
  • Logicool MX ERGO
  • Ultimate Hacking Keyboard
    • Facebook 眺めていたら広告が流れてきたので購入
    • 届くまで 8 ヶ月ぐらい?今なら買ってすぐ届くっぽい
    • 出張時に持ち歩く程度には大好きになってる
    • そのうちどんな設定してるか記事書かないとだ。(バックアップ代わり
  • Titan Security Key
    • 発売されたってニュースを見て買った
    • YubiKey 5C を既に使っていたので切り替えるの面倒で放置してる……
    • タブレットが USB micro-B なので YubiKey 繋げなくて不便じゃんってよく思ってるので作業したいが、Type-C 対応のタブレットに切り替える方が早そう
  • Fitbit Versa 2
    • なんか発売されたってニュースを見て買った
    • 1 週間電池がもつのは最高 of 最高
    • ウェアラブルバイス着けるの初めてで、今まで計測していなかった心拍数グラフを見て「なるほどー」って言ってる
      • 二日酔いが収まった時間が如実に分かるのがすごく便利。酷いときは翌日の夕方まで酔い続けているらしい
  • AQUOS sense3
    • KY-01L は失くした
    • AQUOS zero2 にしたかったんだけどまだ売ってないので、軽さが同じぐらいのものに
    • KY-01L と違ってバッテリーが持つので、テザリング専用機として考えても、より便利になってしまった (知ってた……)
  • WALKMAN NW-A100TPS (40周年記念モデル)
    • なんかのニュースで見て買った。まだ届いていない
    • 買ってからよく見たけど、容量全然足りなくない??
    • 持ってる全曲持ち歩くスタイルとは合わなそうで、さて困ったぞとなっている
  • Garmin vivosmart 4
    • id:hitode909買っていて 羨ましかったので購入
    • Body Battery とストレスが測れるのが最高に良くて、チームでバッテリー残が高い方が仕事を受け持つスタイルを取っていきたい

こうして見返すと衝動買いが多いことが分かる。

机周りが一通り揃った (モニタ、椅子、キーボード、マウス) ので、来年は IYH することは減るはず、きっと、多分。実際 HHKB は買わずに耐えているわけだし。

Percona Toolkit を読む

このエントリは はてなエンジニア Advent Calendar 2019 の 18 日目のエントリです。

qiita.com

前日は id:ikesyo さんによる 2019年のSwiftモック事情 でした。

ikesyo.hatenablog.com

SQL を分析したい

今日の話。DB 負荷を継続的に計測していきたいのです。

そんなときに Percona Toolkit って良いライブラリがあるので、これを使っていきましょう。

SQL を分析するときは以下のようなことを考えます。

  1. 本番環境でログを採取
  2. normalize
  3. グルーピング
  4. EXPLAIN
  5. マズいクエリを見つけたらアラート発報

それぞれ見ていきましょう。

ログの採取

色々眺めたいので一旦 tcpdump を使います。

流れるクエリだけじゃなくて response も全部採取しているので、秒で GB 単位のデータが溜まっていくことに注意してください。

# pcap データをファイルに保存する
$ sudo /usr/sbin/tcpdump -i any port 3306 -G 60 -w /tmp/tcpdump_%Y%m%d_%H%M%S.pcap
# 再生する
$ /usr/sbin/tcpdump -r /tmp/tcpdump_20191218_000000.pcap -n -nn -tttt -x -q

この取得した pcap から、流れている SQL を取り出したいんですが、Percona Toolkit に TcpdumpParserMySQLProtocolParser っていうイイやつがあります。

名前を見るとイメージ沸くかな。これらを繋ぐといい感じに流れたクエリを取り出すことができます。

use strict;
use warnings;
use utf8;
use English qw(OS_ERROR);

use lib '/path/to/percona-toolkit/lib';

use Data::Dumper;
$Data::Dumper::Sortkeys  = 1;

use TcpdumpParser;
use ProtocolParser;
use MySQLProtocolParser;

my $parser   = new TcpdumpParser();
my $protocol = new MySQLProtocolParser();
my $queries = [];

my $file = 'tcpdump_20191218_000000.txt';

sub _read {
    my ( $fh ) = @_;
    return <$fh>;
}

eval {
    open my $fh, "<", $file or die "Cannot open $file: $OS_ERROR";
    my $parser_args = {
        next_event => sub { return _read($fh); },
        tell       => sub { return tell($fh);  },
        fh         => $fh,
    };
    while ( my $p = $parser->parse_event(%$parser_args) ) {
        my $e = $protocol->parse_event(%$parser_args, event => $p);
        push @$queries, $e if $e;
    }
    close $fh;
};

print Dumper $queries;

こんな感じ。

$VAR1 = [
   {
     No_good_index_used => 'No',
     No_index_used => 'No',
     Query_time => '0.000126',
     Rows_affected => 0,
     Thread_id => 4294967299,
     Warning_count => 0,
     arg => 'SELECT  `users`.* FROM `users` WHERE `users`.`id` = 1',
     bytes => 261,
     cmd => 'Query',
     db => undef,
     host => '127.0.0.1',
     ip => '127.0.0.1',
     port => '58430',
     pos_in_log => 130658,
     ts => '191218 23:46:08.915939',
     user => undef
   },
  ...

ところでレスポンスに NO_GOOD_INDEX_USED とか NO_INDEX_USED とかってフラグあったんですね。10 年以上ずっと MySQL 使ってきたけど知らなかった……。

normalize

SQL を取り出すと、

SELECT  `users`.* FROM `users` WHERE `users`.`id` = 1

のような SQL 文字列が取得できたけど、id = 1 の部分は同一視して集計したいですよね。

これまた Percona Toolkit に QueryRewriter っていうイイやつがあります。

この fingerprint メソッドが、よくできた normalize 処理なので、いただきます。

use QueryParser;
use QueryRewriter;

my $queries = ...;

my $qp = new QueryParser();
my $qr = new QueryRewriter(QueryParser => $qp);
print Dumper [ map { $qr->fingerprint($_->{arg}) } @$queries ];

これを通すと

$VAR1 = [
  'select `users`.* from `users` where `users`.`id` = ?',
  'select `users`.* from `users` where `users`.`id` in(?+)',
  ...

のように normalize されたクエリが取得できます。

自分でこの正規表現書けるかというとまったく自信無いので、枯れてるライブラリは最高ですね。

グルーピング

normalize して、クエリごとに集計可能になったので、グルーピングして集計処理をします。

これまた Percona Toolkit に EventAggregator っていうイイやつがあります。

あ、event ってのは、最初に Parser から取り出していた、1 クエリを表現する HashRef のことですね。

使い方はこんな感じ?

use EventAggregator;

my $ea = new EventAggregator(
   groupby    => 'fingerprint',
   worst      => 'Query_time',
   attributes => {
      Query_time => [qw(Query_time)],
   },
);

for my $event (@$queries) {
    # fingerprint を埋めて
    $event->{fingerprint} = $qr->fingerprint($event->{arg});
    # 集計処理に渡す
    $ea->aggregate($event);
}

# 集計結果を取得
print Dumper $ea->results;

ログ貼ると長いので省略するけど

{
  classes => {
    fingerprint => { 集計結果 },
    fingerprint => { 集計結果 },
    ...
  },
  globals => { ... },
  samples => {
    fingerprint => { event },
    fingerprint => { event },
    ...
  },
}

のような hash が返ってきます。

classes は

{
  'select `users`.* from `users` where `users`.`id` = ?' => {
    Query_time => {
      all => {
        100 => 2,
        102 => 2,
        98 => 1
      },
      cnt => 5,
      max => '0.000143',
      min => '0.000116',
      sum => '0.000657'
    }
  },
  'select `users`.* from `users` where `users`.`id` in(?+)' => {
    Query_time => {
      all => {
        111 => 1,
        94 => 1,
        96 => 1
      },
      cnt => 3,
      max => '0.000221',
      min => '0.000096',
      sum => '0.000424'
    }
  },
  ...

のように、Query_time を集計したもの。cnt, sum だけじゃなくて分布も眺めることができます。

samples は worst で渡した集計軸で、一番遅かったクエリの event です。

集計軸を自分で設定できるので、Query_time 以外でも色んな方向から眺めて分析できそうですね。

EXPLAIN

normalize した結果はおそらく 数百〜2000 ぐらいに収まると思う。これを片っ端から EXPLAIN して、悪い SQL じゃないかを確かめたい。

これまた Percona Toolkit に QueryReportFormatterJSONReportFormatter っていうイイやつがあります。

JSONReportFormatterQueryReportFormatter の子クラスで、出力が JSON になるよう override したものですね。

先ほど作った EventAggregator のオブジェクトと、EXPLAIN を投げる先の DB Handler を渡すと、片っ端から投げまくってくれます。

my $config = {
    dsn => 'dbi:mysql:dbname=DATABASE_NAME;host=127.0.0.1',
    user => 'root',
    pass => 'pass',
};
my $dbh = DBI->connect(map { $config->{$_} } qw(dsn user pass));

my $qrf = QueryReportFormatter->new(
    dbh => $dbh,
    ...
);
$qrf->print_reports(
    ea => $ea,
    ...
);

コードを見ると分かるんですが、要は文字列結合で EXPLAIN $query としているだけです。サブクエリがある場合は安易に EXPLAIN すると死ぬ可能性があるのでスキップされます。なるほど賢い!

マズいクエリを見つけたらアラート発報

JSON が取れるので、もちろん Mackerel に投げて、アラートやグラフを作っていきたい、ですよねぇ。

ここまでで、クエリごとの実行回数や実行時間、一番遅いクエリ等をそれぞれ取得し、更に全クエリについて EXPLAIN 結果も取得することができました。実はこれが pt-query-digest でやってることです。

なんで自前でやってきたかと言うと、一番欲しかった全自動 EXPLAIN 結果が --output json だとなーんか動かなくてですね、この記事はデバッグしている途中ってヤツです。

ドキュメント には

json output was introduced in 2.2.1 and is still in development, so the data structure may change in future versions.

とあります。なので JSON 出力に含める実装がまだ無い気がしています。(たぶん)

著名な便利ツールも中身は普通の Perl なので、こうして読み解いて&コントリビュートしていきたいね、というお話でした。

明日は id:Pasta-K さんです。

書類選考時に見ているポイント

2019-04-01 に「チーフエンジニア」という肩書きを手に入れてしまった。

はてなのエンジニア組織にはチーフエンジニアという役割のエンジニアがいて、評価や採用、その他大小諸々の施策を通じて、技術部門全体の生産性と幸福度を向上させるのがその仕事です。

はてなのエンジニアのバリューズ - Hatena Developer Blog

前職でも新卒採用、中途採用のお手伝いはしていたのだけれど、今は主業務の一つとして担当しているので、僕がどこを見ているのか、というのを書きとめておこう。

履歴書

チラ見しています。

「通勤片道1時間ぐらいかかりそうだけど大丈夫かなぁ?」とか「趣味がルービックキューブじゃん! はてなの speedcubing 部と戦わせたろ」とかを見ています。

職務経歴書

まぁまぁ見ています。

プロジェクトで使った技術、特にアーキテクチャについてを一番見ていると思います。次にプロジェクトの人数や、その中でどんな役割を担ったのか。

具体的に何故この技術を選択したのかとか、プロジェクトで困って解決したこと、解決できなかったことが書いてあるとすごくポイント高くなります。

技術スタックの近さも見ますが、そもそも社内で polyglot な選択をしているので (必要に応じて言語を使い分けてきたら自然とこうなった) やってきた言語そのものよりも、時代の変化とともに生きてきたっぽさを見ています。

GitHub アカウント

はてなに応募される方は持ってる確率高いですね。めちゃくちゃ見ています。

リポジトリ

普段リポジトリを見るときと同じ感じだと思う。

どんなプロダクトを作っているのかを知っておくと会話が弾むので、直近でコミットしてそうな数リポジトリ見てます。

  • star 数とか commit 数とか
    • 力の入れ具合をチラ見
  • README を見る
    • そもそも何をやるものなのかが書いてあるか
    • デモがあるとメチャクチャ体感良い
  • clone してきて tig 起動する
    • ブランチや PR を使ってるかとかを見たい
    • コミットをざっと 30-50 個ぐらい眺めて、コミットの粒度とかコミットメッセージとか
  • CI 用の設定ファイル見る
    • テストあるか、lint あるか、使ってる言語バージョンが最新か
    • 今だと GitHub Actions に移行しているとメチャクチャポイント高いですね
  • 使用ライブラリ一覧眺める
    • 面白いものあるかとか、一般的かとか、プロダクトの規模感掴むとか
  • ディレクトリ構成見る
    • フレームワークのデフォルトに沿っているか
    • 逆に離れていると、敢えてそうしているはずなので面接での会話のネタになる
  • DB 見る
    • 論理的にテーブルが分かれているかとか、正規化できているかとか
  • routes 見る
    • 一般的な API 設計方針を認識してるかとか
  • 静的解析かける
    • 神クラス作ってないか、定番のダメコードを避けられているか

サーバサイドっぽい話題が多いね。僕のバックグラウンドによるものです。

競技プログラミング

  • 提出してくる人すごく増えてきたね
  • 例えば AtCoder 緑あったら「おー、しっかりやってる!会話のネタになりそう」って思って見ています

SNS アカウント

  • お伝えいただけた場合、それなりに見ています
  • tweet 数、following 数、follower 数をチラ見
    • この辺は「インターネット慣れしてるか」という感覚
    • 1000 tweet ぐらいしていて欲しい
  • 発言内容が表垢っぽくないか
    • ペルソナ 1 つに集約されている人だと会話しやすい
      • 使い分けることを否定する意図ではないです
    • インターネットミームが頻繁に登場すると身内っぽい
      • 身内で思い出したけど、「Followers you know」見て「へー」って思ったりもする
  • コード (技術) の話が出てくるか
    • 頻繁に出てくると、生活の中にコーディングが入り込んでいるのでメチャクチャ良いですね
  • 最近興味を持っているものを知るのに使ってる感じ。アカウントがあれば会話が盛り上がりやすいし、無ければ他からひねり出します

その他アウトプット

はてなアカウント

  • エンジニア採用ではあまり見ていません
  • コンテンツプラットフォーム (はてなアカウントで使える toC 向けサービス群) 以外も 事業の柱としている ため
  • はてなが好き」は 社のバリューズの一つ ですが、「インターネットが大好き」も同じくバリューズの一つなので、別にどこでも良いと思っています
  • もちろんガッツリ使っていてくれると嬉しい
  • はてなじゃないとダメな理由、があるとカルチャーマッチしやすそうだなって思います

どれぐらいの時間を掛けているのか

一人につき 30 分〜1 時間半ぐらいですねー。だいたい 30 分だと準備不足で会話しんどい。

感想

いやー、書いてみて感じたけど、自分じゃまったく出来てないことを要求してますね。でも転職考えているならポートフォリオ整備するのを頑張りそうとも思う。

数ヶ月おきに一念発起して "Write Code Every Day" とか毎週ブログを書くとかをしばらく続けると、こういう筋力が戻ってくると思います。やっていこー

どういう時に自分の状態が悪いかと良くないときの対処方法

blog.hokkai7go.jp

dekolife.hatenablog.com

なるほどね。ヨサソウ

状態の悪さがわかる指標

  • 起きてるにも関わらず出社する気が起きなくて午後までボーッとしてる
  • なんかおなか空いてる気がする
  • 分かってるのに分かりたくない駄々っ子状態になる
  • 他人への要求水準が高くなる

改善案

  • 楽しいタスクをやる
    • テスト前に掃除する的なヤツ
  • 寝る
  • マンガ読む
  • ゲームする
  • 食べる

しんどいときに何やっても良くはならないので、今この瞬間の負荷を減らさないといけないと思う。なので本来は「タスクを減らすよう関係者と握る」「後で良いものを本当に後で良いとラベリングして視界/脳内から追い出す」が良い。(とは思っているものの、実践できないので、迷惑を掛けることでアラートを上げてマネージャに何とかして貰おう)

寝るのは強制的にタスクを追い出せるのでオススメです。マンガ・ゲームは、それに集中できるぐらい楽しいので追い出せるんだと思う。

楽しいタスクをやる、は「優先度の順位がおかしくなっている」「おかしくなる程度に今危ない」と分かって貰える (変な成果物がチームの手元に残る) のでアラート効果が高いんじゃないだろうか。しかもみんなの日々のツラみは何か解消されている。最高じゃん(ぉ

これらの改善案を、会話する前にやると迷惑がかかるけど、何もせずにズルズルしてると 1 週間ぐらい戻ってこれないので、一日二日で迷惑が収まるなら遊び呆ける日を作っても良いのでは!?(自己中心的な発想)

食べるのはなんか体を痛めつけてる感じがして一瞬だけ満足感ある。そして太る。


書いた後読み返してみての感想追記。