私が歌川です

@utgwkk が書いている

Switchのゲームをプレイしている様子を配信できるとおもしろいはず、と思って、Elgatoのキャプチャーボードを買った。

そこまではよかったのだが、デスクトップマシンにUSB 3.0のポートが生えていないことが分かった。PCIスロットは余っているので増設すればよいのだけど、2016年のマシンを今後も使い倒すかどうか、買い替えたほうが早いこともあるのか? とも思って停滞している。ひとまずゲーム配信はMac経由でやっている。HDMIケーブルを抜き差しするのに加えて、Macを立ち上げる手間もあるのでそろそろ動いたほうがいい気はしつつ難しい。

もう1つ、そもそもディスプレイが足りないという問題もあった。HDMIポートが足りていないし、切替器を買ってみたけどうまく動かなかった。ディスプレイごと買い替えるか、あるいはでかいディスプレイとスタンドを買って環境をガラッと変えるか……。いろいろな変数がある中で検討が加速していない。机の大きさが有限であることを踏まえると、棚を買ったほうがいいのか? とか、そういう話題がどんどん出てくる。

JavaScriptで任意のHTML要素を画像化する取り組みのメモ

表題のことについてちょっと調べていて、だいたい以下のようなアプローチに分類できそう、と思ってきたのでメモです。

SVGの <foreignObject> にHTMLを突っ込む

SVGには <foreignObject> という要素があり、SVG以外の要素を描画することができる。ここにHTMLを丸ごと突っ込んだ上でSVGを画像化する、というアプローチ。

html-to-imageというライブラリがこの方針で実装されている。単にforeignObjectに突っ込むだけだと、画像やフォントがうまく使われないので埋め込む、ということをやっていそう*1

SafariではforeignObjectの制約が強いためうまく動かない、ということがhtml-to-imageのREADMEに書いてある。Safariをターゲットとしないならこのアプローチでうまくいきそう。

レンダラーを再実装する

ブラウザのレンダラーと同様に、任意のDOM要素を画像化する部分を実装してしまおう、というアプローチ。この方法は実行環境によらずうまく動く可能性が高いものの、ブラウザと全く同じ描画結果になる保証はない。

html2canvas は、DOM要素を受け取って独自実装のレンダラーでcanvasに画像として書き出している。

vercel/satoriは、DOMのサブセットとスタイルを受け取ってSVGに変換 (ベクタ画像化) している。

画面キャプチャAPIを使う

developer.mozilla.org

画面キャプチャAPIを使って、ブラウザの画面を映し、それを画像化することができそう? ただし画像キャプチャAPI自体はモバイル端末には対応していない。

既存の画像生成アプリケーションをWASMにコンパイルしてブラウザ上で動かす

ぜんぜん調査できていないけど、たしかにそういう手法もありそう!!

暮らしぶり日記

暮らしの話をちゃんと書けていなかったのでまとめて放出します。

🦑

イカのゲームが発売されたのでやっている。バンカラマッチはそんなに進めてなくて*1、サーモンランだけをプレイしつづけている。協力モードのほうが性に合うような気がしていて、闘争心が弱いのかもしれない。

たつじんまで来ていて、シェケナダムの回以外はうまく立ち回れているように感じる。そもそもステージが難しいのと支給ブキの癖が強いのとが重なっていそう。

ヒーローモードはとりあえず全ステージクリアするところまでやった。

舞鶴・天橋立

サイコロきっぷで舞鶴行きのきっぷを入手したので、天橋立と舞鶴を見てきた。出発が遅かったけど明るいうちに天橋立を見届けて、イメージ通りだな~と思って満足した。

舞鶴はもともと軍港だったようで、ちょっと見渡すと艦これのポスターがよく目に入る。砲雷撃戦をここで開催していたの? 遠くないか?

赤レンガ博物館が、レンガを通じた歴史資料館みたいな趣でおもしろかった。全体的には、車があるほうが便利だったと思う。

何度も東京に行っている

アフターコロナになってからのほうが東京に行っている、という見方もできそうだけど、どちらかというと収入が増えたことのほうが要因として大きいと思う。月に1回以上上京していて、イベントを見たあと、東京の知り合いとよく飲みに行っている。みんな東京に集まっていて、東京に会いに行って話すほうが手っ取り早いという見方もできそう。あとは、追いかけているジャンルの特性上、東京でしかまともにイベントをやっていないことが多い、というのもあろう……。

タイムライン情報をRSS化する取り組み

ブルアカのイベントの特設サイトにニュースが流れつづけている。毎日更新されているようだけど、とくにツイッターなどで告知されている様子もなく、見に行かないと更新されているかどうか分からない。いい情報なのにもったいないと思う。

bluearchive.jp

