ぱいぱいにっき

Pythonが好きすぎるけれど、今からPerlを好きになりますにっき

YAPC::Kyoto 2023で話します!そしてチケットを今すぐに購入しましょう!!

YAPC::Kyoto 2023の採択トークが決まったようですね。面白そうなトークが沢山あってすごいですね。

blog.yapcjapan.org

私のトークも採択されました。ありがてぇ!

ある意味歴史のあるPerlならではのトークということで、Try::Catchの繰り返しであったWebサービスのデプロイ方法およびデリバリー方法の変遷について、デモを交えながら紹介していきます。

以下にトーク予定のトピックを書き連ねていきます。40分セッションなので一部はサラッと流したりするかもしれませんのでご了承ください。

  • CGIおさらい
  • CGI PaaSとしての「レンタルサーバー」
  • CGIの問題点とmod_perlおよびFastCGIの登場
  • Java Servelet, WSGI, RackそしてPSGI
  • 仮想化技術, VPS, IaaSの登場と「HTTPサーバーを飼う」
  • push型デプロイとpull型デプロイ
  • Rolling restart, Graceful restart
    • Server::Starter
  • PaaS, Dockerの登場
    • 注意: 当方k8sの経験があまりないのでk8sの話はあまりしません
  • Serverlessの隆盛
    • AWS LambdaとCloudflare Workersの例を紹介
  • そしてWebAssemblyへ
    • これはオチであって、あまり中身はなさそう

デモとかするので現地で見るのが絶対良さそう。

というわけでチケットを買ってくれ

それはそうとして、そんなYAPC::Kyoto 2023ですがチケット販売が今月1月の31日、つまり明日までとなっています。

passmarket.yahoo.co.jp

今月中にチケットを買わないと参加ができないのです! 今、まさにこの瞬間、すぐに買いましょう!!!!!

買いましたか?買いましたね。

それでは会場でお会いいたしましょう!

yapcjapan.org

オマージュ元

moznion.hatenadiary.com

onk.hatenablog.jp

soudai.hatenablog.com

2022年の振り返りと2023年の抱負

今年もいろんなことがありましたね。皆さんはいかがでしたか? これは自分のメモがてらの毎年書いているやつです。

去年はこちら。

mackee.hatenablog.com

今回はコミケ終わりで帰って眠りこけていたのでこんな時間に投稿しています。

登壇やイベントなど

相変わらずコロナの影響があり、コロナ以前と比べると少ないですが、オフサイトのイベントなどにも参加して、2023年はもっともっといろんなところでいろんな人と直接喋れるといいですね。

YAPC::Japan Online 2022

speakerdeck.com

最近ずっとやっている仕事の話です。ここではマイクロサービスと言っていますが、最近ではミニサービスとか、単にサービスという感じがしております。というのもそんなに分割するメリットがそこまで無いなと思っております。その辺の話とかしたいですね〜

YAPC::Japan Online 2022では、オンラインにも関わらず懇親会などで盛り上がれるように、クラフトビールやチキンなどが届いてこれは新しい形と感じた形式でした。

2023年はYAPC::Kyotoも開催予定です。プロポーザルをすでに送っているので、ぜひ受かって今度はリアルで喋りたいですね。

yapcjapan.org

PR TIMES × 面白法人カヤック合同勉強会 Part.2

speakerdeck.com

これも最近やった仕事の話。この勉強会はオンラインで会社外と交流が少なくなったから、合同勉強会やりましょうということでやったことなので、こういう合同勉強会もぜひ参加していきたいですね

primeNumber × 面白法人カヤック合同勉強会

techblog.kayac.com

発表内容

techblog.kayac.com

これも仕事の内容ですが、ちょっと古めの内容です。ただ現役で使っている仕組みですし、面白いと思ったので話させてもらいました。

この勉強会では懇親会もやったのですが、ああいう場で技術的な意見交換できるのが久しくて、非常に楽しかったです。

ISUCON12

isucon.net

予選出題チームとして参加しました。また、問題を実装に落とし込んだり、Goの初期実装は僕の担当でした。SQLiteという大変トリッキーな題材でしたが、マルチテナントでの苦しみや、ロック待ちなどの課題が出せてよかったと思います。僕の普段の仕事は、いわゆる技術を選択することですが、ISUCONの競技時間で、SQLiteを捨てるか、そのままやるかという技術選択を迫る問題が作れてよかったと思います。

来年はfujiwara組とは別のチームで出る予定です。

仕事

相変わらず仕事は去年と同じくTonamelです。以下は会社ブログで書いた記事。

techblog.kayac.com

今年は前に出てガッツリやるというより、同僚がうまいこと動けるようにサポートしつつ、難しい部分とかは手を貸すみたいな立場でした。とはいえ、説明したくなるような処理の実装もやりましたし、またどこかで紹介できるといいですね。

来年もぼちぼち同じような感じでやっていきます。

今年もStripeを使う仕事をやっていました。ただ他の得意な人にお願いしたいという気持ちはまだまだあります。あと、チューニングがやっぱり一番好きですね。今年から負荷試験をやる体制を整えたので、サクッと負荷試験してボトルネックを見つけて、改善するというサイクルができるようになりました。こういうことをずっと続けていきたいですね。

あと、スカウトの人とか、企業の方からメールが届くのですが、過去のCEDECなどの経歴からか、ゲームの会社さんを紹介されることが多いです。ただ、ゲーム自体はもういいかなという気持ちがあります。やっぱりWebが好きです。

今の仕事はWebですが、新しく出たWeb標準技術を何に使うか考えるとワクワクします。 最近だとWebAuthnが気になっています。そういう仕事をやれるといいな。

趣味

山は相変わらず行ってます。

高尾山からの富士山

三浦半島の大楠山からの富士山

筑波山

金時山。実は2021年に続き2回目

大室山。でもこれはリフトで登っている

大菩薩嶺。初の2000メートル級。ただスタート地点がかなり高め

山ではなく歩きという感じだけれど、碓氷峠廃線跡を歩くツアーに参加。トンネル好きにはたまらない。

突発で黒部ダムに行った。めっちゃ曇りだったけれど室堂まで登った後に、帰りの黒部ダムで晴れ間が見えてラッキー。

