ISUCON8に参加してほとんど何もできなかった

ISUCON8 予選に参加してきました。

自分個人としては、「ほとんど何もできなかった」という感想でした。

どうせ普段できないことは出来ない

どうせ普段できないことは出来ないと思って ( ISUCON8に参加した | | pyama.fun )

これは、本当にそのとおりでした。

チームメンバーのインフラ構成変更に対する手の速さはすごかったです。

一方、自分はというと

  • ソースコードは流石に読めたし修正もできた
  • ボトルネックの特定方法が雑すぎる
  • 慌てて、(普段はしっかり見るくせに)データベース設計を落ち着いて見るということをしなかった
  • 慌てて、短絡的な手しか打てない
  • 修正の手が遅い。トライアンドエラーが多すぎる

普段通り過ぎて、ため息しかないです。

そしてそれは障害対応のときに(チームメンバーと比べて)自分に感じている「遅い感」「冴えてない感」と同じでした。

なんというか、今まで鍛えていない別の筋肉を使っている感覚。

「普段できるように」鍛えないと駄目ですね。そして、せっかくそれを鍛えられる環境にはいるので「活用していかないと」と思いました。

参加してみて

いつの間にか参加することになって、結果、いろいろ足りないところを実感できました。

P山さんの誘い(というか申し込み)がなければ、参加しなかったし、実感できなかったことです。あと、集中モードのメンバーを間近で見れてよかったです。

とりあえず「普段できるように」なっていこうと思います。

net/http/pprof のheapの値は何を表しているのか

tcprxyというTCPプロキシを作っています。

github.com

8月中旬くらいから「よし、プロダクションレディになるくらいまで開発してやろう」と、時間を見つけては設定ファイルを読めるようにしたり、server_starterと連携できるようにしたり、取得できる情報の充実を図ったりしていました。いろいろ機能を追加してみたのですがそれはまたの機会に。

そして、「あとは、常時起動していてメモリリークが発生しないことを確認すればよいだろう」と、とりあえず見よう見まねで net/http/pprof を使ってブラウザ上からプロファイルを確認してみました。

具体的には main.go を以下のように修正して、tcprxy経由でMySQLに対してmysqlslapを使ってクエリを投げていました。mysqlslap <-> tcprxy <-> MySQL という感じです。

diff --git a/main.go b/main.go
index 56f4e32..5689432 100644
--- a/main.go
+++ b/main.go
@@ -20,8 +20,16 @@

 package main

-import "github.com/k1LoW/tcprxy/cmd"
+import (
+       "net/http"
+       _ "net/http/pprof"
+
+       "github.com/k1LoW/tcprxy/cmd"
+)

 func main() {
+       go func() {
+               http.ListenAndServe("localhost:6060", nil)
+       }()
        cmd.Execute()
 }

ところが、 http://localhost:6060/debug/pprof/ に表示されている値を眺めていると、

あれ、heapの値が増え続ける

goroutineの値はMySQLへの通信がなくなると元の値に戻るのに、heapの値だけが増え続けています。

自分がわからないところでメモリリークが起こっているの?

計測してみる

と思ったのでとりあえず計測してみました。

go version

> go version
go version go1.10.2 darwin/amd64

計測コード

for i in {1..1000} ; do
    echo "Do ${i}"
    mysqlslap \
  --no-defaults \
  --concurrency=50 \
  --iterations=10 \
  --engine=innodb \
  --auto-generate-sql \
  --auto-generate-sql-add-autoincrement \
  --auto-generate-sql-load-type=mixed \
  --auto-generate-sql-write-number=100 \
  --number-of-queries=1000 \
  --host=127.0.0.1 \
  --port=3306 \
  --user=root \
  --password=mypass \
  --ssl-mode=DISABLED >> heap.dump
    sleep 2
    curl -s http://localhost:6060/debug/pprof/ >> heap.dump
    curl -s http://localhost:6060/debug/pprof/heap?debug=1 >> heap.dump
done