ちょっと様子を見てみると、ニュース情報を取得するAPIが生えていることが分かった。ここからRSSフィードを生成すれば更新を追いやすくなるはず、ということでGASで作って身内のSlackワークスペースで流しはじめた。とりあえずGASで作ることにすると、どこにデプロイするか、技術選択をどうするか、といった検討を全てすっ飛ばして、とりあえずGASの制約上で動くものを作る力学が働くので高速に実装できる。もうちょっと早いうちに思いついて実装していればよかったと思う。

全て勝手に作っているので、オープンインターネットに公開はしていない。本当は公式でそれらしい情報を流してほしい。

ブルアカをプレイする時間はスプラトゥーン3に吸われているのであった。

*1:ウデマエB+

今日見た夢

引っ越しの手続きを行ったかどうかがわからなくて、メールボックスを検索したけどメールマガジンが多過ぎて埋もれている。マイページを見に行ったら見つからなかったので申し込んでなさそう。

赤ちゃんを抱くときはメニューのインベントリに余裕があると軽くなる、ということを聞く。物を持ったまま判定だと移動速度が落ちるのでそのほうが有利。

#pastak生誕祭 2022でDJした

DJをやっていました。

xn--9myu5gp8s.pastak.net

はじめてのCDJ

さすがに京都からDDJ-400を運搬するのは大変なので、会場にある機材を使わせてもらうことにしました。CDJという機材があって、なんかLANケーブルを使うとパソコンと接続できるらしいぞ?? ぐらいの知識で臨んだところ、初めて触るボタンやツマミが多すぎて何が起こるのか分からずかつてない感じでした。

前日に開催されたIMAP++ #8では本当に初めてCDJを触り、接続方法と音量調整の方法、どこに再生ボタンがあるのか、を音出しのときにレクチャーしてもらったあと、一番手だったのでなめらかに本番が始まってなかなか大変でした。次に流す曲をプレビューするにはどこをどう操作したらいいの? という状態*1でしたが音を途切れさせなかったのでよいということにします。機材に対する解像度が低いのでは??

pastak生誕祭では昨日触ったばっかり、ということでもうちょっとうまくプレイできていたと思います。ツマミを触るよりも身体を揺らしたり腕を振ったりするシーンが多かった印象です。とにかくCDJの操作は勘でやって、オーディエンスを煽るのに注力していました。

セトリ

テーマはいつも通りの「いいね欄」でお送りしました。今回はわりと緩急ある感じだったのではないかと思います。ちゃんとメタデータを整備したほうがいい気がするけど素材の味ということにします。

gyazo.com

  1. Kazmasa ft.yuzuki - Wake Me Up
  2. Stella Drive (FRST. Bootleg)
  3. Transparent
  4. 日常 ED - Zzz [GF - Remix]
  5. フロントメモリーRemix_恋は雨上がりのように 【鈴木瑛美子】
  6. やくしまるえつこ / アンノウンワールドマップ
  7. 相対性理論 - チャイナアドバイス (covered by Toccoyaki feat. somunia)
  8. 【ピューパ!!】 It's too late
  9. ファジータウン (KMNZ × ORESAMA)
  10. ライカ - 長瀬有花 (Official Video)
  11. Natsuzora No Hanabi (value Remix)
  12. 気ままな天使たち (KiraraMagic Remix)
  13. Magical Silkhat
  14. Internet Pajamas Party
  15. 動く、動く (you House Remix)
  16. ミカヅキBIGWAVE - Platinum Groove

ご様子

感想をピックアップしていきます。

また参加したい

にゃんこ酒場の公開収録に始まり、終始いい音を聞きながら身体を揺らして、ポストカードのコンプを狙うという謎の空間になっていておもしろかったです。京都でもやってほしいです(そのほうが近いため)。

blog.pastak.net

*1:ボタンを押したらヘッドホンから聞ける音が変わる

RubyKaigi 2022にオフライン参加した #rubykaigi

RubyKaigi 2022にオフライン参加しました。参加するのは2016年の会*1以来で、これで2回目です。

トーク

トークを聞いたときのメモはScrapboxに残していました。

scrapbox.io

2016年の会に参加したときは全然理解が伴っていなくて、なんだかすごい話が行われていることぐらいは分かるけど……みたいな感じだったのが、もうちょっと噛み砕けるようになっていたと思います。RubyKaigiは、他の参加したことがあるカンファレンスに比べると、言語処理系のディープな話が多いという印象です。最終日のYJITのキーノートはさすがに何度かついて行けなくなったのであとで見返したい……。

mameさんによるTRICKの解説は、どのコードも異常な機能と奇抜な見た目が両立されていて真似できなさすぎる!! と驚嘆するばかりでした。地球を回すコードがリメイクされていて、あのコードベースを改造できるの?? という驚きがありました。

Rubyコミッタが集まって話すトークでは、将来・夢・議論・it がいいのでは? といったさまざまな話が聞けておもしろかったです。

