株式会社はてなに入社しました
1年ぶりです
終わった……。というのが率直な気持ち。 怪我や事故などの大きなトラブルなく終わることができて本当にホッとしている。
2023年度は仕事も忙しく、かつプライベートも色々あり、とにかく様々が立て続けにあってずっと頭がパンクしていたように思う。 ほかにも様々な状況などが重なった結果、今回はあまりまともにYAPCを手伝うことができなかった。
できたのが杜甫々(とほほ)さんとそーだいさんへの講演オファーをするくらいで、あとは多少の相談に乗ったりくらいで運営としての事前準備は今回は殆ど関われていない。 それでもこれだけ多くの人がYAPCを楽しんでもらえたのは、これを準備してきたコアスタッフの面々と他のJPA理事の面々のおかげだとおもう。本当にありがたい。
「段取り八分、仕事二分」とは言うが、その「二分」の部分だけでも貢献しようと当日はできる限りのことをした。
その結果、当日は様々に気を配り目を配りとしていたところあまり余裕がなく、そーだいさんのトークを聞きにいくことは出来なかったが、 杜甫々(とほほ)さんのキーノートだけはなんとか途中から直接聞くことができた。
これが、本当に面白かった。
他の方も書いていたけど、仕事の合間にやったとさらっと言っているその内容が本当に凄かった。 特に印象に残ったWin32 APIのインターフェースをLinux上に構築する話はWineの一部を自作しているようなもので、 仮にやろうと思ってもそう簡単にできるものではないと思うしこういうことをさらっと「やった」と言えてしまうのはかっこいいと思った。
他にも様々なエピソードを聞くことができた。
そして、最後の質疑応答の答えにあった「好きだから」という言葉は、それまでの流れもあって「『好きだから』ここまでできるんだよ」「『好きだから』続けると色々なものを積み上げられるかもしれないよ」というメッセージとしても自分には響いた。
YAPCという場にお招きできて、自らの言葉でこういったことを語ってもらえたのは自分にとってはもちろん、YAPC参加者もとい日本のPerlコミュニティ*1にとって大きな価値になったと思う。 もちろん、このブログとは別途、杜甫々さん本人にも直接お礼を伝えた。
まだ後始末は終わっていないし次のYAPCの実現に向けて動き出している部分もあり、 目まぐるしいなという感じではあるが無理なくできる範囲で今後もやっていこうと思う。
JPA*2ではYAPCを手伝いたい。自分がYAPCを作りたい。自分こそがYAPCだ。 そんな人々のちからを借りてYAPC::Japanの開催を実現しています。
少しでも興味が湧いた方がいらっしゃったら、自分のTwitter DMやJPAのメール窓口 info@perlassociation.org
までぜひお気軽にご連絡ください。
*1:Perlを知ってる人も知らない人もYAPCに参加して楽しめるひとは仲間じゃんPerlコミュニティじゃんっていう感じ
*2:Japan Perl Association YAPCの主催組織
この記事はPerl Advent Calendar 15日目の記事です。
様々からの現実逃避とISUCONに向けたPerlのリハビリでmustache template engineをちまちま自作してたんだけどついにmustache/specなどから作ったテストが全部通ってしまった
— かるぱねるら (@karupanerura) December 12, 2023
ということで、ひさしぶりにCPANizeしました。なんか早速bug fixが見つかって早々に0.02です。
今のPCでは初めてのCPANizeだったようで ~/.pause
がなくてちょっと焦った。
人生色々。様々があります。 たとえば、やることに追われたり、悩ましい考え事に苛まれたり、やることに追われたり、やることに追われたり、様々があります。
一方でISUCONも迫っていました。PerlでISUCONに勝ちたいので日頃の仕事でなかなか書く機会が減ってしまったPerlを素振りしたくなってきます。 気分転換がてら、Perlでちまちまなんかを作ってみるのは良いかもしれないと思いました。
そんななか、趣味でプログラミングするのにちょうど良さそうな題材として、Mustache Templateが出てきました。
Template::Mustacheが1.0系になってめっちゃ遅くなったことに困っていそうな人をみかけたのです。 確かにめちゃくちゃ遅くなったのは知っていて、たしかに調べてみても代替モジュールによさげなのがなかったのです。
まあ簡単そうだしちょうどよいだろう。ミニマルなものを作っていい感じにしよう。と実装しながらMustache Templateの仕様を追っていきます。
すると、なんとも色々な仕様がでてくるではありませんか。
lambdaとか継承テンプレートとかmustacheで1回も使ったことないぞ!などとと思いましたが、それは自分はText::XslateやText::MicroTemplateを使っていたのでまあそれはそう。
まあ必要なひともいるんだろう、そうなるとこれもたぶんほしいんだろう、これを実装するならこれもまあ実装されていて然るべきだろう、などとうっかり色々実装していくと、あれよあれよと芋づる式に全部の仕様を実装してしまいました。
なんで????
Lexerでトークンに分割して、ParserでAST*1を作り、Compilerでコンパイルするという王道な実装となっております。
Lexerといってもそんなに丁寧なものではなくて、素朴なLexerとしてはたとえば "foo{{bar}}"
があったときに、これを ["foo", "{{", "bar", "}}"]
と分割するのが一般的かと思います。
もうちょっとParser寄りな感じで、["foo", "{{bar}}"]
という感じでタグの構文そのものだけ解釈してトークン分割をする実装にしました。
これにはちょっと理由があって、Mustacheって{{
と}}
というデリミタでタグを構成するのがデフォルトなのですが、このデリミタを変更するタグが存在するので、Lexerはそれを解釈しないと正しくトークン分割ができないのです。すると、まあそこまでしてるのに {{
と bar
と }}
を分ける必要もなかろうということで自ずとそれらを一緒のトークンにまとめて扱うことにしました。
そして、Parserはその一列のトークンを解釈してMustache Templateとしての文法の構造をデータ構造上の構造に構造化します。いわゆるASTというやつですね。
たとえば、Mustacheには{{#condition}}yes{{/condition}}
みたいな複数のタグを使って1つの構造を作るみたいな文法をもっているので、これはAST上もこの分岐の子要素みたいな感じで整理してあげます。
ASTを使うとめっちゃ簡単にレンダリング処理が実装できます。 素朴にレンダリングする事もできると思いますが、今回はせっかくなのでキャッシュしたテンプレートの実行効率を高めるべくCompilerとして実装しようと思います。
Compilerってなんやねんとなると思いますが、これはText::MicroTemplateよろしくテンプレートに合わせたパターンの文字列を生成するPerlのコードを文字列で作り上げてevalすることで、事前に分岐などが最適化されたコードを作る操作を指しています。
Perlコードの生成時はASTを解釈して細かい分岐をたくさん辿りますが、生成されたPerlコードはある程度の分岐が事前に解決されたものになるので直にASTを毎度解釈するより高速に実行することができるという寸法です。
Apache::LogFormat::Compilerなど様々なCPANモジュールでこのアプローチは取られています。 なんなら、こういうコードを簡単に作るためのSub::Quoteってやつまであり、これはMooの実装で使われていたりもします。
ほか、render
というテンプレートをパースして即レンダリングするというインターフェースもあるんですが、このケースではコンパイルしたものが1度実行したら廃棄される前提がありかつコンテキスト変数*2がコンパイル前にすでに決定しているという特徴があるので、コンテキスト変数をヒントにASTを最適化することが可能です。
ということで、そういう処理を挟んであり、うまくいくケースではこの最適化をやらない場合と比べて3倍くらい速くなるようになることがわかっています。
ほかにも、正規表現を最適化していたりなど、地味な高速化を測っている箇所がちょいちょいあったりします。
このテストケースをすべてPASSしています。やったね。 ついでにテストカバレッジもステートメントレベルでは100%*3で、ほかも90%台後半まで網羅してあります。
パフォーマンスも簡単なテンプレートをレンダリングするベンチマークで比較してみました。 Template::Mustacheに比べてパースの速度が10倍、レンダリングする速度がパース済のテンプレートをキャッシュしているケースで3倍、キャッシュしていないケースで40倍となることがわかっています。 やったね。
なお、Mustache::Simpleというモジュールがあるのですが、これはテストケースをpassしなかったので比較していません。(存在しない変数を無視せずにエラーにしてくる仕様非準拠な挙動だけ確認してあとは確認してない)
ということで、まあそれなりに実用に耐える品質ではあろうと思ったのでCPANnizeしておきました。
ISUCONの素振りってまとまった時間が必要じゃないですか……。 これ細切れの微妙な時間でちまちま進めたのです。素振りもしたかった……。
まあ、完成したのでもしご入用の方はお試し下さい。
この記事はPerl Advent Calendar 14日目の記事です。
チーム「銀河鉄道の昼」としてISUCON13にPerlで挑みました。 最終的に2台構成で最終スコア7083、最高スコアは1台構成での9381でした。
上位30チームが50000点弱以上なので、残念ながら惨敗と言って差し支えない結果です。 くやしい……。上位チームの皆さんおめでとうございます。
なんやかんや、足回りを整えたりトラブルシューティングしたりで2時間くらい食ってたきがします。
順位を計算する処理があり、その順位とはtipの総額とリアクションの総数によって順位付けられていました。 この際のもとの実装は以下のような感じです。
# ランク算出 my $ranking = []; for my $livestream ($livestreams->@*) { my $reactions = $app->dbh->select_one( q[ SELECT COUNT(*) FROM livestreams l INNER JOIN reactions r ON r.livestream_id = l.id WHERE l.id = ? ], $livestream->id ); my $total_tips = $app->dbh->select_one( q[ SELECT IFNULL(SUM(l2.tip), 0) FROM livestreams l INNER JOIN livecomments l2 ON l2.livestream_id = l.id WHERE l.id = ? ], $livestream->id ); my $score = $reactions + $total_tips; push $ranking->@* => Isupipe::Entity::LivestreamRankingEntry->new( livestream_id => $livestream->id, score => $score, ); } my @sorted_ranking = sort { if ($a->score == $b->score) { $a->livestream_id <=> $b->livestream_id; } else { $a->score <=> $b->score; } } $ranking->@*; $ranking = \@sorted_ranking; my $rank = 1; for (my $i = scalar $ranking->@* - 1; $i >= 0; $i--) { my $entry = $ranking->[$i]; if ($entry->livestream_id == $livestream_id) { last; } $rank++; }
うーん、では中間テーブルを作りましょう。
RedisのSortedSetを使うことも考えましたが、scoreが同順であった場合はlivestream_idの昇順となるように実装されているためそのへんをうまくやれるだけのRedis力が僕にはなかったです。 (出来ないと思っていた。実はできるという話を聞いたけどどうやってやるのか知らないので知っているひとはコメントで教えてください。)
中間テーブルをこんな感じで作って
-- スコア CREATE TABLE `user_scores` ( `user_id` BIGINT NOT NULL PRIMARY KEY, `score` BIGINT NOT NULL, `user_name` VARCHAR(255) NOT NULL, INDEX ranking_idx (`score` DESC, `user_name` DESC) ) ENGINE=InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_bin; CREATE TABLE `livestream_scores` ( `livestream_id` BIGINT NOT NULL PRIMARY KEY, `score` BIGINT NOT NULL, INDEX ranking_idx (`score` DESC, `livestream_id` DESC) ) ENGINE=InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_bin;
更新時によしなに更新していくと、以下のような感じで順位がSQLだけで取得できるようになりました。
$app->dbh->query('SET @r = 0'); my $rank = $app->dbh->select_one(q!SELECT `rank` FROM (SELECT livestream_id, score, @r := @r+1 AS `rank` FROM livestream_scores ORDER BY score DESC, livestream_id DESC) a WHERE a.livestream_id = ?!, $livestream_id);
Window関数を使って DENSE_RANK()
でなんとかする手もあるかもでしたが、うまくインデックスが効かないのでこういう感じにしました。
たしかまあまあスコアは上がった気がします。 しかし、なんやかんやハマって2~3時間くらいこれで溶かしてしまった気がします。
元々の実装はこんな感じでした。
# 予約枠をみて、予約が可能か調べる # NOTE: 並列な予約のoverbooking防止にFOR UPDATEが必要 my $slots = $app->dbh->select_all_as( 'Isupipe::Entity::ReservationSlot', 'SELECT * FROM reservation_slots WHERE start_at >= ? AND end_at <= ? FOR UPDATE', $params->{start_at}, $params->{end_at}, ); for my $slot ($slots->@*) { my $count = $app->dbh->select_one( 'SELECT slot FROM reservation_slots WHERE start_at = ? AND end_at = ?', $slot->start_at, $slot->end_at, ); infof('%d ~ %d予約枠の残数 = %d', $slot->start_at, $slot->end_at, $slot->slot); if ($count < 1) { $c->halt(HTTP_BAD_REQUEST, sprintf("予約期間 %d ~ %dに対して、予約区間 %d ~ %dが予約できません", TERM_START_AT, TERM_END_AT, $params->{start_at}, $params->{end_at})); } }
この手のはロックする範囲をいかに絞れるかがキモっぽいという直感と、幸いにもslotは減ることはあっても増えることがない仕様であることがわかったため、 すでに空になっている予約枠を予め除外してロックを取ることでロックの競合をなるべく抑えるようにしてみました。
# 予約枠をみて、予約が可能か調べる my %slots = @{ $app->dbh->selectcol_arrayref( 'SELECT id, slot FROM reservation_slots WHERE start_at >= ? AND end_at <= ?', { Columns => [ 1, 2 ] }, $params->{start_at}, $params->{end_at}, ) }; if (any { $_ == 0 } values %slots) { $c->halt(HTTP_BAD_REQUEST, sprintf("予約期間 %d ~ %dに対して、予約区間 %d ~ %dが予約できません", TERM_START_AT, TERM_END_AT, $params->{start_at}, $params->{end_at})); } my $txn = $app->dbh->txn_scope; # NOTE: 並列な予約のoverbooking防止にFOR UPDATEが必要 my $overed_slots = $app->dbh->select_one('SELECT COUNT(*) FROM reservation_slots WHERE id IN (?) AND slot = 0 FOR UPDATE', [keys %slots]); if ($overed_slots) { $c->halt(HTTP_BAD_REQUEST, sprintf("予約期間 %d ~ %dに対して、予約区間 %d ~ %dが予約できません", TERM_START_AT, TERM_END_AT, $params->{start_at}, $params->{end_at})); }
これも、たしかまあまあスコアは上がった気がします。
MySQLを2台目に追い出したのですが、どうやらその途端にDNS Serverのスループットが改善してDNS水責め攻撃の餌食となったようです。 2台構成が動いたのが終了10分前とかだったのでそこから元に戻すのは諦めました……。
今回は素振りがほとんど出来なかったので素振りする時間をなんとか確保して、トラブルシューティングに費やす時間を減らし、チューニングの手札を増やしておきたいです。 今回はnginxでキャッシュさせるといったこともほとんど出来ず、またnginx luaを使えば楽にいきそうな部分もあったのでそういったこともできるようにしていきたい。
次回もPerlで挑む覚悟で、実装移植がなかったら自前でいちからコードを書いてでもPerlで戦おうと思います。
移植はモダンなPerlの機能がふんだんに使われており、面白い実装でした。(Entityクラスは正直いらなかった気がしますが……。) 問題もボリューム満点で面白く、素朴なチューニングでスコアがちゃんと上がるような問題に仕上がっていてスコアが上がる楽しさも感じました。 運営の皆さんには感謝の気持ちでいっぱいです。ありがとうございました。
まずは公式ドキュメントをご覧ください。
IPv6 is only supported on Docker daemons running on Linux hosts.
残念!
Docker Desktop for Macなどでローカル開発をしているときに、ローカルで立ち上げたプロセスからDocker内にあるコンテナに通信したいことは割りとよくあるユースケースだと思う。
こういうときは、基本的には宛先をIPv4のLoopback Addressである127.0.0.1
に向けてあげて、 IPv6を使わないようにしてあげるとよい。
localhost
を使ってしまうと、名前解決でIPv6のLoopback Addressに名前解決されるケースがあり、そうなればIPv6に対して接続しようとしてしかしIPv4でしかlisten(2)
されていないのでコケる。
しかし、世の中には親切(?)にも127.0.0.1
をlocalhost
に正規化するようなコードが存在する。*1
困る。
ローカルの問題なので/etc/hosts
を手で書き換えてlocalhost
からIPv6に解決されないようにすればOK!という感じではあるのだが、たとえばチームで開発する基盤としてのローカル開発環境だとしてそれをチームメンバーに強いるにはトラブルの元になりそうでちょっともにょる。
なにより、プロジェクトローカルな事情で環境の設定を変えてしまうのは目的に対してtoo muchな策であり、副作用が大きくあんまり良い解決策とはいえないだろう。
では、nginxのTCP Reverse Proxy Server機能をつかってIPv6で待ち受けたやつをIPv4にProxyしてあげればよかろう。 と思いきや、nginxはDockerで建てるには楽だがローカルで建てるにはだるい!
ということで、Perlさえあればどこでも動く、簡易なTCP Reverse Proxy Serverを書きました。 PerlはだいたいのOSに最初から入ってるのでお手軽に使えるし、コピーしてプロジェクトのリポジトリに同梱しちゃえば各環境でのインストールも不要。お手軽!
簡易、というのは、素直なTCP Reverse Proxyとは異なりTCPパケットレベルでProxyしているわけではなくて、いったんTCPのペイロードを受け取ってからそれをまたTCPで流すということをやっているため。
たとえば、ACKが返ってきたからといって、それがReverse Proxyされた先でもACKされているとは限らないので、厳密にはL4 Proxyと言ってはいけない代物ではある。が、おそらく実用上はそんなに問題はないでしょう。というか、SOCK_RAWで頑張るには目的に対して大変すぎる……。
ニッチなケースで役に立つと思うのでお試しください。
もともと、これをIO::Socket::IPで書いて、IO::Socket::INETと違ってIO::Socket::IPではIPv4もIPv6も使えて便利!というLTをShonan.pm #1でやったのですが、実際にはIO::Socket::IPでpeeraddrやsockaddrなどをうまく取得できず(undefになる)、あきらめて標準関数とHash::Util::FieldHashを使って実装しなおしたという微妙な経緯があります。(そんなわけで。Shonan.pm #1のLT資料は動くでしょうって前提の間違いが微妙に散りばめられており、公開しておりません。あしからず。)
2つの課題があって、ひとつはncではこれと同等のことを実現することは実はあんまり楽ではないこと。
たとえば、proxy modeを使うにしても送信側のproxy対応が必要になって(アプリケーションが小さければ楽かもしれないが少なくとも自分の関わっていたプロダクトでは対応箇所がどうしても多くなってしまって)面倒で、具体的にはhttpではなくgrpcなやつとかもいたりするから単にHTTP_PROXY環境変数を入れておしまいとはならないからsocks proxyを使わねばならず、まあ面倒。
nc -l
とかを繋げたワンライナーっぽいので素朴にやることも考えられるが、複数のリクエストをそれも並行して扱えないとあんまり意味がない場面(まあまああると思う)ではまともにつかえないのでこれもやはり無し。
もうひとつは移植性の問題があるのでスクリプトみたいな形にまとめて使いづらいこと。
*1:厳密にはそうではないかもしれないが、外形的な挙動としてはそのような挙動に見えるケースに当たったことがある。
株式会社はてなに入社しました
3年前、2020年の2月にこんな記事を書きました。
まだその脅威の全貌が明らかにならないままに刻一刻とタイムリミットは迫り、 YAPC::Kyoto 2020のために積み上げてきたものとそれをその状況下で実施することのリスクを直視して、頭を抱えました。
自分たちの本業は当然ながらイベントの運営ではありません。 その一方で、イベントの開催には様々な準備が必要になってくるため、それなりの労力と時間がかかります。 それを誰もが持て余しているわけがないように、スタッフも暇だからそれをやっているわけではないのです。
では、それをどこから捻出するかというと、趣味や大切な家族との時間を削ったり、睡眠時間と体力を削ったり、業務調整をして同僚に助けてもらうなど、人によってその程度の差はあれど全く何も犠牲にしていないという人は少ないでしょう。 そういった、掛け替えのない時間をもらうことによってYAPC::Kyoto 2020の準備は進められてきたことを、自分はよく理解していました。
イベントは一点物です。 イベントの準備のほとんどは「その日」のためにしか使えないものであり、つまりイベントを中止するという判断はそのために準備したもののほとんどを無意味なものにしてしまうことを意味します。 例を挙げると、イベントの内容は会場や日程に大きく左右されます。必ずしも同じスペースが同じ条件で借りられるとは限りませんし、どこでどのようなことをやろうとしていたかという構想も簡単に崩れてしまいます。ゲストとして呼ぶ人々の都合がつくかもわかりません。
和気あいあいと、こんなことが出来たら面白そうだ、こんなことができるとみんな嬉しいのではないか、という具合で盛り上がっていた運営メンバーの様子を思い浮かべながら、 中止を考えざるを得ないこの変化にどのように対処するか、中止以外になにか打つ手はないか、現状でベストな打ち手はなにか、といったことをひたすら考えていました。
そして、2時間近い話し合いをオンラインで行って結論を出し、以下のようなブログエントリを書きました。
あえて「開催見送り」「延期」という言葉を使い、「中止」とは書きませんでした。
イベントの延期は、それを言うこと自体は簡単ですが、それを実際に行うことは簡単ではありません。 その準備のほとんどが無駄になったことにもう一度前向きに取り組むためには意思が必要です。 そして、延期を提案するということは「いままでと同じくらいの労力と時間をもう一度ください」と言うことに等しいので、延期したいということへの共感が必要です。
しかしながら、イベントのスタッフをやった報酬は、イベントを開催してそこに参加したり関わった方々からの感謝、それだけです。 そして、これは開催することで初めてそれを得ることができます。まだそれがない状況で、延期としましょうと言ったところでそれは本当にできるのでしょうか? 最初はよくても、燃え尽きる、折れる、といった形容をされるような状態になってしまっても不思議ではないと思います。
とりあえず言っておけば良いという意見もあるかもしれませんが、もし期待値を高めてそれを裏切るという行為になってしまうと、信頼を毀損してしまいます。 自分自身のことであればそれでもよいかもしれません(よくはない)が、YAPCという世界中で何代にも渡って主宰が交代してきたカンファレンスは自分だけが築き上げたものではない、これまでYAPCをつくりあげてきた人々から受け継いだ大切な財産です。
「延期」と言うからにはたとえ誰も手伝ってくれなくてももう一度やる覚悟を持つ、そういう気持ちが自分にとっては必要でした。 YAPC::Kyoto 2020のメンバーは延期に対して前向きに向き合ってくれて、延期と決めた話し合いでも「絶対やるぞ!」となって終わるくらいに前向きでいてくれたので、それに背中を押してもらったことであまり迷わずに延期と言うことができました。
そうなれば、コロナ禍でイベントがやりづらくなっても、なんとかYAPCを開催するための土台を維持したいとなります。 コミュニティは生き物であり、集まるきっかけが失われると死んでしまいます。
オンラインでそれをどうするか、悩んだ末にJapan.pm 2021、YAPC::Japan::Online 2022というイベントをやりました。 このあたり葛藤とそこからどうしたかはイベント振り返り Meetup #2というイベントでも話したとおりです。
そして、結果的にはこのようにYAPC::Kyoto 2023として復活を果たしました。 いろいろと不安を書いていましたが、実際はどうだったか。
なんと、YAPC::Kyoto 2020を手伝ってくれていたほとんどのスタッフが引き続き手伝ってくれました。そうでない方も3年のあいだに状況が変わって手伝えなくなってしまった、当日の予定が合わなくなってしまった。といった方ばかりで、自分の知る限りでは100%のメンバーが延期開催の実現に引き続き前向きでいてくれました。 先に書いたような不安は幸いにも杞憂に終わり、id:azumakuniyuki さん id:papix さんを中心に多くのメンバーが高いモチベーションでまた新しいカンファレンスを作ることに尽力してくれたのです。
結果的に、(ブランクが空いたことなどを加味しても)残念ながらトラブルは少なくはありませんでしたが、 それを踏まえても「とても良かった」と参加した人々に言ってもらえるような、多くの人の心に残るカンファレンスとなったのではないかと思います。 ぼくも、とても良いカンファレンスになったのではないかと思います。
この彼らの前向きさとこの結果に力をもらって、自分たちにとって掛け替えのない場をこれからも続けるために、自分にできることを引き続きやっていきたいと僕も心を新たにすることができました。 そんなわけで、YAPC::Kyoto 2023を作り上げてくれたメンバーに僕は感謝をするばかりです。本当にありがとうございました。
いろいろと思い出して感慨深いものがありました。という話でした。
YAPC::Kyoto 2023ではぼくはスタッフ業として溢れたタスクを拾ったり、あるいは #ぶつかり稽古 のリバイバルイベントの司会進行兼実況解説役やLTもやりましたが、その話はまた別で書こうかと思います。