tcprxy越しにmysqlslapで連続して通信をします。sleepを挟むことでTCPプロキシにおける接続数を一旦0にして、そのときのheapの情報を都度記録していきます。

実行時間としては一晩くらいかかりました。

計測結果

img

"pprof heap count (http://localhost:6060/debug/pprof/ に表示されているheapの値)" は明らかに増え続けています。

しかし"HeapSys"や"HeapAlloc"などその他の値にはその傾向はありません。

ソースコードを読んでみる

net/http/pprof からソースを追ってみました。

まず、src/runtime/pprof/pprof.go の countHeap()http://localhost:6060/debug/pprof/ に表示されているheapの値 のようです。

// countHeap returns the number of records in the heap profile.
func countHeap() int {
  n, _ := runtime.MemProfile(nil, true)
  return n
}

上記コードのように、runtime.MemProfile() の返り値を表示しているみたいです。

runtime.Memprofile

https://golang.org/pkg/runtime/#MemProfile

runtime.MemProfileのgodocを読むと

MemProfile returns a profile of memory allocated and freed per allocation site.

allocated and freed ということは開放された割り当てメモリも返すようです。

MemProfile returns n, the number of records in the current memory profile. If len(p) >= n, MemProfile copies the profile into p and returns n, true. If len(p) < n, MemProfile does not change p and returns n, false.

countHeap()ではruntime.MemProfileの第1引数の []MemProfileRecordnil なので、常にそのときの割り当てプロファイルの数を返すようです。

countHeap()ではruntime.MemProfileの第2引数の inuseZerotrue なので

If inuseZero is true, the profile includes allocation records where r.AllocBytes > 0 but r.AllocBytes == r.FreeBytes. These are sites where memory was allocated, but it has all been released back to the runtime.

ということで、割り当てプロファイルの数にはランタイムにリリースされた分も含むようです。

mbuckets

runtime.MemProfile のコードを追うと mbuckets の数をカウントしています。

mbuckets*runtime.bucket というコールスタックごとのプロファイリング情報の構造体で mbucktes はメモリ用のプロファイリング情報をもっている形になります。

以下は src/runtime/mprof.go からの抜粋

type bucket struct {
  next    *bucket
  allnext *bucket
  typ     bucketType // memBucket or blockBucket (includes mutexProfile)
  hash    uintptr
  size    uintptr
  nstk    uintptr
}

個人的に面白かったのが、プロファイリング情報群をsliceか配列かで持っているのかな?と思っていたら、そうではなくて next に次の bucket をポインタで保持しているということです。

そういうものなんですね。The representation is a bit sleazy, inherited from C. とコメントに書いてありました。

mbuckets はsrc/runtime/mprof.goの mProf_Malloc() -> stkbucket() で必要に応じて追加されるみたいです。

mbuckets を減らす処理は見つけられませんでした。

なぜ、計測回数を増やすごとにプロファイルの情報の数が増えるのか?

結論から言うとまだわかっていません。

src/runtime/mprof.go の stkbucket() のコードを読むと、bucketを保持するハッシュテーブルのサイズには上限(buckHashSize)があるようです。

そして、必要に応じて(同じプロファイル情報であれば?)再利用もされています。(以下は、sktbucket()のコードを抜粋)

  i := int(h % buckHashSize)
  for b := buckhash[i]; b != nil; b = b.next {
    if b.typ == typ && b.hash == h && b.size == size && eqslice(b.stk(), stk) {
      return b
    }
  }

  if !alloc {
    return nil
  }

  // Create new bucket.
  b := newBucket(typ, len(stk))

ただ、同じ処理(mysqlsrap)を繰り返しているのに、計測回数を増やすごとにプロファイルの情報が増えるというところがしっくりきていないところです(計測回数対しての線形増加なら「毎回新規でプロファイルを記録するんだな」とわかるのですが)。

計測結果を保存したheap.dumpにその記録されたプロファイル情報の詳細が残っているので、次回、そこから調べてみようと思っています。