RubyがWASMで動く、というのが一番インパクトがあったかもしれません。Rubyコードに document.getElementById みたいなメソッド呼び出しが出てきておもしろい感じがするけどたしかに動いていてすごいです。

DJイベント

最終日の夜にはDJイベントがあり、大きな音を聞いて身体を揺らしたり、インターネットでしか見ていない人と話したりしました。あと、なぜか盆踊りを踊っていました。

pixiv.connpass.com

飲みまくっていたら最後の方の記憶がないけど、3000円払った記録がマネーフォワードに残っていておもしろかったです。帰巣本能が強い気がします。

ネットワーク機器の後片付け

最終日の夜に、人手があると助かる、ということでネットワーク機器の後片付けに参加していました。ちょっと雑用をやるぐらいかな? と思っていたけど、案外やることがあって、夕方までかかってなんとか箱詰めができました。ねじりっこの切り方を知ることができたのでよかったです。

当日の連絡用にSlackコネクトでチャンネルに招待してもらったのですが、変なdisplay name*2を使うと目立った感じになってしまうので気をつけてください。

gyazo.com

感想

自分は普段Rubyを書いていない*3のですが、Rubyコミュニティの知り合いが周りにいるとか、久しぶりのオフラインカンファレンスなので行きたい、ということで参加していました。改めて、Rubyという言語が愛されているということが強く伝わってきました。どのトークも興味深く、偉業ともいえる高速化を成し遂げていてすごかったです。

会場までのバスが用意されていてなめらかに来場できたり、お弁当があって昼食に困らなかったりして、大変よかったです。地方で開催されるイベントだと交通の便やレストランの様子などが読みづらいので、イベント側で用意してもらえるのは助かります。

楽しい企画や便利グッズなども用意されていて、久しぶりにオフラインカンファレンスを楽しめたような気がします。運営の皆様、スポンサー企業の皆様、トークをされた皆様、参加者の皆様ありがとうございました。来年は松本で開催されるようなのでそれも行きたいですね。

*1:京都で開催された https://rubykaigi.org/2016/

*2:KMC Slackのdisplay nameがこうなっている

*3:最近はTypeScriptを書くことが多い

ISUCON12 チーム :old_noto_innocent: で本選に出て7位だった #isucon

最終スコアは167,491点で、7位でした。あともう1手ぐらい打てれば上位入賞できたかもしれません。

本選の問題

今回の本選の問題は、育成型放置ゲーム「ISU CONQUEST」の高速化でした。ブラウザから動作確認するとUnity製のゲームが動いていておもしろかったです。

www.youtube.com

最終的なサーバー構成

今回の本選ではサーバーが5台与えられました。

  • 1
    • App (Go)
    • nginx
  • 2
    • DB (MySQL, users.id % 3 == 0 用)
  • 3
    • DB (MySQL, users.id % 3 == 1 用)
  • 4
    • DB (MySQL, users.id % 3 == 2 用)
  • 5
    • App (Go)

やったこと

リポジトリはこちらです。

github.com

分析グッズを導入する (10:45)

予選のときと同じ分析基盤を使うためにアプリケーションコードを直してまわりました。具体的には context.Context を引き回すなどです。ひと通り導入できるまで時間がかかってしまっているのは今後の課題ですね。

ここでプロファイラやトレーシングの様子を見ると、明らかにDBがボトルネックになっており、とくに POST /loginPOST /user が重たすぎる、ということが分かりました。まずはそれらのエンドポイントから改善していくことにしました。

プレゼント付与・ログインボーナスまわりのN+1クエリを解消する (11:26)

プレゼント付与処理 (Handler.obtainPresent 関数) やログインボーナス受け取り処理 (obtainLoginBonus 関数) でN+1クエリが発行されていたので解消しました。受け取り済かどうかの判定をテーブルからSELECTしてきてあとで使うぐらいなので、ここはサクッとできました。

プレゼント付与時にbulk insertする (11:53)

N+1を解きほぐしたあとはbulk insertするようにしました。INSERTするものがない (スライスの長さが0) ときに NamedExecContext を呼び出すとエラーになるので気をつけましょう (1敗)。

このあたりで10749点を記録しました。

user_present_all_received_history テーブルに複合インデックスを貼る (12:05)

プレゼント付与時の以下のクエリが重たく、EXPLAINしたらインデックスが足りなかったので足しました。

SELECT * FROM user_present_all_received_history WHERE user_id=? AND present_all_id IN (?)

このあたりで20264点を記録しました。インデックスを足すだけで1万点ぐらい上がってて景気がいいですね。

アイテム付与処理を解きほぐしながらクエリ発行回数を減らす (13:37)