あと別日に上高地を歩いたものの、めちゃ雨でいい写真がなかった。

大旅行としては、JR四国バースデイきっぷで3日四国の鉄道を乗りまくるというのをやった。

うどんは2回食べた。

すずめの戸締りでも下灘駅出てましたね。これはその周辺の車窓。

巨大な津波避難施設があったりして、そういうところなんだなという気持ちにもなった。

あと金沢にも行った。

糸魚川フォッサマグナパーク。やたら豪雨がふり、列車で帰ろうとしたら運休になってしまい、タクシーを呼んだ思い出。

あと鬼怒川温泉も最近行きました。これ東武ワールドスクウェアの、成田空港とスカイツリーが同居する風景。

キャンプも色々行っております。

ミニ四駆にもハマってましたね。ニコニコ超会議B-MAX GPの観戦をしてました。

うちの愛車と同じボディのミニ四駆です。

あと、組み込み系ではTinyGoとか、XIAOでキーボード作るとかしてました。あとCloudflare Workersを触っています。

コンテンツ

プロジェクトヘイルメアリーをいろんな人に薦めている気がする。格ゲーはちょいちょいGGSTを触っています。

2023年の抱負

3Dプリンタ再開できてないので再開したい。いや、そういえばラップタイマー作っていたな。。。

2022年はそこそこ体重を減らせたのですが、2023年ももっと減らしていきたいですね。

何はともあれ、健康に心安らかに暮らしていきたいですね。

Firebase AuthenticationのSafari 16.1で動作しなくなる問題の解決過程

みなさん2022年いかがお過ごしですか。macopyです。

この記事はPerl Advent Calendar 2022の9日目です。

追記: Firebase Advent Calendar 2022の9日目も空いていたので入れておきました。

今回はFirebase Authenticationを使っていたら、何もしていないのにログインできなくなったと言われて一心不乱で直した話をします。

Firebase Authenticationとは

Firebase Authentication(以下Firebase Auth)とは、Googleのアプリケーション開発プラットフォームであるところのFirebaseの中にある、認証サービスです。

競合サービスとしてはAuth0で、つまりIDaaSとして使えます。専用品のAuth0よりは機能は少ないですが、複数のIdPを組み合わせてユーザの認証管理をしたいという用途には十分使えます。

Auth0に比べて使用量が安いことから、他のFirebaseのサービス(FirestoreやHosting、Configなど)を使わずに、Authだけ使っているという方もいるのではないのでしょうか。私が仕事で開発しているサービスもその一つで、本体のWebアプリケーションはAWS上で動いているものの、IdP個別対応の工数を浮かすために、IDaaSの部分だけFirebaseを用いていました。

検知編: ある日突然なぜかログインできなくなる

ある日、いつのものようにサービスのエゴサーチをしていると「ログインできない」というツイートなどがポツポツと見受けられました。はじめはよくある事例としてWebViewだとセッションが切れる問題から「ログインできていない」という話なのかなと思ったのですが、いきなり複数人が言っているので、なんだかおかしいぞとなります。

また、そういう人たちの一部がスクリーンショットをあげていたのでよく見ると、全員共通してiOS Safariでした。

首を傾げていると、サービスのお問い合わせフォームにログインできないとの問い合わせが来たと報告を受けたので、問い合わせをくださった方のOSとバージョンを聞いてみました。するとみなさんiOS 16.1とおっしゃる。

「なるほど〜」

なぜか知らないけれど、iOS 16.1でログインができないことはわかった。ちなみに、この問題を検知したのは10月29日、iOS 16.1がReleaseされたのは10月24日のようです。リリースされてから一気に更新されるわけではなく、じわじわ浸透していくので、私たちのユーザさんのところまで来るまでそれぐらいかかったようです。

社内の検証機端末もiOS 16.1にあげて、事象が再現しました。挙動としては私たちのWebサービスからFirebase Authを経由してSNS連携先でログインを行った後に、私たちのWebサービスの戻っても、ログインができていない状態です。やれやれと思いました。経験上、OS依存で起こる現象は、解決に手間がかかることが多いです。

調査編: firebase-js-sdkで上がる声

これが起こる状況は分かった。あとはなぜ起こるのかをシミュレーター等で探しつつ、「自分たちのサービスだけで起こっている」のか、それとも「他のサービスも起こっている」のかです。それによって問題の出所が絞り込めますし、後者であればもうすでに誰かが原因と解決方法を見つけているのかもしれません。

早速「firebase auth ios 16.1」などでググると、GitHubにあるfirebase-sdk-jsリポジトリのissueとしてすでに上がっていました。

github.com

10月22日時点でSafari 16.1 betaで事象が起こっているよということらしいです。ちなみにissueを上げられた方はiOSではなくMacSafariを使って事象が発生したようです。

firebase-js-sdkにはsignInWithRedirectという関数があります。

firebase.google.com

これは、挙動としては別のページに遷移し、そこでOAuthを行なってコールバックまで処理した上で、元のWebサイトに戻ってきてFirebaseのID TokenがJSに渡されるようになっています。

一方上記のissueで報告した方は、別の認証方法であるsignInWithPopupだとログインが可能であるとも言っています。こちらは別のウィンドウが開いて、そこでOAuthを行います。ウィンドウが開けないモバイル環境ではタブで代用されますが、タブすらないWebViewでは使うことができないデメリットがあります。私たちのアプリケーションではモバイルではsignInWithRedirect、PCではsignInWithPopupを使うようにしていました。

解決編: signInWithCredentialを用いる

とりあえずよくわからないが、signInWithRedirectを悪者ということにして他の手段を探ります。後述しますが、原因はSafari 16.1で強化されたITPでドメインをまたいだcookieが読めなくなったことなのですが、この時は何が原因なのか分かっていません。なので当てずっぽですが、開発サーバ上で色々試していきます。

先述したissueや、Firebase Authのドキュメントを見ていたところ、signInWithRedirectsignInWithPopup以外にsignInWithCredentialがあると書かれています。こちらは、OAuthもしくはOIDCで手に入れたアクセストークンなどを変換してFirebase Authに渡すことによって、認証を行うものです。

firebase.google.com