まとめ

  • 計測してみた結果、「http://localhost:6060/debug/pprof/ に表示されているheapの値が増える = メモリリークが発生している」ということではないことがわかった。
  • http://localhost:6060/debug/pprof/ に表示されているheapの値はメモリ割り当てのタイミングで必要に応じて追加されるプロファイルの情報の数であることがわかった。
    • そしてその中にはすでに開放されているものも含まれているということもわかった。
  • 「なぜ、計測回数を増やすごとにプロファイルの情報の数が増えるのか」はわからなかった。

とりあえず、tcprxyにメモリリークがなさそうであることはわかったので良かったです。

go.uber.org/zapでコンソールとファイルのそれぞれに別々のフォーマットでログを出力する

夏休みの自由課題です。

今作っているサーバにログ出力の機能をつけたいと思っていて、ログライブラリを検討していました。

必要な要件は以下です。

  • ファイルには、構造化ログを出力したい(JSONでもLTSVでもなんでもいい)
  • コンソール(STDOUT)には、人間にある程度見やすいログを出力したい(色がつく必要はない)
    • ファイルログとコンソールログの情報は一緒で良い。見え方を変えたいだけ

複数の io.Writer に同時に出力するというところでは io.MultiWriter が思いつくのですが、 io.MultiWriter では io.Stdout とファイルに同時に出力することはできても、別のフォーマットにはできません。

実は、最初に採用しようと考えていたログライブラリでもいろいろ試行錯誤したのですが、そのライブラリでの解決方法は見つけられませんでした。

で、結局、zap というLoggerで綺麗に解決できたので紹介します。

zap

github.com

Goの世界ではとても有名なLoggerなのですね。高速というのも魅力です。

ちなみにzapの使い方や設定の説明は以下のエントリが最高です。

qiita.com

io.Stdoutとファイルのそれぞれに別々のフォーマットでログを出力する

上のエントリにもあるように普通のzapの使い方だと zap.Config を作成-> Build() して zap.Logger を作るのですが、今回は面倒なことをするので、zap.New() を利用して zap.Logger を作成します。

zap.New()zapcore.Core を引数に必要としますので、まずはzapcore.Core を作成します。

zapcore.Corezapcore.NewCore() を使って作成します。

zapcore.NewCore() は、zapcore.Encoderzapcore.WriteSyncerzapcore.LevelEnabler の3つの引数を必要とします

それぞれの引数の概要は以下のとおりです。

引数 概要
zapcore.Encoder ログフォーマット形式
zapcore.WriteSyncer 出力先
zapcore.LevelEnabler ログレベル

ようは、ログフォーマットと出力先の異なる複数の zapcore.NewCore() を持つ zapcore.Logger を作れればいいのですが、zapのソースコードを読んでいたらありました

というわけで具体的なコードです。

package logger

import (
    "log"
    "os"

    "go.uber.org/zap"
    "go.uber.org/zap/zapcore"
)

// NewLogger returns logger
func NewLogger() *zap.Logger {
    encoderConfig := zapcore.EncoderConfig{
        TimeKey:        "time",
        LevelKey:       "level",
        NameKey:        "name",
        CallerKey:      "caller",
        MessageKey:     "msg",
        StacktraceKey:  "stacktrace",
        EncodeLevel:    zapcore.LowercaseLevelEncoder,
        EncodeTime:     zapcore.ISO8601TimeEncoder,
        EncodeDuration: zapcore.StringDurationEncoder,
        EncodeCaller:   zapcore.ShortCallerEncoder,
    }

    file, _ := os.Create("app.log")

    consoleCore := zapcore.NewCore(
        zapcore.NewConsoleEncoder(encoderConfig), # コンソールで見やすい形式
        zapcore.AddSync(os.Stdout), # io.Writerをzapcore.WriteSyncerに変換
        zapcore.DebugLevel,
    )

    logCore := zapcore.NewCore(
        zapcore.NewJSONEncoder(encoderConfig), # 構造化ログ(JSON)
        zapcore.AddSync(file),
        zapcore.InfoLevel,
    )

    logger := zap.New(zapcore.NewTee(
        consoleCore,
        logCore,
    ))

    return logger
}