アイテム付与処理は、付与対象のアイテムの item_type カラムの値で分岐しつつ、別々のテーブルにクエリを都度発行するという実装になっていました。ここを解きほぐさないと先に進めなさそうでしたが、見るからに手をつけたくない雰囲気をかもし出していました。まあ、手をつけないといけないんですが……。

以下のような作戦でロジックの解きほぐしとクエリの効率化を進めました。

  • アイテム付与処理を管理するstructを作る (ItemObtainer struct)
  • ループ内のアイテム付与関数の呼び出しを置き換える*1
    • 同じシグネチャで呼べるようにする
    • 付与するアイテムのリストを溜めておく
  • ループを抜けたときに一括でクエリを発行できるようにする*2

元々の関数と同じような返り値を保持するならスライスの順序を保持する必要があるのか? と思いましたが、よく見ると返り値を捨てていたので気にする必要はありませんでした。

このあたりで43862点を記録していました。

DSNに interpolateParams=true を足す (13:50)

SQLのプレースホルダ置換を有効にするアレです。スコアが10000点ぐらい上がってウケました。

dsas.blog.klab.org

このあたりで54594点を記録していました。プレースホルダ置換は、ISUCONにおいては常に有効にすることにして初手で入れてしまってもいいのかもしれません。

users.id ごとにDBを分割する (15:28)

MySQLのCPU使用率がずっと高いままだったので、ユーザーデータごとにDBを分割するようにしました。

このあたりで119577点を記録していました。

アプリケーションを分割する (15:44)

アプリケーションのCPU使用率も上がりつつあったので、残っていたサーバーにアプリケーションを分割するようにしました。採番ロジックをアプリケーション側に寄せていたので、サーバーの番号をIDに埋め込めるように書き換えました。

このあたりで134670点を記録していました。

セッションキーに users.id の値を含める (17:23)

確率的にベンチマークの最初の整合性チェックが落ちる、という現象に頭を悩まされていました。たまにベンチマークが通るので、最初は気にしていなかったのですが、どんどん顕在するようになって焦ります。採番ロジックのバグや、ベンチマーカーのバグを疑いましたが、どうもそれらは違いそうです。

実は、先述したDB分割のさいにバグを埋め込んでいました。セッションキーを取得するときに、セッションキーを発行した先のユーザー用のDBを見ることができておらず、ベンチマーカーが意図したレスポンスを返せずに整合性チェックに通らなくなっていました。原因になかなか気づけなかったのですが、id:nonylene さんがトレーシングの様子と見比べながら原因を突き止めてくれました。今回のMVPだと思います。

最初は users.id ではなくセッションキーの頭文字に応じてDBを振り分ければよいかと思いましたが、セッションキーを再発行する処理でうまくいかない (セッションキーを全て無効化できない) ため、発行するセッションキーに users.id の値を含めてなんとかしました。競技終了が見えてきたタイミングでしたがなんとか修正できたのでよかったです。しかしヒヤヒヤした……。

分析グッズを剥がし、ログ出力を止める (~競技終了)

これ以上破壊的変更を加える余裕はなさそう、ということで分析グッズを無効化したり、echoのアクセスログを止めたりしました。それと並行して、ガチャのマスターデータのキャッシュ実装が途中までできていたので、バグを直しつつ投入して何度かベンチマークを走らせてみましたが (「これ以上破壊的変更を加える余裕はなさそう」とは何だったのか??)、18万点ぐらい出たものの再起動試験に通らないことが分かったので導入を断念しました。

感想

ISUCONは2016年 (ISUCON 6のとき) から毎年参加していて、本選に出るのはこれで4回目でした。それなりに手は打てて、順位表の上から数えるほうが早いぐらいのスコアを残せるようになったのはよかったと思います。アプリケーションコードとトレーシングやプロファイラの出力を見ながら作戦を考えて (考えてもらい)、順に試す、というサイクルがうまく回っているのだと思います。今回は失格/failになったチームが多かったようで、我々もセッションキーの不具合に気づかないままだったら……と思うとヒヤヒヤします。

一方で、上位入賞を狙うにはもうちょっと手数や実装の精密さが足りていなかったとも思いました。とくに今回はDB分割まわりで競技終了ギリギリまで悩むことになるバグを埋め込んでしまったのが悔やまれます。以下はもうちょっと余裕があれば導入できたかもしれない作戦です。

  • プレゼント配布時にbulk updateする
  • DB接続まわりのパラメータチューニング
  • ガチャまわりのN+1クエリを解消する
  • マスターデータをキャッシュしまくる

キャッシュ以外は感想戦で試してみているけど、20万点は超えたものの30万点には届かないので更なるブレイクスルーが必要そう……。このあたりは上位入賞チームの作戦を聞いてみたいです。

今回も予選・本選ともにおもしろい題材の問題で、競技に取り組んでいてすごく楽しかったです。運営の皆様ありがとうございました。次こそは優勝したいですね。