時計を壊せ

駆け出してからそこそこ経ったWebプログラマーの雑記

UNIVERGE IX2105で宅内ネットワークを構築した

とある分譲マンションを購入して入居したところ、棟一括契約のインターネット回線以外は引けないようで、それを使わざるを得なかった。 宅内への配線は1000BASE-Tが1本あり、宅内に設置されたスイッチングハブを通じて各部屋へ配線されている。 ネットワーク的には少なくとも他住居のネットワークへルーティングされることはないようだった。 速度も実測で下り600~700Mbpsが出ていて概ね問題がない。ということで普通に使うぶんにはほとんど不満はなかった。

唯一心配なのがマンション側のネットワーク構成がブラックボックスであることで、まあおそらくそんなに問題ない構成にはしているのだろうとは思いつつコントロールの範囲外で安全性が保たれていることにはちょっとだけ不安がある。 あと、NASを使うにあたってDHCPでNASのMACアドレスに対してIPを固定アサインしたい(でないと使いづらい)のだが、ルーターは家の外なので権限があろうはずもなく、まあIPが変わる機会もそうそうないはずではあるのだがこれもまたちょっと不安。 ということで、明確にネットワーク的に宅内と宅外を分離しつつ外からのパケットを一定ブロックしたい。ということで家の中の最前段にルーターを設置したくなった。

なお、それまではどうしていたのかというと、光回線やCATVなどの終端装置にAterm WX3000HPを置いていた。 しかし、引っ越しに伴って家のレイアウトが変わったことでアクセスポイントを置きたい位置と配線の来る位置が変わったので、これは単なるアクセスポイント兼スイッチングハブにしてルーターは別途調達しようという考えになった。

社内で雑に相談したところ、中古のUNIVERGEがよさそうというオススメをもらい、中古で型落ちなら値段も手頃だったのでそれでやってみることにした。 ということで、UNIVERGE IX2105を購入したのでした。ついでにネットワークのお勉強もできる。

構成

超ざっくり

WAN(※)--->[IX2105]--->[WX3000HP]

※便宜上、WANとしているが、実態はたぶんマンション共用部に構成させたネットワークになっていそう

やったこと

まず、コンソールケーブルを持っていなかったので初期設定に困った。 いまどきRS-232Cのコネクタなどそうそう載っていないわけで、うちの場合は家にMac系しかなかったのでUSB-Cからなんとかする必要があった。

そこで買ったのがこちらです。

RS-232Cのコネクタを経由せずに内部的にコンソール用のRJ45に合わせてよしなにしてくれて便利。 コネクタの仕様はよく知らないけど様々なルーターに対応しているようだし、業界標準的な仕様があるのでしょう。知らんけど。

ドライバはここから入手した。

ftdichip.com

家にM1(arm)のMacしかないなか、macOS向けにはx86_64のドライバしかなくて、しかもダウンロードしてインストールしようとしたらエラーが出て焦ったけど、 よくよく読んだらApplicationフォルダに入れて実行しろということらしく、ほんまかいなと思いつつ言われるがままに入れて実行したらあっさり成功して問題なく使えた。 なんかmacOSによってよしなになっているのでしょうたぶん。

あとはマニュアルを見ながらよしなに設定。 ぐぐって出てきた設定みても結局なぜそういう設定になるのかが分からないので、ちゃんとマニュアルを読んで基礎的な操作方法や概念を理解してから設定するほうが早かった。

構成的にPPPoEとかもする必要もなくいので普通に設定をしていく。

WAN側のインターフェースはDHCPでIPを受け取りつつ、Proxy DNSとNAPTを有効化したり、 パケットフィルターやキャッシュを設定したり、NetMeisterに登録してファームウェア・アップデートをかけたりなど。

NTPはホスト名で指定できないのがちょっと微妙で悩ましかった。

よし

まあ、とりあえず良い感じになったと思うので、しばらくこれで運用してみる。 速度も元のスイッチングハブでやってたときと同等の速度がでているし、パケットフィルタもいれたことでちゃんとネットワーク的にも分離したうえで必要のないアクセスをある程度は排除できていそうで当初の目的が達成できてよかった。

Google Cloud Workflowのエミュレータを作り始めた

github.com

Google Cloud Workflowのエミュレータを作り始めた。 標準関数などもだいたいひとまずの実装が終わってきて、ある程度動くようになっている。

Cloud Workflowを検証・導入する機会があって、便利な一方でエミュレータが提供されておらずローカルでの動作確認に困ったのもあり、いっそ自作してみるかという気分になった。 あと、一度は処理系っぽいものを作ってみたかったというのもあって、それもそこそこ大きなモチベーションだった。

Cloud WorkflowはYAMLかJSONで手続きを記述するとそれをクラウド上で動かせるというもので、 Cloud Functionなどとの違いとしては制約が多く複雑なことがやりづらい(できなくはない)のでオーケストレーター的な役割などに集中させやすいこと、 また冗長性とフォールトトレランスに力がいれられておりゾーン障害などが起きても実行の継続が保証されたりリトライが簡単に記述できるあたりが良いと思う。

詳しくはドキュメントを見るとよさそう:

cloud.google.com

