株式会社Kyashを退職しました
3年間お世話になった株式会社Kyashを2023/02/28付けで退職することになりました
一緒に働いていた皆さん、大変お世話になりました
ありがとうございました
この3年間たくさんの経験をさせてもらったので、やってきたことをまとめようかと思います
Kyashでやってきたこと
入社初期時代
AML(Anti Money Laundering)周りのシステムの設計/開発
詳細は書けませんが、AML用の検知をするためのシステム設計や開発をやらせていただきました
AMLとは悪い人が資金洗浄できないようにするための対策のことです
Anti Fraudチームの方と協力して怪しい動きをしているユーザを見つけ出せるようにしました
負荷対策
入社間もなくKyashカードのリリースだったのですが、高負荷になりアプリが落ちてしまう状態になってしまいました
このようなことが起きないように、さまざまな負荷対策を実施しました
僕が実施した負荷対策の中で大きいものを書くと、ユーザのSession管理をPostgreSQLからRedisへの移行を行いました
高負荷状態のときのボトルネックがPostgreSQLになっていたので、Readの件数を減らしかった背景があります
またSessionへのアクセス頻度はかなり高く保持すべき情報も難しいrelationが必要なわけでもないので、RDBではなくIn-MemoryなKVSであるRedisを選択しました
さらに負荷試験環境の構築や、定期的に負荷試験を実施するための体制作りなども行いました
こちらについては過去に記事を書いているので、興味がある方はそちらをご覧ください
Funds Team時代
社内での体制変更があり、Micro Serviceごとにドメイン領域に特化したチームを立ち上げることになりました
自分はお金の入出金周り(Funds-in/Funds-out)と残高周り(Account)を担当するFundsチームの立ち上げから参画することになりました
Kyashには沢山の入出金手段があるので、この周辺のシステムを担当していました
銀行口座入金
各銀行とシステム接続をして、銀行口座から直接Kyashに入金できるようにするシステムの設計/開発を行いました
Fundsチームとしての最初の仕事である銀行口座入金はレガシーなシステムとの接続ということもあり、非機能要件に苦労したり電文の形式が特殊なことが多くParserを自前で実装しなければいけない苦労や暗号化周りでの苦労など、FinTechならではの沢山の苦労と経験をさせてもらえました
個人的にはこの辺の泥臭い作業が大好きだったので楽しく仕事が出来たのと、あまり他では経験できないような事が多くとても勉強になりました
ATM出金
セブン銀行ATMとシステム接続して、Kyashの残高を現金としてATMから出金できる機能を作りました
接続試験のために丸の内のセブン銀行本社まで行ったのはいい思い出になりました
リリース時もセブン銀行本社に夜中に出向いたのを覚えています
この機能は個人的にすごく便利だと思っている機能だったので、リリースできた時の喜びもひとしおでした
BNPL
Kyashにある後払いサービスであるイマすぐ入金の設計/開発を行いました
このタイミングで、社内のサービスにgRPCを導入しようという話になり、Fundsチーム主導で初のgRPCを使ったMicro Serviceが爆誕したりしました
イマすぐ入金 - コンビニで後払いできるアプリ/Visaカード - Kyash
Bravo Team時代
会社の組織変更のタイミングがあり、人数が減った関係でドメイン特化型のチームからAlphaとBravoの2チーム体制になりました
このタイミングで僕はBravo側に配属されました
Bravoは基本的にはPayment(決済)システム周りをメインに見るチームでした
Lite&Virtual Cardの有効期限対応
Lite&Virtual Cardの有効期限が切れるということで、カードの再発行やPaymentシステム周りやQUICPay+の連携周り、カード発行システム周りなどでカードの有効期限が切れた時の対応をしました
Visaとのテスト環境の一新
もともとあったVisaとの接続テスト用環境が社内のWindows PCに存在していたので、EC2への移行と関連するMicro Serviceの再構築を行いました
これでVisaとのテストがしやすくなりました
Growth Technology Team時代
開発生産性の向上のためのチームを立ち上げました
詳しくはこちらで記事にしてますので、興味があればご覧ください
Fourkeys基盤の構築
開発生産性の向上にあたり、そもそもの現状を把握できなければ改善する事は出来ないので、現状を可視化するための基盤を作成しました
こちらは登壇資料がありますので、興味があるかたはぜひご覧ください
Staging環境の整備
Kyashではインフラ含めて本番に近い擬似本番環境というものが存在していませんでした
また、QAチームで利用している環境と開発者が普段開発している環境が同じであったりして、安全で健全な状態とは言い難い環境でした
この問題を解決するために擬似本番環境の構築を実施していました
この対応は最後まで成し遂げたかったのですが、有休消化前に終えることが出来ず、チームメンバーに託す形となりました
完成した暁には、Kyashのmeetupやブログ等で記事が公開されると思っているので楽しみに待っています
その他
ここには書き切れない細かいことや社外秘にあたるので公表できないことなど、かなり多くの経験をさせていただきました
Profilerの導入もさせてもらいましたし、チームでLayered Architectureの導入やDBの自動マイグレーションの仕組みの構築などもさせていただきました
なんで退職するの?
自分はあまり英語が得意ではないのがずっとコンプレックスでした
エンジニアとしてのキャリアを考えた時に、英語を使って仕事が出来たほうが選択肢の幅が広がるのでいつか勉強せねばと思っていました
もちろんOSSにissueをあげたりDocumentを読む程度の英語力はありますが、時間もかかるし正しく伝えられているのかの自信は今でもないです
そしていっそのこと、英語が公用語ないしは公用語にしようとしてる日本企業に転職しようと思い、このたび転職することにしました
つぎは何をするの?
今はまだ有給消化中なので英語の勉強をしたり、趣味でCPUの勉強をしています
次の会社は公用語を英語にしようとしていて、拙い英語でも受け入れてくれる企業でした
試用期間が終わり、モチベがあれば入社エントリを書くかもしれません
さいごに
たくさんの経験をしたからか長いようで短い3年間でした
Kyashは意外と代替えのない便利なサービスだと思ってます
今後は1ユーザとして応援させていただければと思います
3年間ありがとうございました!
なぜGolangが一部のBinary Toolを自前で実装しているのか
これはなに?
これはKyash Advent Calendar 2022 21日目の記事です
KyashでBackend Engineerをしている @uncle__ko です
お金の入出金を司るチームや決済領域を司るチームなどを経験したあと、現在はTechチームの生産性向上に向けた取り組みを行うチームのリードを行っています
興味がある方はぜひ、下記の記事を御覧ください
Golangは自前でobjdumpやnm、addr2lineなどのToolを内包している
さっそく本題に入りますが、Golangには objdump
や nm
、 addr2line
などのbinary解析でよく使用されるToolが内包されている珍しい言語です
- https://pkg.go.dev/cmd/objdump@go1.19.4
- https://pkg.go.dev/cmd/nm@go1.19.4
- https://pkg.go.dev/cmd/addr2line@go1.19.4
これからのbinary解析でよく使用されるToolはGNU Binutilsなどで提供されているので、わざわざ自前で管理しておくメリットは薄く感じます
それなのに、なぜこのようなToolを内包させてるのかなとふと疑問に思ったので、考古学者になって歴史探索しようかなと思いました
GNU Binutilsなどで管理されているBinary Tool
Golangが独自で管理している3つのBinary Toolについて、そもそもGNU Binutilsでの挙動を軽くおさらいしておきます
objdump
見たままではあるのですが、これはobjfileの情報を表示するためのToolです
optionでどの情報を表示するかを制御します
叩いてみるのがはやいと思うのでシンプルな Hello, World
を出力するコードで試してみます
package main import "fmt" func main() { fmt.Println("Hello World!") }
$ go build hello.go $ objdump -h hello hello: file format mach-o 64-bit x86-64 Sections: Idx Name Size VMA Type 0 __text 0008c1a7 0000000001001000 TEXT 1 __symbol_stub1 00000114 000000000108d1c0 TEXT 2 __rodata 0003a4d6 000000000108d2e0 DATA 3 __typelink 00000548 00000000010c77c0 DATA 4 __itablink 00000070 00000000010c7d20 DATA 5 __gosymtab 00000000 00000000010c7d90 DATA 6 __gopclntab 0005e170 00000000010c7da0 DATA 7 __go_buildinfo 00000120 0000000001126000 DATA 8 __nl_symbol_ptr 00000170 0000000001126120 DATA 9 __noptrdata 00010640 00000000011262a0 DATA 10 __data 000074f0 00000000011368e0 DATA 11 __bss 0002f120 000000000113dde0 BSS 12 __noptrbss 00004988 000000000116cf00 BSS 13 __zdebug_abbrev 00000129 0000000001172000 DATA, DEBUG 14 __zdebug_line 0001e087 0000000001172129 DATA, DEBUG 15 __zdebug_frame 00005d40 00000000011901b0 DATA, DEBUG 16 __debug_gdb_scri 00000046 0000000001195ef0 DATA, DEBUG 17 __zdebug_info 0003745a 0000000001195f36 DATA, DEBUG 18 __zdebug_loc 0001cc3f 00000000011cd390 DATA, DEBUG 19 __zdebug_ranges 000084cb 00000000011e9fcf DATA, DEBUG
とりあえずわかりやすいようにsection headerを表示してみました
Macでbuildしたコードなのでfile formatが mach-o
になってますね
実行環境は下記です
$ sw_vers ProductName: macOS ProductVersion: 12.6 BuildVersion: 21G115 $ go version go version go1.19.2 darwin/amd64
また、objdumpを使って逆アセンブルしたりもできます
$ objdump -d -j __text hello | head -10 hello: file format mach-o 64-bit x86-64 Disassembly of section __TEXT,__text: 0000000001001000 <_runtime.text>: 1001000: ff 20 jmpq *(%rax) 1001002: 47 6f outsl (%rsi), %dx 1001004: 20 62 75 andb %ah, 117(%rdx) 1001007: 69 6c 64 20 49 44 3a 20 imull $540689481, 32(%rsp,%riz,2), %ebp ## imm = 0x203A4449
nm
nmはオブジェクトファイルに含まれているシンボルをリストアップするToolです
先程のhelloに対して実行すると下記のような感じです
$ nm hello | head -10 00000000010c58d8 s _$f64.3eb0000000000000 00000000010c58e0 s _$f64.3f50624dd2f1a9fc 00000000010c58e8 s _$f64.3f847ae147ae147b 00000000010c58f0 s _$f64.3fd0000000000000 00000000010c58f8 s _$f64.3fd3333333333333 00000000010c5900 s _$f64.3fe0000000000000 00000000010c5908 s _$f64.3fe8000000000000 00000000010c5910 s _$f64.3ff0000000000000 00000000010c5918 s _$f64.3ff199999999999a 00000000010c5920 s _$f64.3ff3333333333333
addr2line
addr2lineはデバッグ情報を利用してファイル名と行番号の情報を取得するToolです
こちらに関して、実はmacOSには入ってないです
興味がある人はLinux環境を用意して自分で叩いて見るのも面白いかもしれません
ぜひご自身で確認してみてください
Golangに内包されているBinary Tool
まずはGNU Binutilsと同じように動作するのか実際に叩いてみます
go tool objdump
$ go tool objdump -h hello usage: go tool objdump [-S] [-gnu] [-s symregexp] binary [start end] -S print Go code alongside assembly -gnu print GNU assembly next to Go assembly (where supported) -s string only dump symbols matching this regexp
optionがすべて許容されているわけではなさそうですね
必要最小限のものだけをGolangで作って管理しているのでしょう
与えられているoptionを指定して実行した結果も貼っておきます
$ go tool objdump -gnu hello | head -10 TEXT go.buildid(SB) :-1 0x1001000 ff20 JMP 0(AX) // jmpq *(%rax) cpu_x86.s:4 0x1001002 476f OUTSD DS:0(SI), DX // rex.RXB outsl %ds:(%rsi),(%dx) cpu_x86.s:4 0x1001004 206275 ANDB AH, 0x75(DX) // and %ah,0x75(%rdx) cpu_x86.s:4 0x1001007 696c642049443a20 IMULL $0x203a4449, 0x20(SP), BP // imul $0x203a4449,0x20(%rsp,%riz,2),%ebp cpu_x86.s:4 0x100100f 226f36 ANDB 0x36(DI), CH // and 0x36(%rdi),%ch cpu_x86.s:4 0x1001012 6e OUTSB DS:0(SI), DX // outsb %ds:(%rsi),(%dx) cpu_x86.s:4 0x1001013 51 PUSHQ CX // push %rcx cpu_x86.s:4 0x1001014 56 PUSHQ SI // push %rsi cpu_x86.s:4 0x1001015 33482d XORL 0x2d(AX), CX // xor 0x2d(%rax),%ecx
go tool nm
$ go tool nm -h usage: go tool nm [options] file... -n an alias for -sort address (numeric), for compatibility with other nm commands -size print symbol size in decimal between address and type -sort {address,name,none,size} sort output in the given order (default name) size orders from largest to smallest -type print symbol type after name
こちらも必要最小限のoptionが用意されているだけに見えます
実行した結果は下記です
$ go tool nm hello | head -10 10c58d8 R $f64.3eb0000000000000 10c58e0 R $f64.3f50624dd2f1a9fc 10c58e8 R $f64.3f847ae147ae147b 10c58f0 R $f64.3fd0000000000000 10c58f8 R $f64.3fd3333333333333 10c5900 R $f64.3fe0000000000000 10c5908 R $f64.3fe8000000000000 10c5910 R $f64.3ff0000000000000 10c5918 R $f64.3ff199999999999a 10c5920 R $f64.3ff3333333333333
go tool addr2line
$ go tool addr2line -h usage: addr2line binary reads addresses from standard input and writes two lines for each: function name file:line
こちらも必要最小限の機能です
objdumpの探索
ひと通りの振り返りも済んだので、まずはobjdumpの考古学をしていきます
とりあえず、現在のファイルはいつ作られたのかを追ってみます
実行ファイルはここです
このファイルの変更履歴を辿ってみます
何やら、このcommitが最初のようです
commit commentがしっかり記載されていますね
Update cmd/dist not to build the C version. Update cmd/go to install the Go version to the tool directory.
と記載があるので、Cで書いていたコードをGolangに置き換えたことが読み取れます
Update #7452
と記載があるので、そのissueも見てみる必要がありそうです
This is the basic logic needed for objdump, and it works well enough to support the pprof list and weblist commands.
上記を読むと、なにやらpprof list周りで何かがあったのかもしれません
もしかしたらissueに記載があるかもしれないので、そちらを辿ってみます
On darwin, running the list command in pprof produces an error from the go-supplied objdump. Weblist is similarly affected. What do you see instead? objdump: syminit: Undefined error: 0
issueを見ると、darwin環境だとpprofでprofilingするとエラーになっていたようですね
Go 1.2.1 (homebrew) works, tip does not. I believe it has something to do with one of the changes to address https://golang.org/issue/6853 and objdump hasn't been brought up-to date.
と記載があるので、別のissueの対応のせいでぶっ壊れたと推測していることがわかります
Russ氏も
This is a known problem; I need to rewrite objdump in Go to make it work again
とここでコメントしているので、このタイミングでGolangに書き直す必要があったのだと思われます
Golangに書き直す理由の話なので、今回はあまり関係はなさそうなので、一旦Cのコードがあったときのコードを辿っていくことにしましょう
ちなみに、この原因になっているissueもかなり面白い問題ではあり、興味がある人は追いかけてみてもいいかもしれません
このissueはいまだに閉じていない問題で、GolangのVersionが上がるたびにどんどんbinary sizeが大きくなって来ているので、なんとかしたいという問題です
興味がある人は下記を辿ってみてください
気を取り直して、Cの実装があった頃を辿ります
C言語のファイルが出来たのは、このタイミングのようです
こちらもcommit commentがしっかり記載されています
考古学をしていると、commit commentの大切さがよくわかります
自分自身もcommit commentをしっかり書こうと思わされますね
runtime/pprof: support OS X CPU profiling Work around profiling kernel bug with signal masks. Still broken on 64-bit Snow Leopard kernel, but I think we can ignore that one and let people upgrade to Lion.
OS Xのbugのせいで困ってるようです
Add new trivial tools addr2line and objdump to take the place of the GNU tools of the same name, since those are not installed on OS X.
なんと、OS Xには addr2line
と objdump
が入っていないから、自前でtrivalなtoolsを作ったようですね
意図せず addr2line
の謎も解けそうです
Adapt pprof to invoke 'go tool addr2line' and 'go tool objdump' if the system tools do not exist.
system toolsにこの2つが入っていないときに go tool
を使うようにしたみたいですね
OS Xのbugについては、こちらのIssueに記載されています
メールでも言及されている通り、OS Xはvirtual cpu timer signalをCPUを使い切ったThreadにちゃんと送らないのが問題のようですね
OS X doesn't reliably deliver the virtual cpu timer signal to the thread that used up the cpu
Go Profiling - Symbols not available
結論
pprofが addr2line
と objdump
を使用するため、 この2つのToolがinstallされていない環境でも動くように、自前でpprofが動く最小限の機能だけを自前で実装することにした
ということがわかりました
Golangはbinaryのdeliverをかなり重視している気がしていて、single binaryで動作出来るようにしているし、様々な環境でも動くように自分たちで腹くくってエコシステム周りを自前で実装してる印象があります
addr2lineの探索
objdumpの探索で一緒に解決されたので、そちらを参照ください
nmの探索
nmについても、同じようにhistoryを辿っていきましょう
ここを見る限りは、これが一番古いcommitのようです
cmd/nm: reimplement in Go
ここについてもCで書かれていたものをGolangで書き直していそうですね
The immediate goal is to support the new object file format, which libmach (nm's support library) does not understand. Rather than add code to libmach or reengineer liblink to support this new use, just write it in Go. The C version of nm reads the Plan 9 symbol table stored in Go binaries, now otherwise unused. This reimplementation uses the standard symbol table for the corresponding file format instead, bringing us one step closer to removing the Plan 9 symbol table from Go binaries.
ここに書いてある通り、libmatchが理解出来ない新しいobject fileのフォーマットサポートするために、libmatchをメンテするんじゃなく、Golangで書けばいいじゃないってことです
また、読み取るsymbol tableをstandardにすることで、GolangのbinaryからPlan 9のsymbol tableを削除するのに1歩近づいたそうです
とはいえ、Cの実装を持ち込んだのかはわからないので、historyを辿ってみます
これが最初のcommitですが、あまり情報がありませんね...
2008/8/4とかなり古いので、正式リリース前です
とはいえ、その後のcommitを見る限りはGolang特有のsymbol tableを読み取らせるためのような気もしますね
結論
ちゃんとしたことは分からないけど、おそらくGolang特有のsymbol tableを読み取らせたかったのだろうと思いました
Golangはかなりアセンブラも特殊なので、開発中に自前のnmあるほうが何かと便利だったのだろうという推測は出来きました
僕はここまでしか追いきれなかったので、ここで議論されてるなどの情報があればぜひ教えてください
最後に
今回はふとした疑問から、なぜBinary Toolの一部を自前で実装しているのかを調べてみました
Golangはかなりマルチプラットフォームを意識しているんだなと改めて実感しました
僕が考古学をするときはこのような形で過去のcommitや議論を追ったりしてます
他の方の参考になれば幸いですし、こんなやり方してますっていう他のアプローチがあればぜひ教えてほしいです
また、Kyashでは絶賛採用強化中です
Golangが好きなEngineerもこれからGolangを使って働いてみたいEngineerも大歓迎ですので、興味があればぜひ応募してみてください
PostgreSQLのロジカルレプリケーションとストリーミングレプリケーションの違い
これはなに?
ちょっとStreaming ReplicationとLogical Replicationの違いを調べる機会があったのでまとめてみた
レプリケーションの種類の比較
PostgreSQLでは、バージョン9.0よりストリーミングレプリケーション(Streaming Replication)機能を利用することができるようになり、バージョン10からはロジカルレプリケーションが利用可能になったので、2つの種類のレプリケーション機能が利用できる
結論の表だけ先に書くと、下記になる
Streaming Replication | Logical Replication | |
---|---|---|
伝播するもの | WAL | WALの情報を論理的なレベルに変換(デコード)したもの |
レプリケーション範囲 | 全てのデータベース(データベースクラスタ単位) | データベース単位、テーブル単位で設定可能 |
異なるMajorVersionのサポート | 不可 | 可 |
異なるOSのサポート | 不可 | 可 |
スタンバイ側での更新 | 不可 | 可 |
備考
正確にはLogical ReplicationとStreaming Replicationはちょっと概念上の階層がずれている気もする
Streaming Replicationについては高可用性を担保するためのLog Shippingの設定の一つ
本来はWALレコードを一度に1ファイル(WALセグメント)ずつ転送するファイルベースのログシッピング方式でWALファイル(16MB)がいっぱいになるとファイル単位で転送されるが、Streaming ReplicationについてはWALレコードが生成されたらストリーミングする設定のこと
WALがなにかはこの辺に書いてある
上記を見ればわかる通り、 これはPhysical Replicationと言っていい
Logical Replicationはその名の通り論理レプリケーションなので、対比構造ならPhysical Replicationと対比になると思われる
しかし、わかりやすさを優先してこのような対比構造で書いている
Streaming Replicationの仕組み
Primaryがクラッシュリカバリーやロールバックのために保存しているWALをStandbyに転送して、Standby側がそれを適用することで実現している
Streaming ReplicationについてはこのWALレコードをリアルタイムにStandbyに転送することで実現している
転送はPrimary側の wal sender
プロセスと、Standby側の wal receiver
プロセスで行っている
Logical Replicationの仕組み
Primary側でpublication、Standby側にsubscriptionを定義し、情報を伝播させる
publicationはテーブルやテーブルグループから生成された更新セットで、INSERT、UPDATE、DELETE、TRUNCATEのうちのどのような組み合わせにも制限することができる
subscriber(subscriptionの定義をしたサーバ)のDatabaseは、publicationを定義することで他のDatabaseに対するPublisher(publicationの設定をしたサーバ)としても使用できる
各々のsubscriptionは、一つのレプリケーションスロット(ここを参照)を通じて更新が通知される
Logical Replicationはsubscriberでもデータ更新ができるので、到着したデータが制約違反をするとレプリケーションが止まるので注意が必要
俗にいうConflictと言われるもの
それぞれのレプリケーションの制約
Streaming Replication
Logical Replication
- DDLはレプリケーションされない
- Sequenceデータはレプリケーションされない
- Large objectsはレプリケーションされない
- レプリケーションはベーステーブルからベーステーブルにしかできない
- ビュー、マテビュー、パーティションルートテーブル、外部テーブル等はできない
- truncateはsupportされているが、外部キーで接続されたテーブルのグループを切り捨てる場合に癖がある
それぞれの使い時
Streaming Replication
- 参照処理の負荷分散
- バックアップ
- 高可用性構成
Logical Replication
- 部分的なレプリケーション
- 異なるバージョン間でのmigration
- 複数DBの情報集約
最後に
同期レプリケーション周りもちょっと書こうかと思ったけど、それはそれで長くなるし、文脈がちょっと違うので省略した
参考Link
Go 1.19 でruntime/pprofのgoroutine profileにおいてSTWの時間が大幅に軽減されたらしいので調べてみた
これはなに?
Go 1.19のRelease Notesを眺めていたら、runtime/pprof
でgoroutine profileを取得する際のSTW(Stop-the-world)の一時停止時間が大幅に短縮され、アプリケーションに対する全体的なレイテンシーの影響が軽減されたという記述を見つけました
runtime/pprof
Stop-the-world pause times have been significantly reduced when collecting goroutine profiles, reducing the overall latency impact to the application.
個人的にどのような変更でSTWの時間が軽減されたのか気になったので追ってみました
変更を探す
1.19に入ったどの修正が対象なのかを探すために、Githubから runtime/pprof
配下の変更履歴を見てみたけど、対象っぽいものを見つけられませんでした
しかし、Release Notesに significantly reduced
って記載するくらいなので、絶対に速度検証もしてるはずで、別の場所の変更で大幅に改善したのかと思い、 runtime
側を探してたらそれっぽいcommitを見つけられました
commitはこちら
Gerrit上だとこちら
https://go-review.googlesource.com/c/go/+/387415
関係するProposal
そもそものissueはこちら
大量のgoroutineを扱うアプリケーションだとgoroutine profileのレイテンシーが厳しいよって感じです
goroutineの量に比例してgoroutine profileの時間が伸びてるよって話
関係するProposalはこちら
内容としては SetMaxDumpGoroutine
を追加して、dumpするgoroutineの数に制限をつけちゃえって感じです
PRも出されてます
でもこのProposalはdeclineされた模様
理由としては CL387415
で様子を見て、それでもだめならその時に考えよう
we should hold off on adding any new API right now, which we would be stuck with forever.
って感じのようです
変更内容
そもそもgoroutine profileは、アプリ内のすべてのgoroutineの一貫したスナップショットを取得するためにSTWする必要があります
アプリ内の全ゴルーチンのスナップショットを取得する必要があり、allgs の反復中にSTWしたままにすると、アプリ内のgoroutineの数に比例して一時停止することになります
この変更ではその代わりに、STWしてる間に一定量のbookkeepingだけ行うようにしています
バリアを導入して、goroutineの実行を許可する前に、goroutineがprofileに表示されることをスケジューラーが確認し、そのスタックがSTW中とまったく同じように記録されるようにします
アプリが通常の操作を再開している間、allgs を反復処理し、その間にスケジュールされていない限り (自分自身をプロファイリングしていない限り)、それぞれをprofileに追加します
最後にもう一度STWしてバリアを取り除き、一定量のクリーンアップ作業を行います
これによる影響で、GoroutineProfile の固定オーバーヘッドとゴルーチンごとの CPU 時間コストの両方が増加してます
また、スケジューラーが他のゴルーチンを実行するために呼び出しを中断する可能性があるため、GoroutineProfile への呼び出しのwall-clock latencyも増加してます
この辺はトレードオフなので仕方ないところかなと
コードの変更点
Goroutine scheduler
コードの変更点を見ていく前にそもそもGoroutine schedulerの動きとか変数とかを把握していないと読みにくいので、最初にこの辺を読んでおくとスムーズかと思います
とりあえずこれが頭に入っていると、雰囲気は掴めるとは思います
// The main concepts are: // G - goroutine. // M - worker thread, or machine. // P - processor, a resource that is required to execute Go code. // M must have an associated P to execute Go code, however it can be // blocked or in a syscall w/o an associated P.
Design Docのリンクも置いておきます
いままで
いままではシンプルに世界を止めて、すべてのg分(allgs)を反復処理して、世界を再開させていた模様
変更点
gにstatus管理するためのフィールドが追加されました
StatusはgoroutineProfileState
で(Absent,InProgress,Satisfied)があります
profileの状態管理用の変数も新たに切られていて
var goroutineProfile = struct { sema uint32 active bool offset atomic.Int64 records []StackRecord labels []unsafe.Pointer }
STWして状態を書き込むようにしてます
その後、世界を再開させてから、profileされるべきgoroutineか確認して
goroutine profileに書き込むようになってます
オーバーヘッドの原因はこの辺でチェック処理が入ったことによる影響だと思われます
最後にクリーンアップ処理をSTWして行ってます
まとめ
ちょっと気になってたので、どのようにpprofのgoroutine profileのSTWの時間を削減したのか調べてみました
簡単にまとめると
STWしてgoroutineの数だけ反復処理をしてprofileを取得していた処理を
STWしてgoroutineの数だけbookkeepingだけ行い
世界を再開してからbookkeepingされた情報を元にgoroutine profileの書き込みを行うようにした
その影響でgoroutine profileの固定オーバーヘッドとCPU時間コストが少し増えた
って感じだと思われます
goroutineの数が多いアプリケーションを作ってる人にはうれしい修正だったのではないかと思いました 今後はgoroutineの数が多いアプリケーションを作っている人も安心してprofilingができるので、ぜひとも取得することをおすすめします
逆に、あまりgoroutineの数が多くないアプリケーションの場合はオーバーヘッドが増えただけかもしれません とはいえ、問題になるようなオーバーヘッドではないと思われるのでprofileはどんどん取得して問題はないかなと思います
それではみなさん、ハッピーはProfiling Lifeを!
fourkeys基盤を作った話で登壇しました
今更ですが、こちらのイベントで登壇しました
fourkeys基盤を作った話をしたので、登壇資料を置いておきます
生産性の可視化やfourkeysについて興味がある人はぜひ読んでみてください
PostgreSQLでIndex名,column名,型名の一覧を取得するSQL
これはなに?
タイトルのままだけど、PostgreSQLでIndex名とcolumn名と型名の一覧が欲しかった しかし意外と取得に苦労したのでSQLを貼っておく
SQL
最初は Information_schema
でうまいこと引いてこれないかなって思って試したけど意外とできなかった
理由としては Information_schema
にcolumnとindexを紐付けるテーブルが存在しなかったこと
Information_schema.colums
でcolumnの情報は取れて、 pg_indexes
でindexの情報は取れたけど紐付けは難しかった
pg_indexes
のcreate文をparseすればいけるのかもしれないが、ちょっとだるいのでサクッとSQLだけで取りたかった
それと型の情報を出すのも意外にしんどい
PKだけであれば information_schema. table_constraints
から引けば良さそうであったが、PK意外のindexが取れなそうであった
上記を踏まえて、システムカタログから取ってくるようにしてみた
SELECT t.relname AS table_name ,i.relname AS index_name ,array_to_string( array_agg( a.attname ), ',') AS column_names ,array_to_string( array_agg( tp.typname ), ',') AS column_types FROM pg_class AS t ,pg_class AS i ,pg_index AS ix ,pg_attribute AS a ,pg_tables AS ta ,pg_type as tp WHERE t.oid = ix.indrelid AND i.oid = ix.indexrelid AND ix.indisprimary = false AND ix.indisunique = false AND a.attrelid = t.oid AND a.attnum = ANY( ix.indkey ) AND t.relkind = 'r' AND t.relname = ta.tablename AND ta.schemaname = current_schema() AND a.atttypid = tp.oid GROUP BY t.relname ,i.relname ,ix.indisprimary ,ix.indisunique ORDER BY t.relname ,i.relname;
複雑なSQLになってしまったが、取れたので良かろう
もっときれいに取れる方法知ってる人は教えてくだされ
関連Link
負荷試験を定期的に実施できるように体系化した話
これはなに?
Kyash Advent Calendar 2021 22日目の記事です
昨年のAdvent Calendarでは負荷試験環境を作った話を書きました
今回は負荷試験の実施方法を体系化し、定期的に実施できる状態を作ることができてきたので、その話をまとめていきたいと思います
おまえだれ?
Kyashで主にお金の入出金周りを担当しているチームでBackend Engineerをしている @uncle__ko です
銀行やコンビニなどとシステム接続を行ったり、各種電文のParserを作ったり、電文の暗号化/復号化をしたり、マイクロサービス間&外部システムとの結果整合性の担保に苦心したりしています
事前準備
負荷試験をただ闇雲に行っていても意味がありません
何事もゴールを定めるのがとても重要ですし、負荷試験においても例外ではないと思ってます
ですので、Backend Engineer,SRE,PdM等の関係者を集めて全員の認識を揃えることが重要です
目的を設定せよ
今回の負荷試験の結果で何を得たいのかを決めます
それによってアプローチも変わってきますので、まずは全員で目的を定めていきます
- 例
- 想定されるピーク時のRequestを受けても問題なく稼働することが知りたい
- 現状のシステムで捌ける限界値を知りたい
- Push通知のrate limitを決めたい
観点を決めよ
負荷試験を実施するにあたりどのような観点でテストをするのかを決めます
目的によって色々な観点がでるかと思います
どのようなアプローチで負荷テストして、どのような値を注視していくのかを決めることで、負荷試験結果を振り返りやすくします
- 例
- 瞬間的なピーク値のテスト
- 広告/キャンペーン/push通知など瞬間的に負荷が増大するテスト
- 長時間の平均値のテスト
- 通常稼働時のテスト
- メモリリークの発見など
- 負荷を大きくしていくテスト
- 限界を知るためのテスト
対象を定めよ
何処にどの程度の負荷を掛けるべきかを決めます
目的に応じて必然的に決まるかと思います
負荷を試算せよ
- ピーク値
- 平均値
を決めます
ここに関しては負荷試験の目的ベースにエンジニアだけではなくPdMやドメインに詳しい人と一緒に、実際のユーザ数等を含め現実に使い数値を算出します
ちなみに負荷試験でここが一番難しいと感じます
定石も正解もない部分だと思ってます
ただ、あまり現実的じゃない数値でテストしたところで、サーバがオーバースペックになってしまったり、逆に想定より多くてシステムが耐えれなかったりしたら意味がないので、とても重要な部分です
- 例
確認方法を決めよ
どの項目をどのように確認するかを決めます
チェック項目と確認方法を事前に決めておくことで、振り返りをしやすくします
- 例
- datadogでRDSの
aws.rds.cpuutilization
を見る - Amazon CloudWatchでRDSの
aws.rds.cpuutilization
を見る
- datadogでRDSの
ちなみにKyashではDatadogを使ってシステムのmonitoringをしているので、負荷試験用のdashboardを作っています
シナリオを作成せよ
やっとシナリオが作成できます
Kyashの場合は昨年の記事にあるようにlocustを使っているので、locustのシナリオファイルとして、ここまで決めてきたものを取り入れたコードを記述していきます
記述の方法や構成等は昨年の記事をご覧ください
Locust + AWS ECS(Fargate)で負荷試験環境を作った話 - unless’s blog
実施
実施については、負荷試験担当の人以外にも興味がある人は参加できるように事前にSlack等で声がけしたりしてます
また、負荷試験用の専用Channelを用意していて、負荷試験に関することはそのChannelに情報が集約されてます
そして、負荷試験当日はみんなでわいわいしながら負荷試験を実施してます
上で紹介したdashboardを見ながらネックになっている処理や、マイクロサービスを特定しながらみんなで意見交換しつつ負荷を掛けていきます
結果の共有
負荷試験の結果を振り返るMTGを実施します
目標を達成していない場合はどこを直すべきか、それはすぐに可能か別の方法はあるか等をメンバーで考えます
また、その結果を受けて何をいつまでに実施しないといけないのか、次回の負荷試験の日程はいつなのかなどを決めるようにしています
このように、実施と振り返りを目標が達成できるまで繰り返していきます
Kyashでは実施結果をGitHub issueで管理していて、議事録としてその時の結果やメンバー、振り返り内容とネクストアクションを残しておき、後世に知見を残せるようにしています 結果に関してはスクリーンショットも残すようにしています
まとめ
このようにKyashでは闇雲に負荷試験を実施するわけではなく、目的、観点、対象を把握した上で目標を設定し、その目標をクリアするために負荷試験を行なっています
この実施方法が正解とも思っていませんし、まだまだ改善の余地はあるかもしれませんが、ある程度はこの方法で回るようになってきたかなと思っております
負荷試験などはどうしても後回しにされがちで、特にベンチャー企業などではなかなか実施できないことも多いと思います
Kyashでも大きめのプロジェクトのタイミングなどでは実施できていましたが、定期的に実施することは出来ていませんでした
しかし、このようにある程度体系だった方法を確立し提案資料にまとめてEMやPdMに提案していったところ四半期に1回実施できるような体制が作れるようになりました
今後も実施方法をブラッシュアップしつつ、しっかり負荷対応も行っていき、品質の高い、ユーザが安心して使用できるアプリを作っていきたいと思います
さいごに
Kyashでは一緒に負荷試験を実施してくれる仲間を募集しています
すこしでも興味を惹かれましたらぜひ応募してください!
サーバーサイドエンジニア / Serverside Engineer - 株式会社Kyash
また、下記もぜひチェックしてみてください!
Kyash Advent Calendar 2021はまだ続きます!明日の投稿もお楽しみに!