zapcore.NewTee() を使って zapcore.Core をまとめているのがポイントです。

というわけで

zapでやりたかったログ出力の形を実現できました。

構造化ログのフォーマットもltsvのエンコーダもあることを確認していますので、良さそうです。

tbls v1 をリリース / SQLiteに対応した etc.

データベースドキュメント生成ツールのtblsですが、ひと通り機能が落ち着いた(と思った)のでメジャーバージョンのv1をリリースしました(現在は v1.2.1)。

github.com

前回 v0.8.2 からの追加機能や修正などを紹介したいと思います。

Amazon Redshiftのサポート(?)

Work with redshift by watarukura · Pull Request #29 · k1LoW/tbls · GitHub

Amazon RedshiftはPostgreSQLをベースにしていると聞いていたのですが、まさか pg_catalog なども同じだとは。。

一部キャストが利かなかったらしく、修正Pull Requestをもらって対応できた ようです

(というのもAmazon Redshift、ちょっと高くて手元では検証できていないのです)

ファイルディスクリプタを使い切ってしまうバグを修正

Fix `too many open files` by k1LoW · Pull Request #33 · k1LoW/tbls · GitHub

これはまさに Big Sky :: golang では for ループの中で defer してはいけない。 というヤツです。

こういうバグをつくるのも自分の中では新鮮でした。

Markdownの整形周りの調整

ありがたいことにいろいろな方に使っていただいているようで、想定できていなかったスキーマへの対応や、Markdownテーブルの整形オプションの追加などをしました。

そういえば最近自分のユースケースでTriggerが爆誕したので、それの取得にも対応しました。

SQLiteのサポート

Support SQLite by k1LoW · Pull Request #49 · k1LoW/tbls · GitHub

PostgreSQLAmazon Redshift)、MySQLに続くサポートデータベースです。

tblsを作っていると、その実現する機能の特性からデータベースに対して「データをためて使う」という通常の使い方ではなく、「データベースからスキーマの情報をいかに取得するか」という別の視点で触ります。

実はPostgreSQLMySQLSQLiteも、いつものクライントで(ほぼ)SQLを使ってスキーマの情報を取得できるというのが面白いです。

information_schema.* などはPostgreSQLMySQLも同じ名前のものを持っています。何か共通して参照している仕様があるのかもしれません。

SQLiteは流石に他と比べると若干貧弱ではありますが(例えば、外部キー制約につけた名前を取得するにはsqlite_masterにそのまま保存してあるCREATE TABLE文をパースしないといけない etc.)、なんとか同じように取得できました。

サンプルを見たら分かるように 他のデータベースと遜色なく利用できると思います。

Virtual Tableにも一部対応しています。

というわけで

簡単に導入できますので、ぜひ使ってみてください。

使い方などはQiitaの記事 などを参考にしてみてください。

TCP Proxyを書いてMySQLの通信を覗いてみる

"TCP Proxyを書いてPostgreSQLの通信を覗いてみる - Copy/Cut/Paste/Hatena" の続編です。

MySQLの通信を覗いてみる

また簡単なクエリだけを対象にします(プリペアードステートメントなどは含みません)。

MySQLもしっかりとしたドキュメントがありクエリのプロトコルの説明もありました。

MySQL :: MySQL Internals Manual :: 14.6.4 COM_QUERY

しかし、とみたまさひろさん の資料のほうが断然わかりやすかったです。

slide.rabbit-shocker.org

というわけで今回も tcprxy に実装を追加していきます。

mysqlコマンドでクエリを実行してみます。

my:root@localhost:3306/testdb=> SELECT COUNT(*) FROM information_schema.tables;
  COUNT(*)
+----------+
       305
(1 rows)

まずは tcprxy -d hex で覗きます。