signInWithRedirectsignInWithPopupでは、アクセストークンやID Tokenを手に入れるのはFirebase Authの方の責務でしたが、signInWithCredentialを使う限りは、私たちのサービスで行い、そのあとはfirebase-js-sdkで行うということになります。

これは元々のFirebase Authを導入した理由であった、異なるIdP毎に実装を行うのを省略できるメリットがなくなることを意味します。しかしにっちもさっちもいきません。やっていく!

ちなみに私たちのアプリケーションでは認証部分はPerlで書かれています。IdP毎の認証を行なってsignInWithCredentialにアクセストークンなどを渡すのもPerlで実装しました。なのでPerl Advent Calendarにこの記事を書いています。

Google

firebase.google.com

基本的にはどの認証手段もAuthorization Code Flowでやっていきます。ドキュメントは上記。OIDCで認証してID Tokenを得ればいいっぽいので、以下のドキュメントでやっていきます。

developers.google.com

my $auth_uri = URI->new("https://accounts.google.com/o/oauth2/v2/auth");
$auth_uri->query_form(
    response_type => "code",
    client_id     => $client_id,
    scope         => "openid email",
    redirect_uri  => $host_url . "/auth/google/callback",
    state         => $state,
    nonce         => $nonce,
);
$c->redirect($auth_uri->as_string, 302);

こんな感じで認証URLに飛ばして、戻ってきたら、

$res = $furl->post(
   "https://oauth2.googleapis.com/token",
   [ "Content-Type" => "application/x-www-form-urlencoded" ],
   [
       code          => $code,
       client_id     => $client_id,
       client_secret => $client_secret,
       redirect_uri  => $host_url . "/auth/google/callback",
       grant_type    => "authorization_code",
   ],
 );

my $content = decode_json($res->content);
$c->session->set(google_id_token => $content->{id_token});
$c->redirect($redirect, 302);

こんな感じでいったんセッションに入れておいて、別のAPIエンドポイントで返してあげるようにしました。ただ、一回返したらもう消すようにしておきます。そんなにずっと覚えておきたい情報ではありません。 コールバックでID Tokenを返さずに別のAPIエンドポイントで返しているのは、コールバックURLはAPIではなくブラウザ上で実際に遷移しているので、signInWithCredentialを返すのは難しそうだからです。metaタグとかに埋め込んでレンダリングとかしてあげれば別なんでしょうけれども。

Facebook

大体Googleと一緒ですが、FacebookはOIDCではなくOAuth2なのでaccess tokenを渡してあげる必要がありそうです。

firebase.google.com

Authorization Code FlowでAccess Tokenを得ているだけなので割愛。

Twitter

firebase.google.com

ちょっと毛色が違います。Twitterのドキュメントを見ると、TwitterはOAuth 1.0aと書かれています。

developer.twitter.com

めんどくさいな!ということで、Net::Twitter::Liteの手を借ります。

my $nt = Net::Twitter::Lite::WithAPIv1_1->new(
    consumer_key    => $client_id,
    consumer_secret => $client_secret,
    ssl             => 1,
);
my $callback_uri = $host_url . '/auth/twitter/callback';
my $redirect_uri = $nt->get_authorization_url(callback => $callback_uri);
$c->session->set(twitter_oauth_request_token => {
    request_token        => $nt->request_token,
    request_token_secret => $nt->request_token_secret,
});

$c->redirect($redirect_uri);

これで認証させて、返ってきたら

my $nt = Net::Twitter::Lite::WithAPIv1_1->new(
    consumer_key    => $client_id,
    consumer_secret => $client_secret,
    ssl             => 1,
);
$nt->request_token($request_tokens->{request_token});
$nt->request_token_secret($request_tokens->{request_token_secret});
my ($access_token, $access_token_secret) = $nt->request_access_token(
    verifier => $oauth_verifier,
);

$c->session->set(twitter_credentials => {
    token  => $access_token,
    secret => $access_token_secret,
});

$c->redirect($redirect, 302);

こんな感じでアクセストークンを手に入れます。

とまあこんな感じでここまで1日で終わらせて、検証もやりました。signInWithRedirect時代に作ったアカウントに紐づくSNSアカウントでsignInWithCredentialでログインした際に、ちゃんと同じアカウントになることも確認しました。

次の日にもう一度確認してから本番反映し、めでたしめでたし。と言いつつ、Firebase Authを使っている意味が半減しているので、もっといい解決策はないかとissueを監視する日々が続きます。

後日: 公式の緩和策が出る

少し経ってから、issueに軽減策ガイドが出たよと貼られていました。

firebase.google.com

私たちはFirebase Auth以外でFirebaseを用いていないので、軽減策1は使えず、WebViewのユーザもサポートしたいので軽減策2は使えません。

signInWithCredentialを用いる方法は、軽減策5に当たります。ただ、こちらは実装の負担が大きいので別のものに切り替えたいところです。

軽減策3は良さそうに思えます。私たちのアプリはnginxがいるので、特定のパスに対してFirebase Authのアセットにproxyするようにすれば良さそうです。同じドメインでFirebaseの処理も動くので、cookieの問題も解決するわけですね。

まとめ

  • Webアプリ何もしていないのに勝手に壊れることがある
    • 今時SaaS使わずに作ることなんて多々あるし、ユーザのブラウザによっても壊れる。大変ですね
  • 頭にOAuth2の仕様が入っていてサクッと本番直せたので良かったですね

さて、Perl Advent Calendar 2022の明日10日目の記事は誰も入っていないようですが、これを見てなるほどと思った方は、ぜひ書いてみてはいかかでしょうか。こんな感じでPerlかすっている記事でも良さそうですし、昨日8日目のtomchaさんの記事を参考にやってみたとか、インストールしてみた、とかでも良さそうです。ぜひ参加をお待ちしています。

2021年の振り返りと2022年の抱負

今年もいろんなことがありましたね。皆さんはいかがでしたか? これは自分のメモがてらの毎年書いているやつです。

去年はこちら。

mackee.hatenablog.com

登壇とかイベントとか

Japan.pm 2021

mackee.hatenablog.com

