はじめてのmaven central 公開
前置き:このエントリはJavaおよびJava周辺の*1開発環境に全く縁の無い人間が、可能な限り依存ソフトウェアを少なく手順をシンプルに保ったままやろうとしたものであり、知識・経験のある人にとっては全く最適な手段でなかろうことをお断りします。
先日のエントリ で書いたとおり woothee 1.0.0 をリリースした。Perl, Ruby, Node.js および PHP などはそれぞれの言語毎のモジュールリポジトリに登録されている。
が、Javaについては自分が Maven Central の勝手がわからず、されてると便利だよなーとは思いつつ放置していた。
が、なんと @making さんからMaven Central登録用の pull requestがきた 。きてしまった。これで最大の問題(xmlを書く)はおおむね解決されてしまったので、覚悟を決めて登録作業をすることにした。
せっかくやったので、アカウント登録からfirst releaseまでの手順をここにまとめてみる。
なお環境は以下の通り。
- 適当なバージョンのMac OSX (10.8.5)
- 適当なバージョンのJDK (1.7.0_55)
- 適当なバージョンのMaven (3.2.1)
- できる限りCLIで頑張る
- XMLはEmacsで編集する
JDKとMavenは適当にインストールする。javacとmvnコマンドが動けば多分大丈夫。Mavenについては自分はtarballを展開してパスを通した。
なお英語のドキュメントはここにあるが、割とJavaに慣れた人向けのドキュメントな気がする。つらかった。
- http://central.sonatype.org/pages/ossrh-guide.html#deployment
- http://central.sonatype.org/pages/apache-maven.html
パッケージ名前空間を決める
Maven Centralへのモジュール登録は、人(アカウント)ごとにアップロードできるパッケージの名前空間が決まっている。Java書いてると出てくる package com.mycompany.softwarename; っていうアレ。
確保する必要があるので、何か決めよう。自分は tagomor.is っていうドメインを持ってたのでそれを使った*2。これ、誰がどうやって正当性を確認しているんだろうな……。
なお登録時に共同メンテナみたいなのも登録できるので、1パッケージを一人しかデプロイできないということではない。安心。
アカウント登録
誰がどのパッケージを登録できるのか、みたいな権限はOSSRH JIRAで申請して変更してもらう。ので、まずそのJIRAのアカウントを作る必要がある。このアカウント(ユーザ名/パスワード)は実際のバイナリのアップロード時にも使う。
https://issues.sonatype.org/secure/Signup!default.jspa
このアカウント作成はすぐ終わるはず。終わったらパッケージアップロード権限的なのをもらうためのチケットを作成する。
https://issues.sonatype.org/secure/CreateIssue.jspa?issuetype=21&pid=10134
SummaryとDescriptionは、なんか適当に書けばいいんじゃないでしょうか。自分の場合はこれだけど、プロジェクト名くらいしか書いてない。
groupId には前で決めたパッケージの名前空間を書く。例えば is.tagomor と書いておけば is.tagomor.** なパッケージは自由にデプロイできることになる。
Project URLとSCM uriはそのOSSプロジェクトのWebページとリポジトリURL。GitHubの場合は普通にViewのURLと .git のHTTPS URLを書いとけばいい。
Username(s) は共同開発者がいる場合はその人の JIRA username を書く。とりあえず自分だけでいいなら空欄。
Already Synced to Central は初めて申請するなら No でいい。これは多分既に申請通ってたりする分の修正とかの場合に選ぶものだと思う。
これを埋めたら Create する。すると誰かがなんか作業をやってくれる。ドキュメントには2日は待てみたいに書いてあるけど、自分の場合は8時間で完了した。
完了すると登録したメールアドレスに "Resolution: Fixed" と書かれたメールが来る。
なおメールの末尾には "please comment on this ticket when you promoted your first release, thanks" とも書かれてた。こうしておかないといけない事情が忍ばれる。
pom.xml の記述
これはやってもらいました! すまん!!!
https://github.com/woothee/woothee-java/pull/1
これも基本的には公式ドキュメントにある記述を pom.xml に入れていけばいいと思うんだけど、ひとつだけ問題があって、このままだとビルド時にGPG Keyを要求するので Travis-CI が mvn install したときにコケる。
ググったらStackOverflowの記事がヒットした。これをそのままパクり、普通に mvn install や mvn test したときにはGPG署名用プラグインを呼ばないようにpom.xmlを変更。これで Travis-CI がこけなくなる。また、この時点で mvn install やら mvn test するときに、まだGPGのことを考えなくて済む。
で、この状態でとにかくテストを通せるようにするとか、とか。
GPGのセットアップ
Maven Centralにバイナリを上げるにはGnuPGで署名する必要があるらしい。ので、インストールする。以下のURLから "Download GPG Suite" をクリックしてdmgをダウンロード、展開してインストールした。
インストールすると勝手に起動したので Key -> Generate して鍵ペアを作る。Full NameとEmail addressを入れて Pass Phraseを入れるだけで基本的には終わり。
作ったら公開鍵の方は "Send public key to keyserver" しておく。
またインストールが完了したら、作業中の端末で "gpg2" コマンドが動くことを確認しておく。動かなかったらshellを起動し直すなどする。
アカウント・パスフレーズの設定
OSSRHやGPG鍵を使えるよう、Mavenの設定をしてやる必要がある。設定は settings.xml というXMLに書くが、これはユーザ毎にあるものらしい。自分の場合は ~/.m2/settings.xml に置けば読みこまれた。
OSSRHとGPGの設定が両方必要。まとめるとこんな感じ。
<settings> <servers> <server> <id>ossrh</id> <username>YOUR_JIRA_USERNAME</username> <password>YOUR_JIRA_PASSWORD</password> </server> </servers> <profiles> <profile> <id>ossrh</id> <activation> <activeByDefault>true</activeByDefault> </activation> <properties> <gpg.executable>gpg2</gpg.executable> <gpg.passphrase>GPG_KEY_PASSPHRASE</gpg.passphrase> </properties> </profile> </profiles> </settings>
YOUR_JIRA_USERNAME, YOUR_JIRA_PASSWORD とか GPG_KEY_PASSPHRASE のところは自分のものを埋めること。
これを書いておけば maven plugin が勝手に読んでくれる。
デプロイ
以上で手順は完了。「pom.xmlの記述」で書いたとおり、指定したときにしかGPG署名をしないような記述にしたので、デプロイ時にはそのスイッチを有効にしてやる必要がある。
$ mvn clean deploy -DperformRelease=true
うまくいったら最終的にはこんな表示が出て終了するはず。
[INFO] * Upload of locally staged artifacts finished. [INFO] * Closing staging repository with ID "istagomor-1002". Waiting for operation to complete........... [INFO] Remote staged 1 repositories, finished with success. [INFO] Remote staging repositories are being released... Waiting for operation to complete.............. [INFO] Remote staging repositories released. [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 02:33 min [INFO] Finished at: 2014-10-28T16:07:10+09:00 [INFO] Final Memory: 30M/363M [INFO] ------------------------------------------------------------------------
この時点ですぐに以下のURLを見たところ、ちゃんとパッケージ名をたどったところにアップロードされていた。
https://oss.sonatype.org/content/groups/public/
うまくアップロードできたことが確認できたら、JIRAのチケットのほうにコメントを書いておく*3。自分の場合はこんな感じで適当に書いた。
I've just promoted my first release, woothee-java-1.0.0.jar: https://oss.sonatype.org/content/groups/public/is/tagomor/woothee/woothee-java/1.0.0/
完了!
これで(特に問題がなければ)定期バッチによりmaven centralに同期されるらしい。作業の進捗を示すものだと思うけど、以下のようなメールが順番に来てた。たぶんみっつめが来たら全部の処理が完了。
- "Nexus: Staging Repository Dropped"
- "Nexus: Staging Completed"
- "Nexus: Promotion Completed"
見事以下のURLから見えるようになってた。
http://repo1.maven.org/maven2/is/tagomor/woothee/woothee-java/1.0.0/
おつかれさまでした!
多言語対応User-Agentパーサライブラリ Woothee 1.0.0リリース: OS versionの出力をサポート
地道にバージョンアップを繰り返しているUser-Agentパーサライブラリの woothee ですが、このたび 1.0.0 をリリースしましたのでお知らせいたします。
今回は前からやろうやろうと思ってたOSバージョンのparseをサポートしました。特にスマートフォン(iOS/Android)のバージョンが気になる人が多いようで、手元でもあったらいいなというケースが多くなってきたため、えいやと入れました。
ただし今回はあまり時間がなかったので、とりあえず自分で使いそうな以下の言語のみリリースしています。
- Java
- Perl
- Ruby
- Javascript (npm)
- PHP (10/28追記 対応されました)
以下の言語についてはまだ対応していません。誰かPull Requestを送るといいと思います。
便利だと思うので、どうぞみなさんお使いください。
10/28追記
PHPも 1.0.0 対応されました。 @okonomi さんによるものです。
https://github.com/woothee/woothee-php/pull/13
Rubyでdynamic scopeを(メソッド定義だけ)実現する dyna_mo を書いた
ついカッとなって書いた。たのしかった。
https://github.com/tagomoris/dyna_mo
どういうことかというと、つまりこういうことだ!
require 'dyna_mo' module MyModule class MyClass attr_accessor :num def initialize; @num = 0; end def name; "name"; end def sum(numbers); @num + numbers.reduce(:+); end end end
こういう割と普通なクラスに対して
dynamo_define('MyModule::MyClass', :mytest_case_default) do def_method(:initialize) do @num = 1 end def_method(:name) do # #name を差し替える "dummyname" end def_instance_method(:name, :mytest_case1) do # #name 差し替えの別パターン "dummyname1" end def_method(:sum) do |numbers| @num + numbers.reduce(:+) + 1 end def_class_method(:create) do |init_num=0| # 元の定義になかった MyClass.create の追加 obj = self.new obj.num = init_num obj end end
特定のとき*1にこのメソッドをこういうふうに上書きしてね!*2 ということを指定して
class SynopsisTest < Test::Unit::TestCase def test_synopsis assert { MyModule::MyClass.new.name == "name" } # 元の状態 obj = MyModule::MyClass.new assert { obj.num == 0 } # インスタンス変数 @num は 0 assert { obj.name == "name" } dynamo_context(:mytest_case_default) do # ここから定義をスイッチ assert { obj.num == 0 } # #initialize is not overridden # これは @num の参照だけで変更なし assert { MyModule::MyClass.new.name == "dummyname" } # ウヒョー! assert { obj.name == "dummyname" } # 生成済みのオブジェクトのメソッドだってもちろん変わる! assert { MyModule::MyClass.new.num == 1 } # initializeだって上書きできる! assert { MyModule::MyClass.new.sum([1,2,3]) == (1+(1+2+3)+1) } # 普通に引数だって与えられる! assert { MyModule::MyClass.create(100).num == 100 } # ちょっとこのテスト専用に便利メソッド! end end
こういう感じで使う! dynamo_context(:mytest_case_default) に与えているブロックの中でだけ MyClass#name や MyClass#sum の動きが違ったり MyClass.create が使えたりする!!!!!!!!!!!!!!!!!!!
コンテキスト外で作った obj のインスタンス変数 @num は変化していないがそれを使うメソッドだけが書き変わっている! ウヒー!
もちろん別のコンテキスト名を指定すれば別の動きかたをする!
class SynopsisTest < Test::Unit::TestCase def test_synopsis dynamo_context(:mytest_case1) do # もちろん違うコンテキストでは違う動きになる! assert { obj.name == "dummyname1" } end dynamo_define(MyModule::MyClass, :onetime_context) do def_method(:name) do "onetime" end end dynamo_context(:onetime_context) do # 違う! 動きに!!! assert { obj.name == "onetime" } end end end
もちろんだがダイナミックスコープなので、直接記述されていなくても、与えられたブロックからの呼び出し階層のどこかに存在するメソッドであれば容赦なく書き変わっている。例えば次のコードは MyClass を継承して MyClass2#name を呼んでいるが、親クラスの宣言は :onetime_context 内では書き変わっているので自動的に MyClass2#name の結果も変わる! カッコイイ!!!!!!
module MyModule; class MyClass2 < MyClass; end; end class Synopsis2Test < Test::Unit::TestCase def test_onece_more dynamo_define(MyModule::MyClass, :onetime_context) do def_method(:name) do "onetime" end end dynamo_context(:onetime_context) do assert { MyModule::MyClass2.new.name == "onetime" } # 継承元の書き換えがここにも影響! end end end
このような感じで自由の地平が開けます。ぜひどうぞ。
FAQ
なんで作ったの?
RubyKaigiの質問コーナーでdynamic scope欲しい!って言ったらMatzに「dynamic scopeを使わないためにRubyを作っている」と答えられたのでムシャクシャしてやった。
なんで作ったの?
power_assert とかちょっと真面目に使ってみたかった。が、まだちょっとバグいね*3。
なんで作ったの?
RubyHirobaでうまい方法ないかなーと思っていたら @_ko1 さんにスレッドローカル変数とModule#prependを使うという方法を授かったのでついやってしまった。責任の半分は @_ko1 さんにあると思う。
本番で使っていい?
絶対にやめましょう。ありとあらゆる不幸が振りかかります。
本番で使っちゃおうかな?
万が一にでも自分が数年後とかに転職した先で見付けた本番コードで使われてたりしたら、たとえ退職者であろうとも書いた人のリアル住所を割って襲撃に行くかもしれません。やめておいた方が賢明です。
ぶっちゃけこれ必要なのって設計が悪いんじゃない?
はい。
例えばマルチスレッドをゴリゴリ使いまくっているのに超大事なところにシングルトンオブジェクトが居座っているFluentdというソフトウェアなんかがあって、そいつがマジでもうどうにもならなかったりするので作りました。しかしFluentd v0.12 ではそのシングルトンオブジェクトが排除されたため不要になりました。おおお……。。。
まあ、しかし、外部gemの動作に依存したコードのテストを書かなければならない時ってあるじゃないですか。ナントカAPIとかナントカSDKみたいなgemを叩きたいんだけどそいつがバックエンドをモックに置き換えられなくなってるとか! あるでしょ!
たのしかった?
はい!!!!!!!!!!!!!! 特異クラスに対してprependとかしてて脳から変な汁が出そうになった!!!
「ZooKeeperによる分散システム管理」を読もう、という話
Zookeeperは現代の分散システムに不可欠なミドルウェアで、メタデータの管理、更新通知、リーダー選出といった問題を解決する。自分でZookeeper APIを叩く人は少ないかもしれないが、今やHadoop Namenode HA*1もYARN ResourceManager HAもHBaseもZookeeperを要求する。とにかく使う必要がある、という人は今や多いのではないだろうか。
こういったソフトウェアが何故必要なのか、どういった役割を持つのかについて、明快な回答を返せる人はあまり多くないのではないだろうか。Zookeeperは必要性を喧伝され、あちこちで使われ、しかしいまだに、何台で動かす必要があるのか、どのように運用されなければならないのか、ということをまとめて学べる資料などは多くない。
ということで、この本を読もう。
「2.2.1 Zookeeperのクォラム」。個人的には、自分にとってはこの一節のみでもこの本には価値が十分にあると断言できる。なぜZookeeperは3台以上の奇数ノードで構成するのが良いのか、3ノード構成においてクォラムを2とするのはなぜか。3ノード構成で1ノードに障害が起きても問題が起きない、としてよいのはなぜか。
読もう!
*1:ZKFC、Zookeeper Failover Controller
LINE選抜で isucon4 予選に参加してPerlのコードをごりごり書いた
みなさんtagomorisはPerlなんて書いてねーだろと思うかもしれませんが、意外にちょっとは書くんですよという話……じゃなくて、それはどうでもよくて、今年も ISUCON の季節ですね、という話。
詳しくはこちらをどうぞ。 LINE選抜で isucon4 予選に参加してきました。暫定スコア「51192」を出すためにやったこと - blog.nomadscafe.jp
では済まないので、自分の視点からの話も。
準備
上述エントリのとおり、準備自体はほぼ去年のISUCON3本戦のものを持ち込む形。結果的にほとんど問題なかったと思う。
設計
10時になってからの開始後、サーバ側のセットアップと初期ベンチ実行および結果確認は@kazeburo、コードのバージョン管理まわり等は@sugyanにおまかせしてひと足はやめに詳細が公開されたレギュレーションとアプリケーションコードを読む側に回った。
その前にアプリの動作確認はもちろんやったんだけど、ログインしかできることがなくて面喰らって運営チャットで確認するなど。
今回も1時間以上はほぼ何も手を入れないで内容の確認。
さて、ということでどうするかの相談の第一のお題は、インメモリDBアプリにしたほうが最終的に高得点になるのが明らかだけどどうしましょうかね、という……。選抜チーム枠をもってることだし、本戦の練習のつもりで本戦で使えるアーキテクチャだけでやりますか、ということにした。まあそれでも予選通過できるくらいの点数は出せるだろう、と。*1
テーブルはふたつしかなくて users と login_log のみ。usersは更新が無いしそんな大した量でもないので全部メモリに読んじゃえばいいんじゃね、ということで、そのうち手が空いたらやることに*2。
問題は login_log で、以下の制約をクリアする必要があった。
- 割と面倒なSQLで状態を毎回計算している
- しかも状態を計算するのに /login と /report で異なるSQLを用いていて、整合性がとれてるかどうかが難しい
- 初期状態である程度のログが存在するので、データストアを変えるならこれを再現する必要がある
- /report を出力するためにはデータストアを変える際の設計に注意が必要
さあどうしようかと相談をして、3人であれこれ検討した結果、以下のように。
- 一度 ban/lock された状態から復活することは無いようだ
- memcached で ip/user ごとにカウンタを持っておいて、そいつをincrすればいいんじゃないかな
- そのカウントが閾値までいったら ban/lock すればいい
- チェックは get だけで済む
- ban/lock されたらincrと同時にその ip/user をファイルに追記しとこう
- そしたら /report はそれを全行読み出して json にして返すだけでOK!
- ただし初期状態での ip/user ごとのカウンタはmemcached上に用意してやる必要がある
これでいけるんじゃないかな、と方針が決まったので、Webアプリ側の改修を@sugyan、初期状態の構築を自分が担当することにしてゴリゴリコードを書きはじめた。
経過
sugyan,tagomorisがコードを書いている間にMySQLにインデックス追加したりnginxの調整をやったりでスコアが一時的にトップに。完全にkazeburoパワーオンリーです。
コードの実装もそれなりにいいペースで進んでたのでそこで気分よくお昼ごはんにした。
Memcachedデータストア版の実装がほぼ終わり、MySQL版のコードにマージする形で両方のデータストアを更新しつつ、参照をちょっとずつMemcachedに寄せていったら、ちらほらとバグが。だいたいはすぐ解決したんだけど /report のチェックでこける問題が長いあいだ解決できなかった。commit log見るとこれに2時間近くかけたらしい。
で、最終的には動くものが揃ったんだけど、これで50000付近のスコアを出した頃には遥かに高いスコアを出しているチームが複数あり、うひー、という状況だったのでした。*3
インメモリDBアプリ
まあこんなもんですかね、というところで落ち着きかけたんだけど、あと30分くらいの時間を手持ち無沙汰に過ごすのもなんだったので、とりあえずやってみるかーとインメモリDBアプリ、シングルプロセス構成のコードに改造してみることにした。
login_log をインメモリにするだけだったのでそう難しいこともなくガッと改造したんだけど、ベンチを走らせてみると通常の構成*4と較べてほとんどスコアが変わらず。高いか低いかは微妙だけど、意味ないなーと思って諦めた。
まあやったのは普通に Kossy でのインメモリDB化なので、生PSGIアプリでゴリゴリ書いたらまた変わったのかも。そんな時間はなかったし、あんまりそこまでやる気もなかった。
ISUCON予選という競技
今回思ったのは、インメモリDB化を試すインセンティブが予選だとありすぎて、これはもう別の競技だなあ、ということ。
ベンチマークツールが同一サーバ上で動いているとどうしてもCPUパワーをベンチかける側と受ける側で食いあうので、最終的にはアプリをCPU1コアで動かし、残りのCPUコア全部をベンチツールに捧げるのが正しい、みたいになってしまう。*5
それはそれで正しいのかもしれないけど、複数サーバを効率的に組合せてハイスコアを叩き出す、という決勝の競技と較べると違いすぎてて、ちょっとなあ、という気持ちが抑えられない。
これはたぶん去年のISUCON3予選問題や今年夏のISUCON夏期講習環境が出回りすぎて、決勝の問題じゃなくて予選の問題の練習をするという方に慣れすぎちゃったんだろうな、という気がする。決勝の問題を環境から準備しようと思うとサーバが5台必要になってしまうので、必然的にそうならざるをえない、のかもしれない。うーん……。
まあ、そういうこと言ってる脇で、fujiwara組がこっちのチームと同程度の得点を叩き出した上でベンチツールの挙動を見越した点数アップを更にやっているので、負け犬の遠吠えかもしれぬ。はい。
あとたぶん、選抜チーム枠を持っていて安穏とした気分で予選に出てるからそういう余計なことを考えてしまうのかもしれなくて、他の参加者からすれば何をトボケたことをと言われることかもしれない。何しろ、いまや決勝出場人数の6倍もの人数が予選を勝ち上がれないんだもんなあ。その人達にとって、ISUCONとは予選のことなのだ。いやはや。
まとめ
何にせよ今回も楽しかったです!
運営の方々はいろいろ苦労があったかと/あるかと思いますが、決勝も非常に楽しみにしてます。予選通過の方々は決勝でお会いしましょう! 我々のチームは出られるのです! HAHAHAHAHAHAHA!*6
RubyKaigi 2014いってきた&LTやってきた
RubyKaigi 2014参加してきた。ここのところ続きに続いたカンファレンス・イベント参加もとりあえずこれでひと段落。
RubyKaigi 2014, 18-20 september
正直にいって疲れが限界に近かったので、あんまり気合いを入れてtalkを聞いたり人と話したり……ということにはならず、おおむねぐったりしてた。ソファあたりとかホールの後ろのほうの席とかで。それでもこれまでなんとなく意識してたけど話したことはなかった人、みたいな人とは数人話したし、まあよかったんじゃないかな。と思う。たぶん。
1日目のパーティーの後に飲みにいったらなんとなくLaurentといっしょになったのでうだうだ英語で話してたのは個人的には超面白かった。世界のあちこちに行ってる人の話はいいなあ。というか、そういう人が大量にやってきてくれるRubyKaigiはすごいな。
Talkの内容はもう @tmm1 のクロージングキーノートが圧巻で、パフォ厨かくあるべしというか、スゲーな、という。あの話、改善したって話もすごいんだけど、*1各種の数値をちゃんととること、それをきちんと可視化してることも本当にすごいと思う。
あと今年は全体的に「Rails以外」にすごい力が入ってるなーというか、HTMとSTMの話が続いたあたりとか、スケジュール構成がすばらしすぎたと思う。なんなんですかねいいぞもっとやれ。*2
なおRails関連セッションはまったく見てないのでよくわからない……。
LTやってきた
YAPC期間中の疲れてぐったりしてる時に「さあLTを応募するんだ!」的なtweetを見掛けて、その衝動のままについうっかり応募したら通ってしまった。ので、やってきた。
話は単純で、v0.x.y なバージョンってなんとなく逃げな気がするからちゃんと v1 以上のバージョン番号つけようぜ! という話でした。Norikra v1を出したあとに考えたことがだいたい詰まってます。
Fluentd はまだAPIを壊す(というかデフォルト動作を変える)余地があるので v0 だし、自分の Fluentd plugin も基本的には Fluentd v1 が出てからあわせて上げようと思っている、ので、そこについてはいいんだけど……実際には自分の他のプロダクトもまだ v0 のままのものが大半で、よくないなーとは思っている。
いくつかは上げるための準備をやろうってことで、RubyKaigi中からぽちぽち始めてた。そのうち出します。
LTそのものは、コードをごりごり書いてる方々からポジティブな評判をいただけたので、ちょっと嬉しかった。しかしUstreamで見直すと英語がもう本当にボロボロだし、かなしい。もうちょっと発音をマトモにする練習しないとかなあ。
さて
次は2ヶ月先の Rubyconf2014 にproposalが 通った ので、初のアメリカ行きになりそう。それまではちょっと発表とかは置いておいてちゃんとコード書く期間にするつもり。
ああ、でもISUCONあるなー。がんばる。
gcp ja night 28いってきた&しゃべってきた
BigQueryの話などがあるということで、自分もお誘いを受けたので適当にしゃべってきた。BigQueryそのものは前のほうで naoya_ito という人がやるらしかったので、Fluentdの話でもしようかなと。
結果的に streaming inserts api の制約についての話などがそこそこ新鮮な人もいたようだった。
聞いた話とかもいろいろ面白かったけど、さすがに人が多過ぎてどういう人が来ているのか把握するのも困難、というのと、BigQueryまわりとDockerまわりの人が混ざってたのかなーという印象。
個人的にはもうちょっと話題を絞った勉強会のほうが好みだな。
なんにしろ @kazunori_279 さん、いつもいつもお疲れさまでした。楽しかったです!