00000000  30 00 00 00 03 53 45 4c  45 43 54 20 43 4f 55 4e  |0....SELECT COUN|
00000010  54 28 2a 29 20 46 52 4f  4d 20 69 6e 66 6f 72 6d  |T(*) FROM inform|
00000020  61 74 69 6f 6e 5f 73 63  68 65 6d 61 2e 74 61 62  |ation_schema.tab|
00000030  6c 65 73 3b                                       |les;|
00000000  01 00 00 01 01 1e 00 00  02 03 64 65 66 00 00 00  |..........def...|
00000010  08 43 4f 55 4e 54 28 2a  29 00 0c 3f 00 15 00 00  |.COUNT(*)..?....|
00000020  00 08 81 00 00 00 00 05  00 00 03 fe 00 00 22 00  |..............".|
00000030  04 00 00 04 03 33 30 35  05 00 00 05 fe 00 00 22  |.....305......."|
00000040  00                                                |.|

クエリのデータは、1-3バイト目がデータ長、4バイト目がシーケンス番号、5バイト目からデータで、その先頭バイトが 0x03 になっているので、そのメッセージだけ受け取ってクエリの文字列だけ取得します

-d オプションに、今回作成したmysql を指定して 33306 ポートで待ち受けます。

$ tcprxy -l localhost:33306 -r localhost:3306 -d mysql

適当なクエリを実行。

my:root@localhost:33306/testdb=> SELECT COUNT(*) FROM information_schema.tables;
  COUNT(*)
+----------+
       305
(1 rows)

my:root@localhost:3306/testdb=> SELECT COUNT(*) FROM information_schema.columns;
  COUNT(*)
+----------+
      3269
(1 rows)

出力は以下。

$ tcprxy -l localhost:33306 -r localhost:3306 -d mysql
SELECT COUNT(*) FROM information_schema.tables;
SELECT COUNT(*) FROM information_schema.columns;

MySQLでもクエリ取得ができました。

まとめ

今度はプリペアードステートメントなども取得して、組み立ててみたいです。

TCP Proxyを書いてPostgreSQLの通信を覗いてみる

覗いてみました。

ただ見るだけなら tcpdump などを活用すれば良さそうなのですが、せっかくならプロトコルを解析した結果を出力してみたかったので、取得結果をイジれるようにTCP Proxyを書くところからはじめてみました。

TCP Proxyってどうやって書けば?というのは基本的にインターネットの情報を参考にしました。特に以下のツールのコードを参考にしました。

github.com

で、書いたのが tcprxy です。

github.com

$ tcprxy -l localhost:12345 -r localhost:1234

という、よくありそうな感じでTCP Proxyなプロセスを起動するだけのツールです。

準備

まず、localhost:5432PostgreSQLを起動しておきます。 tcprxyPostgreSQLをリモートアドレス -r に指定して起動します。

$ tcprxy -l localhost:55432 -r localhost:5432

tcprxy はデフォルトで双方向の通信を hex.Dump() を使って標準出力に出力します。

あとは psql コマンドなどを使って localhost:55432 に接続して通信結果を覗いてみます。

PostgreSQLの通信を覗いてみる

まずはPostgreSQLへの接続まで

$ psql -U postgres  -h localhost -p 55432 -d testdb
Password for user postgres:
psql (10.2, server 10.4 (Debian 10.4-1.pgdg90+1))
Type "help" for help.

testdb=#

ダンプ結果が以下

