バグチケットに各APIのリクエストとレスポンスが貼ってあったらバグの調査は早く終わるのか?
モヤモヤと考えたことを書き出しています。結論ありません。実践もまだしてません。
背景
私は今、こんな感じでInternalなWeb APIが相互作用するサービス*1に関わっています。
また、こんな感じで組織が別れています*2。
さて、このサービスについて不具合が起票されることがあります。
リリース前のQAだったり、残念ながらリリース後、本番可動しているサービスに対してだったりします。
いずれにしても大事なこととして、不具合はここの視点、最も浅いレイヤである、UIの「挙動が期待通りでない」事象として発見されます。
一方システム的には、不具合の原因は以下のいずれかになります(複数の組み合わせの場合もある)
不具合を修正するためには、この中のどこに問題があるかを見つける必要があります。
どうすれば早く原因が見つかるのか
シンプルに、いずれか一つのプログラム(=UI component または API 1~4 のいずれか)にバグが有るケースを考えます。
バグが有る、ということは外から見たプログラムの挙動として、以下の2つのいずれかに分類することができます。
たとえばAPI 3がバグっていて、「正しいリクエストを受けているのに、レスポンスが正しくない」場合
API 3が正しくないレスポンスを返すので、それがAPI 1→UI と伝搬することになります。
この場合、API 3を修正する必要があります。
また、API 2がバグっていて、「正しいリクエストを受けているのに、次のAPIの呼び出しが正しくない」場合
API 4そのものは正しく動作していますが、リクエストが誤っているため期待通りでないレスポンスを返し、最終的にそのレスポンスが原因となってUIの挙動が期待通りになりません。
この場合、API 2を修正する必要があります。
「赤い矢印」が見つかればどのプログラムがバグっているのかわかる
前項で見たとおり、期待通りでないリクエスト、またはレスポンスである、「赤い矢印」
がどこから始まっているかがわかれば、バグのあるプログラムである、「赤い四角」
の箇所が特定できそうです。
ということで、バグが起票された際に、全APIのリクエストとレスポンスがチケットに貼ってあれば、即座にどのAPIがバグっているのかわかるのでは? と考えました。
こんな表を自動生成することはできないでしょうか。
API | リクエスト | レスポンス | 他APIへのリクエスト |
---|---|---|---|
API 1 | to API 2 , to API 3 | ||
API 2 | to API 4 | ||
API 3 | |||
API 4 |
※矢印はイメージです。実際はこの表の各セルに具体的なJsonが書かれます
Spring Cloud Sleuth でTraceIDを発行しているため、一つのUIアクセスから始まる各APIのリクエスト/レスポンスを集めてくるのは技術的には可能そうです。
「赤か青か」を判断するのが実は難しい?
上の表では、API 2は正しいリクエストを受けているにもかかわらず、API 2からAPI 4へのリクエストが間違っています。ここから、API 2にバグが有る、と推測することが可能です。
では、API 2→API 4のリクエストが間違っている と気づくことができるのは誰でしょうか…。API 2はTeam C、API 4はTeam Dが担当しているので、それぞれのチームのエンジニアに気づくチャンスがありそうです。
API 2の担当エンジニアは気づけるか?
まず、API 2の担当エンジニアは、上の表から以下のことに比較的簡単に気づけそうです。
ここから、API 4へのリクエストが間違っているか、API 4そのものにバグが有る という推測は立てられそうです。
しかし、API 4へのリクエストの検証についてはどうでしょうか。
シンプルな間違え*3ならばすぐ気がつけそうですが、もう少し複雑な間違えですとすぐには気づけないかもしれません。
また、API 2の担当エンジニアが、API 4のリクエスト使用を誤って理解しているかもしれません。
レスポンスの正しさの確認のほうが簡単かも
では一旦、「すべてのAPIのリクエストは正しい」と仮定して、レスポンスの正しさだけに注目したらどうでしょうか。
バグの起票時に、UIレベルで「本来Aという振る舞いをすべきところ、Bという振る舞いをしている」ということは記載されています。
この場合、「『B』の原因となるレスポンスを自分のAPIが返している」かどうかの確認は、リクエストの確認の正しさに比べると比較的難易度が低い …気がします。 自分の経験的には。
各APIのレスポンスだけとりあえず並べてみる
レスポンスの正誤の判別は比較的簡単… という前提で、「バグ起票時に各APIのレスポンスが自動的に貼り付けられるツール」のようなものを想像してみます。
API | レスポンス |
---|---|
API 1 | |
API 2 | |
API 3 | |
API 4 |
※繰り返しになりますが、矢印のところには具体的なJSONが入るイメージです
システム構成が頭に入っていれば、以下のような図が見えるわけです。
※データがおかしい可能性もありますが、今は除外して考えます
API 3には絶対バグがないことがわかった!バンザイ! といいたいところなのですが、
UI, API 1, API 2, API 4 のどれもバグ持ちの可能性が消えていません。
これだけでは、あまり何かが解決しているようには思えません。
『複雑なドメインに泥臭く立ち向かう』JJUG CCC 2018 Fall #jjug_ccc
JJUG CCC 2018 Fallに参加し、@su_kun_1899 さんの 『複雑なドメインに泥臭く立ち向かう』というセッションに参加してまいりました。
法律という完全に外部要因で決まって変えることもできないものが要件に深く絡む世界で、
どのように立ち向かっているのか、というお話でした。
今一番読みたかった本が現れたかのように感じたセッション
どうやって複雑なドメインを理解し適切にコードに落とし込むか
というのは今現在の私の最大の関心事の一つでありながら、
もう一段自分のレベルを上げたいのだけれど何を勉強したら良いかわからないのが課題でした。
そんな中、今日のセッションは
- 自分が聞きたかった話を
- 理解できる言葉で
- 体系立てて
話してもらえたセッションでした。
更にいうと、一部の内容は、普段から自分がぼんやりと「思っていた*1」ことを、明確に言語化してもらった内容でした。
以下、私が感じたことが中心です。
順序も元の発表と異なります。ご了承ください。
共感したポイント
一本道を見つける
「変化は受け入れ、一本道に対して複雑さを足していくアプローチをとる」
そうなんですよね。いかに幹を見極め、枝葉と分離できるか。
いまから作ろうとしているプロダクトの背景にある真の目的はなんなのか。
それを見つけ出せることが重要なんだと思います。
モデルを抽出するするために ひたすら write & talk
とにかくなんでも良いから書き始めてみること。
脳内でウンウン考えているだけだと、自分自身が認識するのもなかなか難しい。
サービスクラスがユースケースを表す
すごくわかります。
個人的に、サービスクラスが数行でかけたら価値だと思っています。(というよりはサービスクラスがごちゃごちゃしたら負け)
taichiw.hatenablog.com
コードに書いて、動かして、初めて理解したと言える
今年の私の実体験。
単なるAPIだろうがんなんだろうが、実際に動くものを見て、触れて、初めて理解できることや気づける疑問が人間どうしてもあります。
更にこの時、クソコードだろうがなんだろうが、
とりあえずでも「動いて」、そして一応でも「読める」プログラムがあることによって、それまでに比べて格段に理解がはかどるようになります。
皆さんエンジニアですから。
(クソコードでもよいのですが、「適切な枠で切られている」ことはとっても大事。その中がクソな分には最悪差し替えれば良いのです)
気づかせてもらったこと
最低限のドメインナレッジの勉強は必要
実際の業務のロールプレイをしてみる
手書きで帳票を書く、という言うようなことをしているそうです
3歳の息子が私のサービスを使った日
このエントリは、子育てエンジニア Advent Calendar 2018の14日目のエントリです。
このブログではプライベートなことをあまり書いたことがないのですが、乗っからせていただきます。
About 私 & 息子 (と奥さん)
私には、現在3歳半の息子がおります。一日の生活リズムはこんな感じです。
私の「夢」
私には、ずっと以前、社会人になるよりももっと前から思い描いていた「夢」が一つありました。
「家のリビングにテレビが置いてあって、そのテレビを指差して、『このテレビ、お父さんが作ったんだよ』って自分の子供に向かって話すんです」
この話を唐突に、(今も働いている)インターネットサービスの会社の採用面接で話したので、当時面接官の方にポカーンとされたのを今でも覚えています。
「テレビ」というのは喩えでして、
自分の作った「モノ」を自らの子供に見せたい、使ってもらいたい、という思いを、子供もいなければ結婚もしていない当時から抱いていたんです。
ようやく念願のリリース…!
私の「お仕事」に話を移します。
最近、実に一年半もかけた新規開発案件がようやくリリースされました。*1
Webサービスをやっていて一年半もの間じっくりと未稼働のサービスに取り組む、というのは初の経験で、勉強になる部分も多かったのですが、
やはり、「本番で動いている」というのは良いものです。
家に帰ってからも思わず、自分のスマートフォンを取り出しては、
こんな感じの画面を見ながら、
動いてる姿にニヤニヤしておりました。
すると覗き込んでいた息子が、脇から「ANAだー」「JALだー」と言い出したんです。
この、羽のアイコンだけ見て飛行機を表していると気づいたことにまず驚いたのですが、
どうやらこの検索画面が大変お気に召されたようで、息子氏は私から携帯電話を奪い取り、グリグリと画面をスクロールして、
「この水色のは何?」「これは?」
と、航空会社を覚えることを数日間お楽しみあそばされたのです。
いちいち携帯電話を奪われるので大変面倒くさかったのですが、この日は「 息子が私のサービスを使った記念日」になりました。
残念ながら息子は予約までしてくれたわけではないので、真に「使ってくれた」わけではありません。
また、「お父さんがこれを作った」と話してみたのですが、ピンときていないようでした。
しかしながら、
自分が作ったモノを、誰でも利用できる
というコンシューマ向けサービスのエンジニアならではのやりがいを、息子を通して改めて感じた日でした。
*1:…が、この後、事情により一旦サービ停止中となっております。ご迷惑をおかけしています
ParallelStreamで外部接続しちゃいかんの? → 独自スレッドプールでParallelStreamを使ってみた
ParallelStreamでI/O待ち状態を作るとプール内のスレッドを食いつぶす?
ParallelStreamはアプリ全体で共通で持っているスレッドプールを使う上、CPUのコア数分しかスレッドが無いのでI/O待ちが発生するようなものには使ってはいけない、という話を聞きました。
そうなの?ということで検証してみました。
List<Integer> list = new ArrayList<>(); for (int i = 0; i < 10; i++) { list.add(Integer.valueOf(i)); } list.stream() .parallel() .forEach(i -> { //1秒かかる外部サービス restTemplate.getForObject("http://localhost:8081/sleep/", String.class); System.out.println(i); });
手元のPC上で動かした結果を、Visual VMでみてみたところ。
ForkJoinPool.commonPool-worker- ...
という、アプリ内での共通スレッドプールが、ParallelStreamで使われています。
4コアCPUで実験しているため、プール内のスレッドは3つ。(それに加えて呼び出し元のスレッドがあるので、JVM全体で可動しているスレッドは4つ)。
I/O待ちの間はスレッドは専有状態になるため、
4個通信 → (1秒経ってレスポンス) → また4個通信
というように、外部通信が4件ずつしか行われません。
また、この間に同じアプリ内の他の処理でもParallelStreamを使おうとすると、そちらとの、Common Pool内のスレッド取得合戦が起こってしまうことも確認できました。
オレオレスレッドを使ってみる …でもこれ大丈夫?
Common Poolのスレッド数を設定で増やすことも可能なようですが、「スレッド取得合戦」を避けるため、独自のスレッドを作ってParallelStreamを回すことを検討してみます。
ForkJoinPool pool = new ForkJoinPool(10); pool.submit(() -> list.stream() .parallel() .forEach(i -> { //1秒かかる外部サービス restTemplate.getForObject("http://localhost:8081/sleep/", String.class); System.out.println(i); }) ).get();
ForkJoinPool pool = new ForkJoinPool(10);
で、スレッドプールを作成し、このプールを使ってparallel streamを回します。
すると
- スレッド数の上限が10になったので、10並列で通信を実行。一気に10回分のサービス呼び出しが終了。
- 同時に、アプリ内の他の箇所でParallelStream(デフォルトのCommon Poolを使用)を実行してもスレッド取得が競合しない
という結果が得られました。
ただ… メソッド実行のたびにスレッドが増えていきます。大丈夫なんでしょうか。
これ、本番可動させてたら無尽蔵にスレッドが増え、一定期間経つとアラートが飛んできて、再起動を余儀なくさせられるんじゃないでしょうか。
過去の悪夢が蘇ってきます。
…ということで軽くロングランテストを実施。
3時間位流し続けてみた限りでは、
GCのタイミングで古いスレッドは消えていっており、特にスレッドが溢れたりメモリリークしたりしている様子はありませんでした。
49652番目のスレッドプール。これより以前に作成されたものは消えている。
ただ、都度スレッドを作成して破棄するのは、CPUとメモリの無駄遣いっぽい雰囲気がします。
専用の「スレッドプール」を作る
ということで、以下のように(シングルトンの)インスタンス変数としてスレッドプールを定義してみました。
@RestController public class TestController { final ForkJoinPool pool = new ForkJoinPool(100); @GetMapping(value = "test") public String test() throws ExecutionException, InterruptedException { RestTemplate restTemplate = new RestTemplate(); List<Integer> list = new ArrayList<>(); for (int i = 0; i < 10; i++) { list.add(Integer.valueOf(i)); } pool.submit(() -> list.stream().parallel() .forEach(i -> { //1秒かかる外部サービス restTemplate.getForObject("http://localhost:8081/sleep/", String.class); System.out.println(i); }) ).get(); System.out.println("OK"); return "OK"; } }
いい感じでスレッドの使い回しができているようです。
SpringBootのアプリで、JMHを使ってみた
メソッドのちょっとした書き方の違いによる速度の計測をしたくて(Micro Benchmarkというらしい。対義語はMacro Benchmark)、JMHで計測をしてみました。
JMHのオフィシャルページを見ると、mvn archetype:generate なるものが出てきて「なんじゃこりゃ?」だったり
適当にググったページでは、そもそも古くて今は動かないサンプルだったりと、どうにもわかりにくかったのですが、こちらのページのとおりに試してみたところ、うまくいきました。
Microbenchmarking with Java | Baeldung
それでもハマったところ
1. SpringBootのmain methodがあるクラスに、Benchmark用のmain methodも書いたところ、以下のようなエラーが出て何故か起動できず。
# JMH version: 1.21 # VM version: JDK 1.8.0_121, Java HotSpot(TM) 64-Bit Server VM, 25.121-b13 # VM invoker: C:\Program Files\Java\jdk1.8.0_121\jre\bin\java.exe # VM options: -Xmx3000m -Ddebug -XX:TieredStopAtLevel=1 -Xverify:none -Dspring.output.ansi.enabled=always -Dzookeeper.address=b -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=51723 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=localhost -Dspring.liveBeansView.mbeanDomain -Dspring.application.admin.enabled=true -javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2018.1\lib\idea_rt.jar=51724:C:\Program Files\JetBrains\IntelliJ IDEA 2018.1\bin -Dfile.encoding=UTF-8 # Warmup: 5 iterations, 10 s each # Measurement: 5 iterations, 10 s each # Timeout: 10 min per iteration # Threads: 1 thread, will synchronize iterations # Benchmark mode: Throughput, ops/time # Benchmark: rakuten.travel.availability.calculation.api.BenchmarkRunner.benchMarkKick3 # Run progress: 0.00% complete, ETA 00:10:00 # Warmup Fork: 1 of 1 Error: Exception thrown by the agent : java.rmi.server.ExportException: Port already in use: 51723; nested exception is: java.net.BindException: Address already in use: JVM_Bind <forked VM failed with exit code 1> <stdout last='20 lines'> </stdout> <stderr last='20 lines'> Error: Exception thrown by the agent : java.rmi.server.ExportException: Port already in use: 51723; nested exception is: java.net.BindException: Address already in use: JVM_Bind </stderr>
→ 上記ページのように、mainメソッドを書くための専用のクラスを別に作ったところ、問題なく動作。
2. テスト対象のメソッドにパラメータを渡す方法がわからなかった
→ これも上記ページに書いてある
おまけ
とあるメソッドのリファクタ結果
Benchmark Mode Cnt Score Error Units BenchmarkRunner.benchMarkKick1 thrpt 5 29271.881 ± 1088.831 ops/s BenchmarkRunner.benchMarkKick2 thrpt 5 74336.732 ± 4597.243 ops/s BenchmarkRunner.benchMarkKick3 thrpt 5 280990.396 ± 185017.629 ops/s
Mavenで実行しているTestに割り当てるヒープを増やす方法
Maven Surefire Pluginを使う。
https://maven.apache.org/surefire/maven-surefire-plugin/
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <configuration> <argLine>-Xmx1024m -XX:MaxPermSize=256m</argLine> </configuration> </plugin>
ぐぐると、MAVEN_OPTSに書けばいい とか、 Jenkinsの場合はGlobal MAVEN_OPTSに書けばいい とか出てくるんだけど、
これは、MavenのJavaプロセスに割り当てるメモリ。
その中で実行されるテスト用のメモリは別に確保される。
実際、テストコード内で以下のように割り当てメモリを書き出してみると
int mb = 1024 * 1024; Runtime runtime = Runtime.getRuntime(); System.out.println("Max Memory:" + runtime.maxMemory() / mb);
MAVEN_OPTSに何を書いても増えないし減らない。
Chrome Tech Talk Night
events.withgoogle.com
Google さんで行われた、Chrome Tech Talk Night に参加してきました。
弊社の新人研修依頼、10年以上ぶりに森タワーのオフィスに入ったなぁ とか しょうもないことを思いながら。
そもそも私、フロントエンジニアではないので、正直細かいところはよくわからんというか的はずれな理解だった感も否めないのですが…
発表する人、発表する人、
くっ ここはそういう世界か…!
と。
テクノロジー会社としてのあるべき姿をまざまざと見せつけられた気がしました。
なので忘れないようにブログ。