どうつくったかというと、GoだとCIからバイナリを落としてくるだけで動く世界観になってインストールが楽そう && 慣れているのでGoで作ることにした。

Cloud Workflowの構文はYAMLとJSONで書けるのだが、お馴染みの github.com/goccy/go-yaml が実は YAMLToJSON というインターフェースが提供されており、さらに一定のパターンは持つものの決定的に構造が定まるわけではないので一部を json.RawMessage にしてその親の構造に応じて遅延評価するような実装ができると楽そうだと踏んでそういう感じで実装していった。

JSONのデータ構造を解釈して、それの実行順序を組み、そのとおりに実行していく簡単なインタプリタを基本としている。

ほか、式や変数を扱えたり for によってスコープが作れたり、関数呼び出しが式のなかでも行えたり、演算子に優先順位があったり動的型付けながら型の概念もあったりなど、諸々をうまく解決していく必要があってなかなか大変だったし勉強になった。このあたりは機会があればどこかで話したい。

というわけで、OAuth2を使って(Google Cloud APIではなくGoogle CalendarなどのAPIである)Google APIを叩けないなど一部困った問題はあるが、 おおまか動くっぽい感じになってきたので、Cloud Workflowを使っている皆様におかれましては、ぜひ試してみてほしいです。(まだまだテスト不足でバグってるところも多いかとは思いますが。。)

ISUCON12予選に1人で挑み惨敗を喫しました

FAILでスコア0でフィニッシュです。ありがとうございました……。

経緯

近頃、チームで仕事するの難しいなーと感じていたところ、気が狂い、いっそのこと俺一人で全部やりてーとなって、血迷いました。難しさから逃げるな。

やったこと

最初に動いているコードはGoでした。

Goのまま行ってもよかったのですが、せっかく好きな言語であるところのPerlの初期実装が用意されているわけだし、Perlを使うことでチームメイトが手を出しづらくなるわけでもないし、RDBを使った開発経験*1としては一番長いPerlで挑もうということでPerlに切り替えます。 なにより、Perlで勝てたらカッコいいじゃんと思っていました。負けたが……。

兎にも角にも、まず計測。

ベンチを回し、事前に用意したAnsibleによってセットアップしたNetdataでOSリソースの消費バランスを見つつ、アクセスログをalpで集計しました。 ISUCON11予選と同様にセキュリティグループの変更は制限されていそうだったので、こんな具合でnginx経由でNetdataにアクセスできるようにしておきます。

        location /_netdata/isucon12q-1/ {
            access_log off;
            rewrite /_netdata/isucon12q-1/(.*) /$1 break;
            proxy_pass http://127.0.0.1:19999;
        }

        location /_netdata/isucon12q-2/ {
            access_log off;
            rewrite /_netdata/isucon12q-2/(.*) /$1 break;
            proxy_pass http://192.168.0.12:19999;
        }

        location /_netdata/isucon12q-3/ {
            access_log off;
            rewrite /_netdata/isucon12q-3/(.*) /$1 break;
            proxy_pass http://192.168.0.13:19999;
        }

https://github.com/karupanerura/isucon12-qualifier/blob/main/ansible/files/config/nginx.conf#L113-L129

オープニングムービーの通り、ランキングを提供するエンドポイントが圧倒的な処理時間を占めています。また、I/O waitが大変に高い値を示していることが分かりました。 (本当はtopも見る手筈だったけどうっかり抜けていた。。)

では、と、実際にコードを読み始めてみるのですが、SQLiteだったので少々面食らいます。コード量も多く、焦りを感じます。

コード量も多く一人なのもあってつい焦ってしまい、I/O waitの原因は都度openしているSQLiteを通じた、ディスクへのアクセス過多だろうと早合点してしまいました。 本来はちゃんと計測して切り分けをするべきです。

スキーマを見ると明らかにIndexが効いていないのでとりあえずIndexを貼ってベンチを流したところ、ほとんどスコアが変わりません。

実は、このとき、実はスキーマの定義ファイルだけを変更しただけで初期データに対してIndexを貼るのを忘れており、またしても早合点していました。 これでは新しく作られたテナントに対してしかIndexが効かず、実際にはID=1のテナントがかなりヘビーに使われていたため、ほとんど効果がないのは当然です。 この結果をすぐに疑うべきでしたが、ここでSQLiteのままチューニングすることに対する懐疑心が強くなります。

また、SQLiteのままでは、普段使っているMySQL向けのツールが使えません。 Profilerも用意されていましたが、初見のプロファイリングのログをうまく集計/解析して頑張ってSQLiteのままチューニングしきれる自信はありませんでした。

さらに、SQLiteからMySQLへの移行を補助する簡易なスクリプトも用意されているのを発見したので、MySQLへ頑張って移行するぞという気持ちが強くなります。

そして、MySQLへの移行を決意しました。すでに判断ミスが多い……。

テナントDBのMySQL移行

SQLiteのDBをMySQLに移行するにあたって、それぞれを別のデータベースとして作ってしまうのがよかろうという発想に自然となりました。データベース名は isucon_tenant_%d のようにしました。