00000000  00 00 00 08 04 d2 16 2f                           |......./|
00000000  4e                                                |N|
00000000  00 00 00 52 00 03 00 00  75 73 65 72 00 70 6f 73  |...R....user.pos|
00000010  74 67 72 65 73 00 64 61  74 61 62 61 73 65 00 74  |tgres.database.t|
00000020  65 73 74 64 62 00 61 70  70 6c 69 63 61 74 69 6f  |estdb.applicatio|
00000030  6e 5f 6e 61 6d 65 00 70  73 71 6c 00 63 6c 69 65  |n_name.psql.clie|
00000040  6e 74 5f 65 6e 63 6f 64  69 6e 67 00 55 54 46 38  |nt_encoding.UTF8|
00000050  00 00                                             |..|
00000000  52 00 00 00 0c 00 00 00  05 75 d4 cb 36           |R........u..6|
00000000  00 00 00 08 04 d2 16 2f                           |......./|
00000000  4e                                                |N|
00000000  00 00 00 52 00 03 00 00  75 73 65 72 00 70 6f 73  |...R....user.pos|
00000010  74 67 72 65 73 00 64 61  74 61 62 61 73 65 00 74  |tgres.database.t|
00000020  65 73 74 64 62 00 61 70  70 6c 69 63 61 74 69 6f  |estdb.applicatio|
00000030  6e 5f 6e 61 6d 65 00 70  73 71 6c 00 63 6c 69 65  |n_name.psql.clie|
00000040  6e 74 5f 65 6e 63 6f 64  69 6e 67 00 55 54 46 38  |nt_encoding.UTF8|
00000050  00 00                                             |..|
00000000  52 00 00 00 0c 00 00 00  05 d2 92 d0 67           |R...........g|
00000000  70 00 00 00 28 6d 64 35  63 66 35 39 35 66 36 63  |p...(md5cf595f6c|
00000010  34 39 34 66 64 35 66 32  66 34 37 62 62 31 31 32  |494fd5f2f47bb112|
00000020  35 30 62 35 65 65 31 31  00                       |50b5ee11.|
00000000  52 00 00 00 08 00 00 00  00 53 00 00 00 1a 61 70  |R........S....ap|
00000010  70 6c 69 63 61 74 69 6f  6e 5f 6e 61 6d 65 00 70  |plication_name.p|
00000020  73 71 6c 00 53 00 00 00  19 63 6c 69 65 6e 74 5f  |sql.S....client_|
00000030  65 6e 63 6f 64 69 6e 67  00 55 54 46 38 00 53 00  |encoding.UTF8.S.|
00000040  00 00 17 44 61 74 65 53  74 79 6c 65 00 49 53 4f  |...DateStyle.ISO|
00000050  2c 20 4d 44 59 00 53 00  00 00 19 69 6e 74 65 67  |, MDY.S....integ|
00000060  65 72 5f 64 61 74 65 74  69 6d 65 73 00 6f 6e 00  |er_datetimes.on.|
00000070  53 00 00 00 1b 49 6e 74  65 72 76 61 6c 53 74 79  |S....IntervalSty|
00000080  6c 65 00 70 6f 73 74 67  72 65 73 00 53 00 00 00  |le.postgres.S...|
00000090  14 69 73 5f 73 75 70 65  72 75 73 65 72 00 6f 6e  |.is_superuser.on|
000000a0  00 53 00 00 00 19 73 65  72 76 65 72 5f 65 6e 63  |.S....server_enc|
000000b0  6f 64 69 6e 67 00 55 54  46 38 00 53 00 00 00 31  |oding.UTF8.S...1|
000000c0  73 65 72 76 65 72 5f 76  65 72 73 69 6f 6e 00 31  |server_version.1|
000000d0  30 2e 34 20 28 44 65 62  69 61 6e 20 31 30 2e 34  |0.4 (Debian 10.4|
000000e0  2d 31 2e 70 67 64 67 39  30 2b 31 29 00 53 00 00  |-1.pgdg90+1).S..|
000000f0  00 23 73 65 73 73 69 6f  6e 5f 61 75 74 68 6f 72  |.#session_author|
00000100  69 7a 61 74 69 6f 6e 00  70 6f 73 74 67 72 65 73  |ization.postgres|
00000110  00 53 00 00 00 23 73 74  61 6e 64 61 72 64 5f 63  |.S...#standard_c|
00000120  6f 6e 66 6f 72 6d 69 6e  67 5f 73 74 72 69 6e 67  |onforming_string|
00000130  73 00 6f 6e 00 53 00 00  00 11 54 69 6d 65 5a 6f  |s.on.S....TimeZo|
00000140  6e 65 00 55 54 43 00 4b  00 00 00 0c 00 00 0d a7  |ne.UTC.K........|
00000150  05 14 5d 33 5a 00 00 00  05 49                    |..]3Z....I|