思ったらpublicな場での登壇はこれだけかも。社内だとLT的なことはちょいちょいしているんですが、コロナもあってか勉強会参加の習慣もめっきり少なくなってしまいました。

上のやつでは、hotwireを触っていますが、その他の諸々が忙しくなって触ってないですね〜。

ところで、Perl Mongersなイベント関連では来年YAPC::Japan::Online 2022が行われるそうです。スピーカー募集も始まっていますね。

blog.yapcjapan.org

僕も何かネタを出せたらいいのだけれども。Perlから別の言語へマイグレーションしている途中の話するかな〜。パールパールしていないですが。

ISUCON11

去年に引き続き、fujiwara組の組員として出場し、優勝していました。本人は今でも特に実感はなく、会社の人たちがfujiwara組で出場して優勝してめでたいな〜みたいな一歩引いた視点になっています。何故...

チームメイトのfujiwaraさんとacidlemonさんの記事はこちら。

sfujiwara.hatenablog.com

beatsync.net

僕は特に書いてないというか、優勝報告記事の下書きはあるんですが、天然物の先送り体質のせいで時期を逸してしまった。

内容としては、

  • 素振りを2回やった。素振りを一緒にやっていただいた id:Soudai さんと id:uzulla さんにマジ感謝
  • サーバサイドエンジニアとしての業務にわりかし近い位置の問題が出たので相性が良かった

みたいな話です。あと、インタビューされた記事が以下に2つほどあります。

gihyo.jp

www.kayac.com

ARCREVO Japan 2020 Onlineのパーカーを着ている者です。誰もそのパーカーなんですかと聞いてくれなかった。

とはいえこの結果に満足せずに精進していきたいですね〜。

仕事

相変わらずTonamelの中の人です。

今年取り組んだ話としては、決済基盤の組み込みでしょうか。その辺の話の一部か以下の記事に書いてあります。

techblog.kayac.com

今年は半年ぐらいStripeのドキュメントやらと睨めっこして、あーでもないこーでもないみたいなことをやったりしていました。

以下ポエム。

ところでこの記事にある、マイクロサービシーズみたいなのは結構楽しいんですけれど、Stripeをいかに使いこなすかみたいな部分に関しては、結構"仕事"って感じになってしまって、なかなか足が向かない。うーむ、何が違うんだろうなあ。エンジニア市場で一般的に価値があるのは決済ゲートウェイを使いこなす人であって、僕が今の仕事で大好きなトーナメント表をいかに綺麗に組むかみたいなのは、日本だと片手で数えるほどの会社しか必要とされてないのになあ。

以上ポエム。

他にも来年に向けてさまざま仕込みをやっているのですが、その辺りはわりかし興味がある分野なので、話していきたいですね〜。Stripeに関しては、、、こういう自分で持ちたくないものこそ書くべきだと思ったので何か書くか〜。社内ドキュメントになるかもしれんが。Stripe Connect使ったプラットフォーム決済基盤とか、ノウハウがインターネットにそんなにないからだいぶ苦労したんよ。

趣味

去年少しだけ始めた山登りも、月1ぐらいで登れるようにしようとなって、とはいえ今年登ったのは低山も含めて6座だったと思う。

f:id:mackee_w:20211231130707p:plain
金時山

金時見晴らしパーキングという最近できた道の駐車場から標高チートして登った。

f:id:mackee_w:20211231130832p:plain
宝永山火口

会社で「噴火したら大阪リージョンに逃げような」という話をしていて、前に噴火した火口を見にきたのだった。宝永山自体には登っていない。

f:id:mackee_w:20211231130920p:plain
鋸山 ラピュタの壁

途中でお腹が痛くなって山頂まで行ってないのでまたリベンジしたい。

f:id:mackee_w:20211231131031p:plain
畦ヶ丸山頂

合計6時間ぐらい歩いた。川を渡るのに石を飛んでいかないといけない箇所があって、恐怖であった。

f:id:mackee_w:20211231131116p:plain
近所の天園ハイキングコース

鎌倉に引っ越したので、鎌倉と横浜の境目のハイキングコースにチャレンジした。足の運動にちょうどいい感じ。

キャンプ

今年も何回かキャンプをした。とはいえ、緊急事態宣言が開けた後からだったので、回数はそれほどでもない。

f:id:mackee_w:20211231131618p:plain
西丹沢

前述の畦ヶ丸に登った際に駐車場にデイキャンプ扱いで停めさせていただいたのだが、なんかいいなと思ったので2週間後に泊まりにきた。紅葉し始めな感じでなかなか良かったです。

f:id:mackee_w:20211231131655p:plain
南房総

山の急斜面にサイトがあるみたいな感じで、オートサイトとはいえ、その急斜面を車で登らないといけなかった。入り口で「この車四駆ですか?」と聞かれ、おそらくジムニーとかランクルみたいな山の四駆っぽい感じで聞かれたのだと思うが、我がGRヤリスは四駆なので「(スポーツ)四駆です」と答えてなんとか登った記憶がある。

f:id:mackee_w:20211231131730p:plain
ふもとっぱら

言わずと知れた広大なフリーサイトと富士山を望む絶景のサイト。ここで一人焼肉大会とイキっていたものの、台風が過ぎ去った直後で、風がめちゃくちゃ強く、その日はできなかった。これは翌朝に早朝焼肉をしているところ。

カート

八王子の方々に誘われてレンタルカートに励んでいた。

Google Photosを見返すと自分の写真がなく、他の方の写真やヘルメットにつけたGoProの動画しかなかったので、今度やったら撮ってもらおう。

キーボード

今年はKeyboadio Atreusでほぼほぼ過ごしていたものの、年末に機運があって、Atreus風のキーボードを作っていた。

f:id:mackee_w:20211231132553p:plain
potreus keyboard

これの基板データは以下にある。

github.com

GL516のGBにも参加したのと、他にもアイディアがあるので、基板設計周りはもっと励んでいきたいですね。

組み込みRust

ゴールデンウィークに以下の本を買って、Rustを学んでいた。

www.amazon.co.jp

Wio Terminalでライフゲームを実装していたりしていた。

f:id:mackee_w:20211231132921p:plain
ライフゲーム on WioTerminal

こちらで同じコードがWebでも動くやつもあげてある。