そして、IDは数値なので、IDの2の余剰で2台に分散させることができそうです。実際にシャーディングするかはさておき、ベンチマーカーで負荷が高くなっていった際に新しくヘビーに使われるテナントが発生するシナリオはありえなくはなさそうですし、そういったテナントを隔離するためにもDBを選択できる実装にしておくのはよさそうな気がしたのでついでにやってしまうことにしました。

幸い、SQLiteのファイルがテナント毎に別れていためにそのようなシャーディングの実装は簡単にできそうでした。

どうせMySQLに移行するなら同時にスキーマも最適化してしまうことにします。 幸い、今回は1人チームなので他のひとの作業をブロックすることはありません。

SQLiteからMySQLへの移行はそこそこ時間がかかりそうだったので、シャーディングすることを考えたときに同居する可能性があるAdmin DBをどうするか見ていきます。

Admin DBのRedis移行

Admin DBをどうするかを考えるためにスキーマと実装を追ってみると、 id_generatorvisit_history がつらそうなことに気づきます。

いずれもハードに利用されるテーブルであり、特に visit_history はランキングを提供するエンドポイントにアクセスする度にINSERTされているのでボトルネックになりやすいかなり邪魔げです。しかし、実際には visit_history は.finished_at 以前の最終アクセス日時しか求められていないようでした。ランキングを提供するエンドポイントで finished_at 以前の期間だけ更新するようにすればRedisのSET型で十分そうです。これもついでにやってしまうことにしました。

id_generator もRedisでINCR/INCRBYを使って代替できそうです。UUIDやULIDに変えることも考えましたが、現状のID発番は連番となっておりそれをわざわざ16進数に変換して扱っていたため、連番であることや外形的に16進数表記であることに意味がある可能性があると考えてそれはやめておきました。なお、いずれも問題なかったそうです。残念。

これをやりきれば、Admin DBはテナントの定義だけになり、かなりシンプルになりそうです。負荷はほとんどなくせるでしょう。

ついでにガッと移行してしまうことにします。

テナントDBの最適化

SQLiteからMySQLへの移行がなかなか終わらないので、テナントDBの最適化も先回りして考え始めます。

ランキング情報を作るための player_score はCSV経由でインポートされます。 flockでロックを取ってアイソレーションが行われていますが、DELETEする際にtenant_idcompetition_idで絞り込む際に適切にロックを取れるはずです。トランザクションにまとめることでこのflockは外せるでしょう。

また、N+1クエリとなっているクエリを読み解くと、各player_id毎に最後の行のスコアを採用してそれをscore順に並び替えていることがわかります。 他の箇所でも最後の行以外は要求されていなさそうでした。CSVからインポートする際に各ユーザーごとに最後の行だけを採用すればよさそうです。(後述しますが、この考えは実際には少し間違っていました。)

ここで、初期データを修正する必要性に気付きますが、修正は窓関数を使えばSQL一発で出来るので他のALTERと一緒にやってしまえばよさそうです。(このときに、先のINDEXの適用が実はできてなかったと気づければ引き返せたのだけど、気付かず。)

SELECT
   id, tenant_id, competition_id, player_id, score, row_num, created_at, updated_at
FROM (
    SELECT
        id, tenant_id, competition_id, player_id, score, row_num, created_at, updated_at,
        ROW_NUMBER() OVER (PARTITION BY tenant_id, competition_id, player_id ORDER BY row_num DESC) AS `rank`
    FROM player_score) a
WHERE a.rank = 1;

実際には、idは使っている箇所がないのでDROPできますし、row_num も使うことはないので残す必要はありません。(後述しますが、この考えは実際には少し間違っていました。)

さらに、IDが16進数表記の文字列になっているわけで、単調増加なぶんB+Treeの挿入効率は悪くはないのですが、無駄にインデックスサイズが大きいのが気になります。 (ボトルネックではないので)放っておけばいいものの、ついでなのでbigintにすることにしました。変換はMySQLのCONV関数で一発です。

……などを織り込んだものがこちらです:

-- competition
CREATE TABLE `competition_new` (
  `id` bigint NOT NULL,
  `tenant_id` bigint NOT NULL,
  `title` text NOT NULL,
  `finished_at` bigint DEFAULT NULL,
  `created_at` bigint NOT NULL,
  `updated_at` bigint NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
INSERT INTO competition_new (id, tenant_id, title, finished_at, created_at, updated_at) SELECT CONV(id,16,10) AS id, tenant_id, title, finished_at, created_at, updated_at FROM competition;
RENAME TABLE competition TO competition_old, competition_new TO competition;
DROP TABLE competition_old;

-- player
CREATE TABLE `player_new` (
  `id` bigint NOT NULL,
  `tenant_id` bigint NOT NULL,
  `display_name` text NOT NULL,
  `is_disqualified` tinyint(1) NOT NULL,
  `created_at` bigint NOT NULL,
  `updated_at` bigint NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
INSERT INTO player_new (id, tenant_id, display_name, is_disqualified, created_at, updated_at) SELECT CONV(id,16,10) AS id, tenant_id, display_name, is_disqualified, created_at, updated_at FROM player;
RENAME TABLE player TO player_old, player_new TO player;
DROP TABLE player_old;

--- player_score

CREATE TABLE `player_score_new` (
  `tenant_id` bigint NOT NULL,
  `player_id` bigint NOT NULL,
  `competition_id` bigint NOT NULL,
  `score` bigint NOT NULL,
  `created_at` bigint NOT NULL,
  `updated_at` bigint NOT NULL,
  PRIMARY KEY (`tenant_id`, `player_id`, `competition_id`),
  INDEX `ranking_idx` (`tenant_id`, `competition_id`, `score`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
INSERT INTO player_score_new (tenant_id, competition_id, player_id, score, created_at, updated_at) SELECT tenant_id, CONV(competition_id,16,10) AS competition_id, CONV(player_id,16,10) AS player_id, score, created_at, updated_at FROM (SELECT id, tenant_id, competition_id, player_id, score, created_at, updated_at, ROW_NUMBER() OVER (PARTITION BY tenant_id, competition_id, player_id ORDER BY row_num DESC) AS `rank` FROM player_score) a WHERE a.rank = 1;
RENAME TABLE player_score TO player_score_old, player_score_new TO player_score;
DROP TABLE player_score_old;

https://github.com/karupanerura/isucon12-qualifier/blob/main/sql/pre_convert.sql

お気づきでしょうか。せっかくtalentごとにDB分けてるのにうっかりtenant_idを抜くのを忘れています。 ちょっともったいないですが些細なことなのでそのまま先に進みます。

このあたりを組み終わった頃に様子をみてみると、なかなかSQLiteからMySQLへの移行スクリプトが終わらないことに気付きます。 SHOW FULL PROCESSLIST を打って進捗を確認してみると、どうやらすごい量のplayer_scoreを持っているようです。

こりゃ大変だということで並列でインポートすることを試みます。 seq 1 100 | xargs -P10 -I{} bash -c './sql/sqlite3-to-sql ../initial_data/{}.db | mysql -uisucon isuports_tenant_{}' みたいな具合で並列で入れることにします。 この時点では、ろくに見積もりも取らずに、どうせ実装時間は取られるのだし並行で移行を進めておけば時間の無駄にはならなかろうと高をくくっていました。見積もりくらい取るべきでした。

実装デバッグ祭り

このあたりの実装をいったん終えたのが15:21頃だったようです。

github.com

新しいスキーマに合わせていざベンチ実行!コケたー! というわけで修正まつりです。

様々な問題、考慮漏れが次々と見つかります。visit_historyのRedis移行に対するPOST /initializeへの対応漏れや、同様に初期化におけるテナントDBのDROP DATABASE漏れ。 さらには、IDのURL上での16進数表記との相互変換漏れや、数年ぶりに使うSQL::Makerの使い方のミスなど、実に様々な問題を発見しますが一向にFAILは解消されず、刻一刻と時間が過ぎていきます。

最後まで残った問題が 大会内のランキング取得: ページングなし,上限100件 GET /api/player/competition/9fa52466/ranking 大会のランキングの1位のプレイヤーが違います (want: 9fa524cb, got: 9fa5252f) tenant:et-vrj-1659142684 role:player playerID:9fa524cb competitionID:9fa52466 rankAfter: というものです。 新しく作成されたテナントのもので、WebUIで確認しようにもログインセッションの発行の仕方が分からず(事前にちゃんと確認しておけばよかった……)、バグの原因が分からぬままモンキーデバッグを繰り返し、タイムアップを迎え、敗北が確定しました。

感想戦

なお、ベンチが公開されたので追試しつつデバッグを進めたところ、ベンチマーカーのレスポンスのログからすべてのプレイヤーが同一のスコアであった場合のランキングが正しくなかった事がわかりました。

修正内容としては以下です。

https://github.com/karupanerura/isucon12-qualifier/compare/main...study#diff-500630450a78877efc40cad8ce8ee6addd162a6bbe0ccf2bc9cd8cfaeae750ecR2

row_num をDROPしていたのを残すようにして、 INDEX ranking_idx (competition_id, score DESC, row_num) のインデックスを貼ることでクエリ一発でいい感じに取れるようにしています。

結果はこんな感じでした。

ほか、tenant_idをDROPしたり、 ORDER BY created_at 向けにインデックスを貼ったり細かいことをしているのでその影響も多少はあるかもしれませんが、デバッグ用にアプリケーションのAccessLogを有効にしているのも入っているので影響はほぼトントンでしょう。

もしこれが17時くらいにできていれば、複数台構成にしたりログを切ったりすることで3万点台は確実に狙えただろうと思うと悔しい限りです。

KPT

  • KEEP
  • PROBLEM
    • 計測のツメが甘い
      • 焦ったのもあるがボトルネックとなっているプロセスすらちゃんと特定せずに手を出したのはダメだった
      • 効果が思ったほど出なかった打ち手はその原因をちゃんと追求するべき
    • 計測と改善のサイクルがちゃんと回せていない
      • "改善"をデカくしすぎた
      • ついでに変えるのをやりすぎ
        • 先回りが良い方向に働いた部分もあったはずだけど確実性を欠いている
        • 結果的に博打っぽい打ち手になってしまった
        • 効果がそんなに見込めない打ち手に時間を使ってしまった(e.g. IDの16進数→10進数変換)
    • 一気にたくさん変えすぎた
      • 変更を一気に入れすぎてデバッグのサイクルを素早く回せなかった
    • 動作確認環境として他のサーバーを活かせなかった
      • MySQLへの移行とかは遊んでるサーバーでやっておいて、その間にSQLiteのままできる改善を進めておけばよかった
      • これができていれば、ULIDにシュッと変えてみて試しにベンチ流してみる。といったことを気軽に試せたはず
  • TRY
    • 焦らずちゃんと計測する
      • 問題の原因をちゃんと明らかにしてから次へ進む
    • ボトルネック以外には手を出さない
      • 特に、IDの16進数→10進数変換とかやる必要なかった。コードの変更箇所多すぎて時間的なコスパ悪い打ち手だった
      • 本当に手軽にできそうかちゃんと考えてから取り組む
    • 動作確認環境として他のサーバーを活かす
      • DBのオペレーションとか、ベンチ回しやすいところではやらないほうが他の作業をブロックしないのでよい

感想

解いていて面白かったです!ありがとうございました!次こそは……。

*1:GoでのWebアプリケーションの開発経験の大半はGoogle CloudでCloud Firestore(Datastore mode)を使うことが殆どでRDBの出番がなかった。

GCSへのアクセスをIdentity Aware Proxyで制限したいのでProxyを作った

storybookを共有したいなど、GCSに静的ファイルを配置しつつもそれを限定したメンバーだけに見せたいような用途ではアクセスを簡単かつ確実に制限するために、その制限にIdentity Aware Proxyを使いたくなることがあります。 しかし、Identity Aware ProxyはBackend Serviceに紐付けることはできますが、Backend Bucketに紐付けることはできません。Cloud Armerも同様です。 そこで、Backend ServiceとしてGCSのコンテンツを配信できるように、Proxy Serverを作りました。

github.com

ghcr.io/karupanerura/gcsproxy:v0.0.1 にビルド済のイメージを置いてあるので、 これをそのまま(予め自身のProjectのArtifact Registryにコピーを配置した上で)Cloud Runにdeployすることで、 GCSを参照するProxyとして動かすことができます。

GCS_PROXY_BUCKET 環境変数でGCSのバケット名を指定すればとりあえず動きます。

ほか、URLのパスプレフィクスをどうにかしたい場合、たとえば /static/ 以下をCloud Load BalancerからルーティングしてGCSのルートを参照させたい場合は GCS_PROXY_PATH_PREFIX/static/ と設定すればよいようになっていたり、 /index.html を参照させたかったら GCS_PROXY_INDEX_FILEindex.html と設定すればよくなっていたりなど、細かい機能が少しだけ実装されています。

あと(複数のRangeは扱えないけれど)Range Requestに対応していたり、gzip圧縮済のコンテンツをAccept-Encodingに応じてうまく扱えたり、HEADリクエストを解釈できたりなど、地味な機能がいくつか付いています。

よかったら使ってください。こんなの使わなくてもこれでいけるよ的な情報もお待ちしています。

PerlNavigatorがすごい

年々とelispのメンテが雑になってきて、ついにはemacsclientがemacs serverにうまく接続できなくなってしまい、とはいえ普通にスタンドアロンで立ち上げると動くのでログも取れずに原因究明が難しく、もはやこのままでは引退も近いかと思われたので、悪あがきでVSCodeに手を出してみることにした。

Perl Mongerの端くれとして、まずはPerlが書ける環境を整えようと、とりあえず最近ちょっと話題になっていたPerlNavigatorをVSCodeと共にインストールしてみた。

github.com

ところがこいつがすごい。

シンタックスハイライトをいいかんじにやってくれるのはもちろんのこと、emacsではperldoc -lmした結果に飛べるelispを仕込んでおいた(たぶんid:sugyanさんあたりのelispから拝借したきがする)のを使っていたが、PerlNavigatorはinclude pathの設定を入れたらいい感じにロードしてジャンプできる。 asdf でPerlを入れてもいい感じにつかってくれているようにみえる。

さらに、flycheckみたいな感じでエラーをわかりやすいところに出してくれし、auto-complete相当の雑な補完もよしなにやってくれる。

それも、大した設定もせずにこれだけのことがあっさりとできてしまったのがすごい。以下が入れた設定。

{
    "perlnavigator": {
        "includePaths": [
            "lib",
            "local/lib/perl5"
        ]
    }
}

これだけです。 lib.pm 置き場、 local/lib/perl5 はCartonで入れたやつを読むためのパスという感じ。 実際はarchごとにXSをビルドしたものが入るパスを lib pragmaのように突っ込めばより良い感じになるんだろうけどめんどくさくてまだやっていない。

設定したのはこれだけだけど、VSCodeに不慣れでもちょっとコードを書くにはそんなに困らない環境があっさり出来上がってしまったので驚いた。Amon2+Anikiで作ったWebアプリがいい感じに書ける。

実はむかしもVSCodeに移行しようとしたことがあったんだけど、そのときはセットアップしようとしたものの色々とどうすればいいのかわからず、だるくなってemacsに戻ってしまったが、これならVSCodeでもいいかなというところまであっさり来れたのでPerlNavigatorはすごい。

YAPC::Japan::Online 2022が終わり、その先の未来について

YAPC::Japan::Online 2022が終わりました。

JPA代表あるいはYAPC::Japan::Online 2022の主催としてのコメントはこちらに書きました:

blog.yapcjapan.org

ここでは、個人的に考えていることを書いてみます。

コミュニティ主催のカンファレンスの価値はなんだったのか

なぜ、ぼくたちはカンファレンスに参加(した|していた)んでしょうか?
なぜ、ぼくたちは、どこかの誰かがやってくれる大きなカンファレンスではなく、自分たちのカンファレンスを作っているのでしょうか?

人それぞれいろいろな答えがあると思いますが、はっきり「これだ」といえる理由がある方はおそらく多くは無いのではないかと思います。ぼくも、なんとなくカンファレンスに参加するのは好きだし、得られるものが多い気がしているだけで、あまりうまく言語化できないです。

自分の場合はどうだろうと、自分がカンファレンスに求めているものを言語化するとしたらなんだろうと考えると「自分の興味を広げる場」が大事なことの一つになるのかなと思いました。
そして、自分の場合はその主催者でもありますが、それに労力を割く理由はまた違って、PerlとPerlコミュニティの人々が好きで、それらのために少しでもなにか出来ることをしたいということになるのかなと思います。

自分の興味を広げるということ

自分の興味を広げる場、というのは実はなかなか作るのが難しいものだと思います。

なにか新しいことに興味を持つときのことを考えると、もちろん最初はそれに興味を持っていないわけですから、なにかきっかけが必要になります。 多くの場合、それは会話やSNS、テレビなどのマスメディア、あるいは仕事や生活のなかで起きることになります。

ただ、いずれも自分で選ぶものなので、いつの間にか傾向が偏っていきます。

フィルターバブルという言葉を聞くようになりましたが、ご存知でしょうか。 これはパーソナライズされたレコメンドなどを通じた情報に触れ続ける環境に居続けることによって、興味関心の薄い情報に触れにくくなる現象と言われていると思います。

フィルターバブルはこの最たるものの1つだと思いますが、そうでなくとも情報源を選択する必要がある以上は多かれ少なかれ偏りをもたらしているのではないでしょうか。 たとえば、普段よく見るテレビ番組はほとんど固定化されていませんか?新しい雑誌を最も最近読んだのはいつでしょうか?意識しなければ、たいていは同じものを選ぶのではないかと思います。 興味の幅を広げようと思っているのであれば、このような選択はしないほうがよいかもしれません。

自分の考えでは、興味の幅を広げるためには、興味関心から「少し」ズレた選択をすることが必要だと思います。 たとえば、身近な例だと、少し違う道を通って町並みを見てみる、書店に足を運んで少し違う本や雑誌を読んでみる、関連する少し違う分野の勉強をしてみる、といったようなことです。

カンファレンスの良さ

ぼくがカンファレンスの場に感じる良いところは、興味関心から少しズレたものに出会えるところでです。

カンファレンスに参加しようと思うと、一定のまとまった時間をカンファレンスに割くことになります。 目当ての発表がある場合はそれを聞きますが、そうでない場合はあまり興味がなかった発表を聞いてみたり、廊下などで近くにいた知り合いと話したりすることになるでしょう。 あまり興味がなかった発表が予想外に面白くてそのトピックに興味を持ったり、知り合いが聞いた別の発表について聞いたら

そして、懇親会で近くにいた人にとりあえず話しかけてみたりすると、全く違う発表に興味を持っていることがわかったりします。なかには当日に発表をしていた人などもいるでしょう。 その発表や分野の魅了を語ってもらったりするうちに、自分の興味がいつの間にか広がっているということも珍しくないのではないでしょうか。 さらに、気が合う人であればその後も他のカンファレンスなどで何度も合うことになって、いつの間にか同僚や友達になってることすらあります。

そういった、自分の興味を広げるチャンスがカンファレンスには多く広がっており、同じカンファレンスに参加し続けるだけでも少しづつそれを広げていくことが出来ると思っています。 そんな知的好奇心を刺激する楽しみがカンファレンスにはあります。また、いわゆる勉強会もその縮小としての楽しみがあるように思えます。

特に、コミュニティ主催のものはよりカジュアルなコミュニケーションに重きを置いていたり、トピックの幅も少し広く持っているものが多く、よりそんな楽しみが多いような気がしています。 (もちろん、企業主催のものでも通ずる部分は多いですし、また違った良さや面白さもあると思います。)

一方で、コロナ禍でオンラインカンファレンスが広まった後、オンラインカンファレンスでは以前ほどカンファレンスを楽しめないことが多くなったような気がします。

オンラインカンファレンスの難しさ

なぜ、コロナ禍でオンラインカンファレンスが広まった後、以前ほどの盛り上がりがなくなってしまったのでしょうか?

個人的な所感として、まず、懇親会への参加がいままでよりも難しくなったように感じました。

ZoomやGoogle Meetなどで大人数で雑談をしたり、オンライン飲み会をしたことがある方はおそらく共感していただけると思いますが、 意外と遅延が影響してなのか発話のタイミングが難しかったり、イヤホンやスピーカーだけでは同じ方向から複数の人の声が聞こえてくるからか聞き分けるのが難しかったり、カメラとマイクだけでは気分がなかなか共有できなかったりすると思います。

どれもちょっとしたことではあるのですが、その積み重ねでどうしてもある種のコミュニケーションの緊張感というか、そういったものが解きほぐしきれず会話が弾まない。会話が広がりにくいところがある気がします。

次に思い浮かんだものは、以前id:matsumoto_rさんが書いていたこの記事です。

hb.matsumoto-r.jp

自分は昨年に結婚をさせてもらったばかりで子供もいないのでまだ家庭内の仕事の総量は少ないほうだと思いますが、(パートナーがそこまで気にしていようといまいと)一定以上の負担をかけてしまう事実が自宅からオンラインで参加するとよりはっきりと分かるし、それがわかってしまえばそれを想像すると(オンラインでも、たとえオフラインに戻っても)二の足を踏みがちなのは気持ちとしてとても理解できます。(もちろん、それに何らか折り合いを付けて参加している方もおおぜいいらっしゃると思います。)

さらに懇親会といういわゆるサブイベントともなれば、ちょっとやめておこうという方も当然いらっしゃることでしょう。いい調子のときはただリラックスして楽しそうに喋ってるしそのように見えるものですから、パートナーからすると頭では分かっていても少しイラッとくる瞬間があるであろうことは想像できるでしょうし、そもそもの理解を得るにはコミュニティの文化的な背景とカンファレンス参加のモチベーションを共有する必要があるでしょう。

そして、一緒に参加している人の様子がわからないので、懇親会に参加してもどんな人たちと喋ることになるのかイメージが湧きにくいのも難しいポイントでしょう。

こういった様々なハードルがあってなのかはっきりとは分かりませんが、懇親会に参加する人はオンラインカンファレンスでは少ないようで、そうなるとやはり懇親会に参加する魅力はますます落ちてしまうでしょう。

このように、懇親会ひとつ取ってもこれだけ難しいので、オンラインカンファレンスには様々な難しさがあるといえます。

振り返ってYAPC::Japan::Online 2022ではどうだったのか

こういったことを感じていたので、目指したしたのが「ちゃんと交流できる」オンラインカンファレンスでした。

コミュニケーションの軸はDisocrdに置いて、いくつかの施策を実施したのですが、特に印象に残っているものを2つ紹介します。

裏トーク

まず、昨年にやったJapan.pm 2021でも同じことを目指して「裏トーク」というシステムを考えました。

これは、コミュニティの人を2~3人ほどMCとして招いて、カンファレンスの発表を聞きながらざっくばらんに語りあってもらい、それを参加者でトークの副音声として聞くことができるという仕組みです。 これによって、プラスアルファの楽しみを感じてもらいながら「空気感」を感じてもらうのが狙いでした。

どんな人がいるのかよくわからないところに飛び込むのは怖いものです。 こんな人達がいるのか、ということが人や空気感からなんとなく想像できると、少しだけかもしれませんが安心できるのではないでしょうか。

これがどれだけうまく行ったか、自分がフラットに体験することは叶いませんが、参加者の方々の反応を見る限りは上々だったのではないかなと想像しています。というか、そう思いたい。

前回で手応えを感じたので、今回も同様にやることにしました:

blog.yapcjapan.org

YAPCチキン && ビール && ラムネ

これは今回のためにスタッフとして手を上げてくれたメンバーが提案してくれたのですが、やはり飲食は強くて、同じご飯を食べるだけでもTwitterやDiscordなどを通じて空気感を共有できます。

ゲスト対談をしながら進行したのも相まって、懇親会は予想を大幅に上回る盛り上がりを見せて、自分は配信現場から翌日のために素早く撤収したりなどしてその環に混ざることが叶いませんでしたが、とても楽しんでもらえるオンラインカンファレンスの懇親会にできたのではないかなと思います。

紹介しきれないくらい様々なアイディアが出て、今回は少なくとも自分だけでは作ることができなかった、とても良いオンラインカンファレンスになったのではないかなと思います。

その先の未来について

来年のことを言えば鬼が笑うと言いますが、その先の未来についても少し想像を膨らませてみたいと思います。

YAPC::Kyoto 2020の延期判断をしてから実に2年もの月日が経ちました。 YAPC::Kyotoはまだ開催することができておらず、その見通しもまだ立っていません。

ただ、希望は出てきました。

ワクチンや治療薬の開発が進み実際に接種・投与がなされたり、さらに自宅療養も含めて医療的な知見が蓄積して、COVID-19に対して少しづつ医療的に立ち向かえるようになってきていること。コロナ禍における音楽フェスなどの大規模イベントの開催などを通じて、出来る限りの対策をした上で開催されるそれなりの規模のイベントに対してのリスクがその参加者のリテラシーと合わせて見直され、ものによっては社会的な反発が少なくなりつつあること。COVID-19の感染対策への個々人の向き合い方について一定の社会的常識ができつつあること。などです。

もともとの社会に戻ってきつつあるというわけではありませんが、オフラインでカンファレンスを開催する上でのハードルは少しづつ解消されていっているのではないでしょうか。

その一方で、一定の不可逆な変化もあるでしょう。

コロナ禍においてリモートワークが中心となり、生活スタイルが変化したこと。先に紹介した記事のように、それによって意識が変わったこと。様々な事情で引き続きCOVID-19への感染リスクが高い人がいるなかでウィルスの根絶はおそらく困難であることから感染対策が全く必要がなくなる可能性はおそらく低く、少なくとも中長期的にそれを継続する必要があること。などです。

オフラインでカンファレンスを開催することができたとしても、それに安心して誰もが参加することができるかというと必ずしもそうではなく、各々が何らかのハードルを乗り越えてくる必要があると想像しています。 いったいどれだけの人がそのハードルを乗り越えることができるのか、これだけ大きな変化があったわけですので、それを予想するのは困難になってきていると思います。

個人的な話

結婚して、将来について考えるなかで、人生の厳しさ・難しさを実感することが増えたように思います。 フィナンシャルプランナーに相談したり、家について考えたりなどすると、もっと頑張らなければと思わされます。

その一方で、カンファレンスの運営業などは報酬などの発生しない、ボランティアで行っています。

一般的に、コミュニティ主催のカンファレンスの運営は業務としては行わせてもらえないことが多いので、 業務外で家庭内などプライベートでもやらなければならないことが多いなかで、その隙間をうまく使ったり時間を捻出したりしてカンファレンスの準備に充てる必要があります。

しかし、カンファレンスを主催する立場ともなれば、多くの意識や注意をカンファレンスに注ぎ込まなければなりません。 十分にリソースを割かなければ、大したことができません。

たとえば、タスクを洗い出してスケジュールを決めたり、決まったことをスケジュールどおりにただやるだけではなく、状況に応じて柔軟に素早く意思決定をする必要があります。 そのためには、その背景に関する理解やチームに対する理解がなければならず、日々変化していくそれを自らインプットし、判断するべきタイミングを見逃さずにそれを発見し判断していくことが必要です。

少なくとも、自分がそういったことをちゃんと行うには、苦手なりにそれなりの時間をそこに費やして頭を整理して集中して考えなければ、まだまだそんなことはできそうにありません。

その一方で、カンファレンス運営にリソースを割きすぎると、当然ですが本業に影響をきたします。 うまくバランスが取れないと、どちらにも迷惑をかけてしまいます。

実際、今回は個人的にはそのバランスをうまく取れずどっちつかずな状態に陥ってしまい、それぞれで十分なバリューが発揮できなかったように感じます。 特に業務に目立って支障が出たとき、迷惑をかけてしまった同僚たちにはもちろんのこと、年に限られた回数しかない評価のことなど、考えると色々と苦い気持ちになります。

じゃあそんなに難しいのになんでやっているんだという話ですが、それはやはり僕がPerlとPerlコミュニティの人々が好きで、それらのために少しでもなにか出来ることをしたいというこの1点に尽きます。 そして、その気持ちだけで何も行動をしなければ、同じように他のひとも行動を起こさなくて、そのコミュニティやカンファレンスはあっさりと無くなったりあるいは風化してしまうという現実もよく知っているからです。

だからこそ、そういった難しさと向き合いながらも、YAPC::Japanというカンファレンスに関わり続けてきているわけです。

ただ、その気持ちと実際の負担に折り合いが付かないタイミングが来たら、どうしても関わり方を見直すことにならざるを得ない場面がいずれくるのだろうとも想像しています。

その先の未来

さて、暗い未来を想像しましたが、確かな希望もあって、それはこんな記事をここまで読んでくれている皆さんの存在です。

今回のYAPC::Japan::Online 2022では、自分にできることの限界をよく思い知らされました。 そして同時に、自分にない発想をもたらして、それを実現に向けて奔走してくれるスタッフのありがたさをです。

こういった活動を自分がこれからも続けていくためには、他でもない皆さんの助けが必要で、そしてそういった個々人の負担を減らしていくためにはまだまだ多くの方々の助けが必要であるということです。

もちろん、ぼくが苦しんだようなこういった難しさと多かれ少なかれ向き合う必要はあるかもしれないし、モチベーションの大小もひとそれぞれで、そしてみんな忙しくて、なかなか難しいことは分かっています。

それでも、どうか、少しだけでもいいので、力を、時間を、頭を貸してください。 もしよかったら yapc-japan-online-2022@googlegroups.com あるいは自分のTwitter DM宛で相談をください。

もちろん、カンファレンスは参加してくれる人、発表してくれる人なども居て初めて成立するものなので、無理に裏方を手伝ってもらう必要はないですし、これまで通り普通にカンファレンスを楽しんでもらうだけでもとてもありがたいです。

それでも、他のスタッフの参加ブログなどを読んで楽しそうだなとか少し興味を持つことができたなら、最初はお試し感覚でも大丈夫なのでご相談をいただけると嬉しいです。

PerlとPerlコミュニティの人々のためのそういった場や仕組みを、いっしょに色々なことを考えて実際に行動していって、未来をいっしょに作っていきましょう!