全くわからない。

簡単なクエリを実行してみます。

testdb=# SELECT COUNT(*) FROM information_schema.tables;
 count
-------
   196
(1 row)
00000000  51 00 00 00 34 53 45 4c  45 43 54 20 43 4f 55 4e  |Q...4SELECT COUN|
00000010  54 28 2a 29 20 46 52 4f  4d 20 69 6e 66 6f 72 6d  |T(*) FROM inform|
00000020  61 74 69 6f 6e 5f 73 63  68 65 6d 61 2e 74 61 62  |ation_schema.tab|
00000030  6c 65 73 3b 00                                    |les;.|
00000000  54 00 00 00 1e 00 01 63  6f 75 6e 74 00 00 00 00  |T......count....|
00000010  00 00 00 00 00 00 14 00  08 ff ff ff ff 00 00 44  |...............D|
00000020  00 00 00 0d 00 01 00 00  00 03 31 39 36 43 00 00  |..........196C..|
00000030  00 0d 53 45 4c 45 43 54  20 31 00 5a 00 00 00 05  |..SELECT 1.Z....|
00000040  49                                                |I|

クエリが見えます。これなら解析結果を出力できそうです。

PostgreSQLプロトコルを解析してみる

PostgreSQL通信プロトコルも含め、しっかりとドキュメント化されています(日本語化も)。

第52章 フロントエンド/バックエンドプロトコル

とりあえず今回は、簡易クエリ ( 52.2.2. ) だけを解析して出力できるようにしてみました。

52.7. メッセージの書式

クエリは先頭バイトが 'Q' になるのでそのメッセージだけ取得して、クエリ文字列の部分だけ取得してみます

実際の解析結果は tcprxy に -d pg オプションを渡して確認できます

$ tcprxy -l localhost:55432 -r localhost:5432 -d pg

先ほどと同じようにクエリを実行してみます。

testdb=# SELECT COUNT(*) FROM information_schema.tables;
 count
-------
   196
(1 row)

testdb=# SELECT COUNT(*) FROM information_schema.columns;
 count
-------
  1775
(1 row)
$ tcprxy -l localhost:55432 -r localhost:5432 -d pg
SELECT COUNT(*) FROM information_schema.tables;
SELECT COUNT(*) FROM information_schema.columns;

クエリを取得できました。

まとめ

簡単ですが普段使っているPostgreSQLの通信を覗くことができました。

せっかくTCP Proxyを書いたので他の通信も覗いてみようと思います。

Fukuoka.go#11 に参加してspf13/cobraの紹介をした #fukuokago

先週、Fukuoka.go#11 に参加しました。

fukuokago.connpass.com

どの発表も興味深く聞くことができました。主催の皆さん。発表者の皆さんありがとうございました!!

感想を1つだけあげるとしたら

どの発表も本当に面白かったのですが、個人的には @deeeet さんのAPI Gatewayの開発の話が面白かったです。

チーム内外の社のメンバーが機能開発ができるための技術選択としてのGo、そしてGopherが入りやすいように考えられた設計やコーティング

Middlewareのレイヤーを作ることでシンプルでわかりやすいアーキテクチャを維持

パフォーマンスや統一的に活用できる機能をCore packageとしてSREチームが責任をもって開発していく。それを読み込むことでその恩恵を得つつドメインに沿った機能追加ができるようにするという。プラグイン的な責務分担

Goの話というよりも、開発すたやアーキテクチャの話が実践的でほんとうに唸ってしまいました。同じものを作りたいわけではないのに、参考にできそうなことばかりでした。

LT: spf13/cobraの紹介

あと、Gopher君のぬいぐるみが欲しくて、LTを発表してきました。

作者がいらしているのは当然知っていました*1(笑

spf13/cobraフルスタック過ぎる感じで便利だし、ちょうど @deeeet さんと話すきっかけにもなるしいいかも。と。

もっとGo力をつけていこうと思いました。

*1:自分が初めて作ったGo製CLIツールはtcnksm/gcliを使って進めました