他にもRust入門として、こちらで紹介されている記事もやった。

diary.hatenablog.jp

というわけで、ある程度、今のRustを知れたので、何かに活かしたいが活かせるかな〜。そういや去年の振り返りの記事に、

あーあと、Rustやりたいんだった。ずっとやりたいって言っているのをどうにかしたいですね。

と書いてあるのはやっと達成できたと言えそう。

格ゲー

eスポーツのトーナメントプラットフォームを仕事でやっているので、そういう類のゲームにチャレンジしてみたいなと思い、今年新作が出たGUILTY GEAR -STRIVE-に取り組んでいた。

一時期は毎日やってたけれども、今は時間をおきつつやっている。メインキャラはラムレザル、ランクタワーとしては9Fぐらい、10Fは一瞬拝んだことがある程度。エンジニア仲間でGGSTをやるDiscordで部屋をたててやることも多いので、それも励みになっている。

ラムレザルというキャラクターはコンボで高火力が出るという、僕はコンボ練習大好きなのでうってつけと言えるのだが、プレイスタイルとしては中距離戦メインで、画面端までなんとか持っていって固めをして崩して一発6割のコンボをやる。それを2回やれば勝ちといった感じ。しかし基礎的な格ゲー筋がついていない僕は、画面端まで持っていったり、相手の崩しに耐えるという点で弱く、なかなか強みをいかせてなかった。最近、出口が見えてきた兆しがあるので、来年は掴み取りたい所存である。

目標は大会で年度内に一勝です。1ラウンドは取れるぐらいにはなってきた。

追いかけているマンガ

ちなみに単行本派です。

去年からの継続

新規

三体

https://www.amazon.co.jp/gp/product/B0922G73JR/

ついに、最終巻「死神永世」が出て読んだのだが、読んだ直後は魂を抜かれたような気分になった。作中では宇宙のどこか遠いところ、もしくはこの宇宙ではないところに読者ともども連れてかれるのだが、終わった瞬間に家の部屋にぽつんと一人になってしまって、oh...となってしまった。

ドがつくほどのハードSFでありながら、エンタメ小説であり、ため息が漏れてしまう。読んだ時のことを思い出して、感情が来てしまった。

来年

2022年って、2と0しかなく、次は2200年で多分生きてないので、貴重な1年であると言える。健康を維持しつつやっていきたい。チャレンジしたいこととしては、仕事のエンジニアとしてはいろいろあるんだけれども、まあそれは社内評価システムに書きつつ、表に言えることとしてはあんまりないかな〜。これは前からの課題だけれども、内外問わず巻き込み力をやっていきたいですね。

工作としてはキーボードもあったけれど、仲間内でミニ四駆ムーブメントが起き始めているので、このビッグウェーブに乗り遅れないようにしたい。あと、3Dプリンタ全然できてなかったので、再開したい。やっていなかったわけではないが、また3Dプリンタのパーツ作るので溶けてたんだよなあ。

あーあと!なんかこういう終わり際にいっぱい出てくるな。来年のGWの文フリで何らかを頒布することになっていたので、執筆しないといけない。何らかは何も決まっていない。

では、良いお年を。

大コンテナ時代における.gitを使うワークフローの難点を解決するためにGitHubDDLを作った

こんにちは、この記事はPerl Advent Calendar 2021の4日目の記事です。

3日目は@yoku0825さんのPerlで作られたMySQL用の何かについてでした。日々お世話になっている、pt-query-digestがPerlで作られているのは知っていたのですが、他にもいろいろPerl製ツールがあるんですね。

さて、最近仕事で発生した課題を解決するためにGitHubDDLというCPANモジュールを作ったので紹介させていただきます。

TL;DR

  • コンテナ環境において、プロジェクトの.gitをコンテナイメージに焼いたり、volume mountを行うのはいくつかの面で望ましくない
  • 仕事ではDBスキーママイグレーションに.gitを用いるGitDDLを使用していた
  • 以上のために、ECSでEFSマウントで.gitをマウントして構成が複雑になったり、.gitをイメージに焼いてpullが遅くなるなどしていた
  • それを解決するために、.gitを使わないツールとしてGitHubDDLを作った
    • .gitの代わりにGitHubからファイルを取得する

コンテナ環境に.gitを使うツールを使うのは辛い

Web開発においてはほとんどのケースでgitを使うようになっているのではないのでしょうか。gitはバージョン管理ツールではありますが、その副作用としてファイルの変更ごとに対応したコミットハッシュがつき、それをバージョン番号と見なすことができます。このバージョン番号や、過去のバージョンのファイルを取り出せるというメリットを使って古今東西さまざまなツールやワークフローが作られていると思います。

さて、現在の私の仕事では、全ての本番環境はAmazon ECSを使ったコンテナ環境で動作していて、踏み台サーバ的な存在も廃止してしまいました。では、手動でバッチプログラムを実行したり、DBスキーママイグレーションする作業をする場合には、本番とほぼ同様のコンテナを起動して、そこにECS Execやそれに類する技術でログインし、実行しています。また、シェルへのログインもせずコンテナ起動時にCMDをバッチプログラムに上書きして実行する場合もあります。

つまり、S3やEFSなどで外部から引っ張ってこない限りは、コンテナイメージに焼かれたファイル群のみを使ってバッチを実行するという制限が付いているわけです。ここに.gitを要求するようなワークフローが存在するとコンテナに.gitを焼くなり、EFSマウントを行うしかありません。このケースは非常にめんどくさくなります。

.gitをコンテナイメージに焼く場合の弊害

.gitにはshallow cloneを活用しない限りは、最初のコミットから今に至るまで全てのファイルが記録されています。.gitをファイル変更履歴として活用する場合、shallow cloneで--depth=1を指定すると意味がなくなるため、ある程度のコミットを掘って取得する、あるいは通常のcloneを行うことになります。

前者の場合、depthで指定した深さよりも前の履歴が必要と思われるケースではさらにunshallowをすることになりますが、それが頻繁に想定される場合はshallow clone自体が意味がないので、結局通常のcloneが最適解ということになります。さらに必要になったらunshallowする場合は何らかの形でcloneする場合の鍵をコンテナタスクが取得できなければなりません。必要になったらunshallowは労力が大きそうです。

というわけでリポジトリの全ての履歴を持った.gitを焼くと楽になるのですが、反面コンテナイメージが大きくなります。大きくなった場合、buildやpushに時間がかかります。一番大きな問題はpullに時間がかかることです。コンテナタスクを増やしてスケールアウトをする場合にpullの時間がかかると、タスク数が負荷を受け止め切れる台数に至るまでに時間がかかり、障害を起こすかもしれません。

また、Amazon ECSの場合はEFSを使用する手があります。つまりコンテナイメージの外に.gitを置くボリュームを作成しておき、これを必要に応じてマウントするという手法です。ですが、これは構成が複雑になり、定期的な.gitのメンテナンスが必要です。頻繁にブランチを切り替えたりする環境ではgit gcを定期的にするのを怠って、checkoutが非常に遅くなり問題になったケースがありました。

.gitを使わないAlternative GitDDL => GitHubDDL

私のプロジェクトで使用しているGitDDLは、DBスキーママイグレーションツールで、.gitを使用してDBに適用ずみのDDLを取り出し、ローカルにあるDDLと比較してALTER文などを生成するものです。

スキーママイグレーションは頻繁に行う作業であり、このためにコンテナイメージに.gitを焼いていたのですが、いい加減なんとかしないといけないなというのと、実装アイディアはあったので、今回新しくGitHubDDLを作成しました。GitHubDDLはGitDDLから多くのコードを利用しており、初期化オプション以外のメソッドの互換性を保った実装にしています。

.gitの代わりにGitHubを使う

GitDDLではgitコマンドを使ってDBに適用されているDDLを取り出すと記述しました。具体的にはこのようなコードになっています。

GitDDL/GitDDL.pm at bdf5dae23d685d90136cec606cbe42d5e9c78c95 · typester/GitDDL · GitHub

sub _dump_sql_for_specified_commit {
    my ($self, $commit_hash, $outfile) = @_;

    my ($mode, $type, $blob_hash) = split /\s+/, scalar $self->_git->run(
        'ls-tree', $commit_hash, '--', $self->ddl_file,
    );

    my $sql = $self->_git->run('cat-file', 'blob', $blob_hash);

    open my $fh, '>', $outfile or croak $!;
    print $fh $sql;
    close $fh;
}

この関数の外部から渡されている$commit_hashは変更対象のDBに存在するgit_ddl_version(名前はオプションで変更できる)から取り出したものです。これからgit ls-treeでgitオブジェクトのIDを取得し、git cat-fileで実際に取り出しています。

一方、GitHubDDLではどうやっているかというと、

GitHubDDL/GitHubDDL.pm at 046e10461231e619aae1473523aeedacfc1031f7 · mackee/GitHubDDL · GitHub

sub _dump_sql_for_specified_commit {
    my ($self, $commit_hash, $outfile) = @_;

    open my $fh, '>', $outfile or croak $!;
    if (my $method = $self->dump_sql_specified_commit_method) {
        my $sql = $method->($commit_hash);
        print $fh $sql;
        close $fh;
        return;
    }

    my $url = sprintf "https://raw.githubusercontent.com/%s/%s/%s/%s",
        $self->github_user,
        $self->github_repo,
        $commit_hash,
        $self->ddl_file;

    my $furl = Furl->new;
    my $res = $furl->request(
        method          => "GET",
        url             => $url,
        headers         => [
            Authorization => "token " . $self->github_token,
            Accept        => "application/vnd.github.v3+raw",
        ],
        write_code      => sub {
            my ( $status, $msg, $headers, $buf ) = @_;
            if ($status != 200) {
                die "status is not success when dump sql from GitHub: " . $self->ddl_file . ", status=" . $status;
            }
            print $fh $buf;
        }
    );
    close $fh;
}

このような形で、GitHubへ直接アクセスすることでファイル本体を取得しています。ちなみにprivate repositoryなどでも使えるように、Access tokenをつけています。これは基本的にはいわゆるPersonal Access Tokenを指定しますが、拙作のGitHub::Apps::Authを使うことで、GitHub Appsの認証情報でも同様に使うことができます。

その他おまけ

上記の__dump_sql_for_specified_commitメソッドの動作を外部から書き換えられるように、dump_sql_specified_commit_methodというオプションをつけられます。これはCodeRefを受け取るもので、どういうケースで使うかというと、.gitがあるときはGitDDLと同じ動作、GITHUB_TOKENが与えられたらGitHubDDLの動作という風に切り替えられます。

以下のコードは、これに加えて、DDL_VERSION環境変数にローカルのDDLのコミットハッシュが与えられるつもりだが、ない場合はGitDDLと同じ動作という挙動を実現しています。

my $ddl_version = $ENV{DDL_VERSION};
if (!$ddl_version) {
    $ddl_version = `git log -n 1 --pretty=format:%H -- sql/schema.sql`;
    die 'require $DDL_VRESION or .git on local for migrate' if $?;
    chomp $ddl_version;
}
my $dump_sql_specified_commit_method;
my $github_token = $ENV{GITHUB_TOKEN};
if (!$github_token) {
    $dump_sql_specified_commit_method = sub {
        my $commit = shift;

        my (undef, undef, $blob_hash) = split /\s+/, `git ls-tree $commit -- sql/schema.sql`;

        my $sql = `git cat-file blob $blob_hash`;
        chomp $sql;
        return $sql;
    },
}

my $gd = GitHubDDL->new(
    ...,
    ddl_version => $ddl_version,
    dump_sql_specified_commit_method => $dump_sql_specified_commit_method,
);

このようにすれば、漸進的にGitDDLからの移行ができます。

いかがでしたでしょうか? 明日5日はid:kfly8さんの「Perlのコンテキストクイズにツールで答えてみた」です。お楽しみに!

以下は捕捉情報

DBスキーママイグレーションとは

普段のWeb開発で私はMySQLもしくはMySQLプロトコルをしゃべるDBソフトウェアを使っています。MySQLが属するRDBMSというデータベースの種族は、かっちりと形が決まったデータベーススキーマを定義してRDBMSに適用し、それに沿ったデータを実際に稼働するWebアプリケーションで操作します。

ところで、RDBMSスキーマ、一般的にはDDLと呼ばれる形式で記述されます。例えば新しくテーブルを作るときは、

CREATE TABLE `sample_table` (
    `id` BIGINT NOT NULL AUTO_INCREMENT,
    `name` VARCHAR(191) NOT NULL,
    PRIMATY KEY (`id`)
);

のように記述するわけです。

ただ、私がお仕事で作るWebアプリケーションは、インターネット上で公開された後も、数年間の運用というものが発生し、その間も機能を追加したり修正をしたりなどの作業が発生します。

そういった既存コードをいじる作業もあるのですが、DBスキーマを変更しなければならない場面も発生します。

例えば、上記のsample_tableにカラムdescriptionを足したいとなった場合、これがお手元の開発環境であれば、

DROP TABLE `sample_table`;
CREATE TABLE `sample_table` (
    `id` BIGINT NOT NULL AUTO_INCREMENT,
    `name` VARCHAR(191) NOT NULL,
    `description` TEXT DEFAULT NULL
    PRIMATY KEY (`id`)
);

とすれば良いのですが、これをこのまま実際に稼働している本番環境で適用できることはまずありません。何故なら、運用を始めたWebアプリケーションのDBには消えてはいけないとされるデータが既に入っており、そこにDROP TABLEを打つことはできないからです。

ではどうするか。以下のDDLで既存テーブルにカラムを追加します。

ALTER TABLE `sample_table` ADD COLUMN `description` TEXT DEFAULT NULL;

このような一連の作業をDBのスキーママイグレーションと呼びます。

実際に世間ではどうやっているの

人間がスキーマ変更を伴うデプロイのたびに、手で温かみのあるALTER文を書くのは、できなくはありませんが、めちゃくちゃ大変なことではあります。また、ローカル開発環境構築時などではイチから完全な状態のDDLが欲しいところです。その場合、開発環境向けには完全なDDL、デプロイ用にALTER文を用意することになり、ミスや抜けもれが容易に発生します。もちろんこの完全なDDLとALTER文を逐次適用した状態のDBを比較するテストコードを作ることはできなくはないとは思いますが...。

というわけで私の知るかぎり、この辺りの運用を解決するためのアプローチとして以下の2つがあります。

  • 変更したい部分を記述したDDLもしくはそれに準じるDSLを書く。完全なDDLはそこから生成する
  • 古い(もしくは適用されている)完全なDDLと、変更後の完全なDDL間の差分をプログラムで計算し、ALTER文を生成する

また、後者の差分を出すケースの場合の中にも、比較対象のDDLの取り出し方にいくつかアプローチがあるようです * 適用したDDLをバージョンごとにローカルファイルで保存しておく * メリット: ローカルファイル同士の差分でシンプル * デメリット: DDLの適用ごとに完全なDDLのファイルが増えていく。以前に適用したファイルはどれか何らかの方法で覚えておく必要あり * バージョン管理ツールでDDLが管理されている場合、比較する際にバージョン管理ツールで前回適用したコミットハッシュからDDLを取り出す * メリット: バージョン管理ツールでDDLを含んだコードを管理している場合は合理的 * デメリット: ALTERを生成する場所にバージョン管理ツールのメタデータファイルが必要, 適用済みコミットハッシュを何らかの手段で覚えておく必要あり * 変更対象のDBに接続し、SHOW CREATE TABLEなどのコマンドで適用済みのDDLを取り出す * メリット: 前のDDLのバージョンなどを覚える必要はない。適用したいDDL以外のローカルファイルが必要ない * デメリット: ALTERを生成する場所からDBに接続できる必要がある

それぞれメリットデメリットがあります。

GitDDLのやり方

GitDDLは今まで説明した方法のうち以下を採用します。

  • 2つの完全なDDLから差分を生成する
  • gitから過去に適用したDDLを取り出す
  • 適用したコミットハッシュはgit_ddl_versionという専用のテーブルに記録する

プログラミング言語・技術を流行りで選んでもあんまり意味がない気がする

これはただの持論だし、さらに今はこう思っていたよっていう感じで、思想をブログに焼き付けておいてあとから自分で見たときに「ガッハッハ、このときはこう言う考え方をしていたんだな」とあとから自分を振り返って酒の肴にするぐらいの意味なんだけれども。

過去を振り返ってみると、プログラミング言語やその他のWebに関連する技術を流行りで選んだとしても、あんまり意味がない気がしたと思った。

ある言語は将来が無いから選択しない、ある技術はこれから伸び盛りだから選択する、みたいなことを僕は昔していた。ここでいう昔というのは2005年ぐらいのときに、Webプログラミングを独学でやっていたときにしていた気がしていた。いわゆる駆け出しって言う感じだし、そもそも高校生でアマチュアも当然、お金をもらってWebプログラミングをしている今とは環境も状況も全く違う。

当時の僕から見たWebプログラミングといえばレンタルサーバPerlを動かすCGIであり、PHPが盛り上がってきていた時期がと思う。その時期にはRailsもでてきて、フロントエンド技術としてはAjaxが提唱された頃。でもブラウザ上でゴリゴリ何かを動かすのであればFlashを使うのが主流だった。

僕はこれらの選択肢の中から、PHPFlashを選択している。Flashではモーショングラフィックスっぽい感じの動きで自分のサイトを作ってみたり、PHPではスレッドフロート掲示板を作ってみたりしていた。そこに「将来これが盛り上がるだろうからベットする」ではなく、今選べる選択肢の中からしっくり来たものを選んだ。その時期にCやPerlなんかもコピペまじりでHello Worldぐらいのチュートリアルはやってみたものの、一番「これは自分で物を作れそう」と思ったのは唯一PHPだけだった。Flashはその後に見様見真似でonClickでgotoAndPlayとモーショントゥイーンだけで作っていた。そのぐらいでも楽しかったし、何かしら物を作って公開している感は出ていた。その時の僕のモチベーションというのは、「物を作って人に見せる」というものだった。だから自分が考えたものを自分の手で完成しきれる技術なのか、という軸で選んでいたと思う。ただ、意識的にやっていたわけではない。

時が経ち、Webサービスプログラマとして就職したときには、今流行っているだとか、やっている人が多いだとか、キラキラしてそうみたいな観点で選ぼうとしていた気がする。ただ技術選択の余地というのは新人さんだとそんなに選択幅がないので、入れられる範囲で流行っていそうな物を取り込んでいた。これはこれで当時は正解だったと思うし、今となっても、吟味するという力にもなっていると思う。ただ、そのときプログラミング言語や技術に惹かれたときの最初に興味を持った理由は「流行ってそう」である。たぶん今はこう言う理由で選ばないと思う。

ポジションを話すと、僕は運用しているWebサービスを開発しながら発展させていく立場で、技術選択も出来る立場である。が、故にコンサバに技術選択をしていると思う。これは回り回って原点に戻ってきたのだけれども、一番の軸で「自分たちに扱える技術なのか」という点である。お仕事でプログラミングしている以上、完成してユーザに提供しないと意味がないことを今の僕は知っている。当時の僕は知っていたけれど見ないふりをしていた気がする。

俺たちには今しか確実には見えないし、今やれるかどうか、そして今選んだものを未来も選び続けられるかどうか、選ばないとして捨てられる選択肢かどうか、つまり自分がコントロール出来る範囲で選択するという感じである。軽量なフレームワークやライブラリを選べば、いざとなれば自分でメンテナンスができるが、重厚だとそれもままならない。これもコントロールできるかどうか、扱えるものなのかという観点だと思う。

これはチーム全体を見るベテランの観点であり、ジュニアのレベルで大事なのは、作りきれるかどうかだと思う。自分で作ろうと思ったものを作りきれたときに学べることは大変多い。言語や技術に特化した知識も学べるけれども、あとに効いてくるのはもっとメタな知識だと思う。メタな知識があれば、他の技術にわりかし楽に乗り移れる。強くてニューゲームがこの界隈にはあると思っている。あと技術を学ぶっていうのは、脳に覚えられる容量があって、一定の容量までしか詰め込められない、みたいなものではない。いっぱい覚えられるし、覚えれば覚えるほど技術の理解の解像度が上がる。ただ、時間的制約はあるので、そういう観点から使える技術をまず学ぶという圧力はあると思う。

最初に選んだ選択肢のうち、PHPは世間では広く使われている主流のプログラミング言語であるものの、今の僕は仕事で使っていない。もう一つのFlashの今の状況は、皆さんがよく知っているとおりである。だが、この選択肢は間違っていたかと言われると、全然間違っていなかった。なぜならどちらの道具も当時の僕が作りたいと思ったものを作りきれる技術だったからだ。それで学んだことは多いし、今にも活きている。

いつも調べることをチートシートにしてTシャツにした

相変わらず仕事ではPerlを書いているんですけれど、none とか zip とかそういう便利関数がList::UtilにあるのかList::MoreUtilsにあるのかわからなくて困っていた。

List::Utilっていうのは何かというと、Perlで配列を扱うときに便利な関数集で、コアモジュールなので特に追加でモジュールを入れなくても(CentOSとかのシステムPerlでない限り)使える。

例えばmaxっていう配列(厳密にはリスト)を渡したら最大の値のやつを返してくれる君があるんだけども、こんな感じ。

use List::Util qw/max/;

my $max = max(1..10); # 10

別にこれはList::Utilを使わなくても、こんな感じでsort使えばできるんだけども、

my ($max) = sort { $b <=> $a } (1..10);

firstとかnoneとかは確定したら途中で打ち切ってくれるので自前で書くよりは速いし、そもそもXSだし、名前もわかりやすい。sumとかはmy $total; $total += $_ for ...;ってやればできるけれど、まあなんか$_とかあんまり使いたくないじゃないですか。そういうのを丸っと引き受けてくれて便利。

ところがこれだけだと足りないことが世の中あり、もっといろんな便利関数が実装されているのがList::MoreUtilsです。こっちはコアモジュールではないのでcpanm List::MoreUtilsをやる必要がある。

Pythonでもあるzipって関数がList::MoreUtilsにあったり、after { $_ > $treshold } @arrみたいなのもあって便利。

というわけで普段の暮らしでは、これらを両方使っていくという感じなんですが、List::UtilにあってList::MoreUtilsにないもの、List::MoreUtilsにあってList::Utilにないもの、両方にあるものがあってどっちがどっちだっけとuseを書くときに毎回perldocで調べていました。

毎回調べるのはなんだか無駄だなと思っていたある日のこと。

suzuri.jp

インターネットにこれが流れてきて、なるほどこれはコードの主義主張ですが、もしかしたらチートシートをTシャツにするといちいち調べなくてもいいかもと思ったのでした。というわけで画像を作ってTシャツを作って人生を豊かにしましょう。

List::UtilとList::MoreUtilsの差分を知る

両者はコードを読むとExporterを使って関数を外から使えるようにしていて、@EXPORT_OKに入っているのでこんな感じでワンライナーで生成してみる。

$ diff -u <(perl -MList::Util -E '$"="\n";say "@List::Util::EXPORT_OK";' | sort) <(perl -MList::MoreUtils -E '$"="\n";say "@List::MoreUtils::EXPORT_OK";' | sort)

これでdiffが出る。ちなみにList::MoreUtilsは_XScompiledってやつも出てくるが、これは実際に使える関数ではないので、取り除く。

画像を作る

このままImagerモジュールなどを使って生成したらPerl Hackerっぽいなと思ったけれども、僕はプロプライエタリ野郎なのでAdobe Colorでいい感じの配色を探しつつAffinity Designerで作る。

背中はこんな感じにしようと思った。どちらかというと背中の方がわかりやすい。

ちなみにこんな感じでレイヤーで分けている。

f:id:mackee_w:20210327225258p:plain

Tシャツにする

Tシャツは上記ツイートと同じsuzuriで作った。

suzuri.jp

パーカーも作った。

suzuri.jp

f:id:mackee_w:20210327225517p:plain

ただ、思ったのはこれを着てどっちがどっちかわからなくなって見ようと思った時に、自分の胸からお腹を見ると逆になっているので見づらいと思う。なので、同僚などに自分を見てもらって、どっちがどっちか教えてもらおうと思う。