ぺんぎんメモ

プログラミングのメモです。たまに私生活のことや鬱っぽいことを書きます。

認証・認可について

OAuth 2.0 についての仕組みについて勉強するのが好きだ。OAuth 2.0 は様々な場所で使われている。OAuth 2.0 を理解すれば、それらについての理解も自ずと深まる。その感覚が好きだ。

でも、OAuth 2.0 については過去に何度か勉強したことがあるが、そのたびに勉強し直している。なぜ忘れてしまうかというと、認証・認可周りに触れる機会が極端に少ないからだと思う。まあ、時間が立つと忘れてしまうけど、理解し直すまでにかかる時間は短くなっている気がする。

ここで改めて、OAuth 2.0 と OpenID Connect について、理解したことを書き残そうと思う。いきなり整った文章を作るのは難しいため、まずは箇条書きで書いていこうと思う。

認証・認可についての書き出し
OAuth 2.0 / OpenID Connect について
  • OAuth 2.0 の主体は、ユーザー、クライアント、認可サーバー、リソースサーバーの4つ
  • 認可サーバーとリソースサーバーは同じことが多い気がする
  • 認可サーバーは、ユーザーが何者かを事前に知っている
    • しかし OAuth 2.0 は、そのあたりの仕組みに言及しない
    • どのようにユーザーを認証するかについて OAuth 2.0 は興味がない
  • OAuth 2.0 の認可コードフローでは、認可サーバーに認可リクエスト、トークンリクエストが送られる
    • どちらのリクエストもHTTPリクエスト
      • URLはクライアントが組み立てる
    • 認可リクエストの結果、クライアントに認可コードが送られる
    • トークンリクエストに認可コードを含めることで、クライアントはアクセストークンを得られる
    • 順序は、認可リクエスト→トークンリクエストである
  • OAuth 2.0 はリソースサーバーについてもあまり興味が無さそう
    • OAuth 2.0 を土台にした OpenID Connect にはリソースサーバーが絡まない
  • 認可リクエストは、ブラウザの JavaScript で組み立てることも、Python で組み立てることもある(gcloud コマンドがおそらくそう)
    • 認可リクエストには、redirect_uri、response_type、code_challengeなどを含める
      • response_type:code を設定すると認可コードフローになる
      • redirect_uri:認可コードを受け取るエンドポイント
      • code_challenge:PKCE 対策。ブラウザしか知らないランダムな文字列を作成し、それをSHA-256でハッシュ化して設定する。ハッシュ化前のランダムな文字列はトークンリクエストのときに使うため、保持しておく必要がある
      • client_id:アクセストークンを取得したいクライアントのID。認可サーバーが事前にクライアントに対して割り振ったものである
  • OpenID Connect は OAuth 2.0 の拡張で、response_type に id_token を含めることができる
    • トークンリクエストのレスポンスにIDトークンが含まれるようになる
    • IDトークンが正しいかどうかの確認は、とりあえず以下を行えば十分そうである
      • iss から認可サーバーを知る
      • すると、その認可サーバーの公開鍵のリストのありかがわかる
      • kid と公開鍵のリストから、IDトークンの署名に使われた公開鍵を特定できる
      • IDトークンを検証する
  • この検証が無事通ったら、IDトークンに書かれていることが正しいものだとわかる
    • 重要な点は、一度公開鍵のリストを取得しておけば、2回目以降はIDトークン検証時に外部サーバーへのアクセスが不要になることである
      • 認証を行うエンドポイントがあったとしても、認証によりレイテンシが極端に長くなることはない
    • 最近 DNS の設定を変えることによる攻撃が多発している気がする。上記の検証手順は、iss のドメインを第三者のIPアドレスに変換すれば不正が行える気がする
      • DNS の設定を書き換えられないように構成する必要がありそう
  • リフレッシュトークンから新たなリフレッシュトークンを作ったとき、古いほうを使えなくするかどうかは未定義
Firebase Authentication (FA)について
  • signInWithXXX により user が手に入る
    • 外部の IdP を使う場合、signInWithXXX を呼んだときにその IdP のページに遷移しそうである。このときに redirect_uri を設定することになるが、この redirect_uri は FA のドメインだろう。よって認可コードは FA のエンドポイントに送られることになる。僕はそのあたりに関与しないし、考える必要がない。FA の責務である
    • FA は IdP のIDトークンを取得する。ここも僕は関与なし
    • 検証して正しければ、FA 独自のIDトークンを発行する
      • iss は FA のURLになっているはずである
      • 独自のIDトークンには、IdP のありかや、IdP 上の sub などが含まれていそうである(または FA のデータベースに紐づけが保存されていそうである)
  • user.getIdToken() によりIDトークンが取得できる
    • どこにキャッシュされているかは知らない。これも FA の責務だと思う
  • このIDトークンが第三者に漏れたとき、不正に利用されそうである
    • IDトークンは短命でありそう(10分とか1時間とかだった気がする)
    • 第三者かどうかを判定する材料として、UserAgentやIPアドレスを一時的に保存する必要があるだろう
  • このIDトークンは第三者に漏れても使えるため、Bearer が正しそうである
    • FA は DPoP などに対応しているのだろうか
IdP について
  • LINE、Twitter、Facebook、Google、Apple は IdP を提供する
  • Google Cloud の Identity Platform では、Google ログインを有効化したとき、OAuth 2.0 の設定を要求される
    • アプリ名、サポートメールをまず入力し、その後でクライアントを順次追加していく方式だったと思う
      • 各クライアントにIDとシークレット、リダイレクトURIの設定がある
        • クライアントシークレットは、認可コードとトークンを交換するときに使った気がする
      • 認可サーバーアクセス時に表示されるアプリ名は、クライアントに限らず同じ
      • クライアントの種類は5個くらいあったと思う。Webアプリケーション、iOSアプリ、Androidアプリなど
  • それぞれについて審査がある
    • IdP に関連するリソースを渡すのだから審査があるのは不思議ではない
    • どんなところを重点的に見るのだろう
      • 外部データの扱い方についてきちんと明記されているかとかだろうか
  • クライアントの追加、リダイレクトの設定は、どの IdP にも限らず行っていた気がする
終わりに

現時点での解像度はこのくらいである。OAuth 2.0 / OpenID Connect に関する理解はある程度深くなったが、やはりそれらを実際に使った製品の仕組みの理解はまだ浅い。ただ、上記のことを理解したことで、以下のようなことを考えられるようになった。

  • リソースサーバーのデータだけが欲しいとき、IDトークンではなくアクセストークンを取得すればよい
  • IDトークンの検証を行うとき、認可サーバーの公開鍵で署名検証すれば正しさが証明でき、また aud を見れば別のクライアントが発行したかどうかがわかる。検証のときに見る場所は、kid、alg、iss、aud の4つになりそうだ
  • ログイン機能に IdP を使うとき、その IdP にクライアントの情報を登録する必要がある
    • OpenID Connect は OAuth 2.0 の拡張だから、「IdP を使いたいだけなのになぜ OAuth 2.0 という認可の設定を行わないといけない?」という疑問は浮かばなくなった
  • ただやはり、まだ少し認証と認可が混ざることがある
  • スコープにはそれほど複雑な情報を含められないから、認可サーバーを自作するときはそのあたりで考えることが多そう

また次のようなことを考える。

  • これから様々なリソースサーバーと連携することになるが、できれば OAuth 2.0 の仕様に則っていてほしいと願う
    • 独自の認可フローだと、それを理解するのに時間がかかるため

疲れたので一旦ここまで。

システムの目標

思いついたものを気ままに書いていく。現時点で乱雑。明日綺麗にするかも。
[2026/09/29 09:17:00] 綺麗にした。

まとめ

  • デプロイプロセスが明確であること
  • 十分に自動化されており、人の操作が必要以上に介入しないこと
  • 可観測性が十分にあること
  • AIが安全に調査できるような土台を整えること(可観測性はここにも繋がる)
  • システムに一貫性があること
  • テストが十分に行われていること
  • テストがしやすいこと
  • セキュリティが保たれていること(依存モジュールの脆弱性の検知が行える、第三者に誤って機能を提供しないなど)
  • 個人情報の扱いが適切であること
  • 障害対応についての仕組みが整っていること(週によって対応する人を変えるなど)
  • システムの開発フローが現在のチームに合っていること
  • 変更しやすいこと
  • 外部サービスが使えなくなったときのシステムの挙動を理解していること
  • 言語設定、タイムゾーンについての考慮がなされていること
  • エラーが十分に網羅されていること

あと、心構えについて大切にしたいこと

  • あらゆる選択はトレードオフが存在しうること
    • 銀の弾丸はない
    • 過去の自分が上記を満たさないシステムを構築してしまったのは、そちらの選択の方が、そのときの自分にとって有効だと思ったから。それは無知による選択かもしれないし、知った上での選択かもしれない。そもそも選択する必要がなかったのかもしれない
  • 人は器用ではない。複数のことを同時にはできない
    • 理解の浅い技術の技術検証と、その技術を自社システムにどう組み込むかは、同時に考えるのは難しい
      • 類似の技術を既に知っているのであれば可能かもしれない

詳しく

デプロイプロセスが明確であること
  • アーキテクチャ図を作成したあと、デプロイCIを作成する
    • アーキテクチャを構成する要素を見るときは、リリース後に変更されるかを意識してみる
      • RDBの変更について考えるとき、
        • RDBのインスタンス、データベースの設定は、リリース後にほとんど変わらない
        • RDBのデータベース内の構造は、リリース後も変わる可能性がある
        • また、APIサーバーはRDBに依存するため、まずマイグレーションを適用したあとにAPIサーバーを更新する(これがデプロイの順序になる)
    • Createであれば葉から、Deleteであれば根からデプロイする(デプロイの順序が逆転する)
      • Updateはどちらからデプロイしても途中で壊れるため、Update型の変更はデプロイしないこと
    • PRがデプロイの単位である。アーキテクチャ上の異なる2要素のデプロイを1つのPR内で行えるのは、デプロイの過程で壊れないものだけである
      • 壊れる可能性のあるもの、反映に時間のかかる要素に依存するものは別PRとする
十分に自動化されており、人の操作が必要以上に介入しないこと
  • 順番も自動的に決まるくらいが良い
  • テスト、コードの健全性確認のためのスクリプトの実行がCIで行われること
  • 本番環境データベースのbackfillを行う安全な手順は何か
    • backfillを行うためにはテーブルへの書き込み権限が必要。これはやはり人がやるべきではないか(AIにテーブルへの書き込み権限をもたせるのは少し怖い)
      • 十分にテストした上で、Cloud Run Job上で実行する。一時的にCloud Run Jobを作成して実行し、完了してデータベース内の情報が正しければジョブを削除する
可観測性が十分にあること
  • フロントエンドでは、すべてのリクエストのログを取ることは非現実的である。なぜならフロントエンドは公開されているからである。よって、何らかのエラーが発生したときに、その原因を調査するために使う、程度が無難
  • フロントエンドにSourceMapが正しく導入されていて、エラーの発生場所がわかること
  • OpenTelemetry、構造化ログなどを使う
  • 不必要なキャッシュはできる限り入れたくない。サーバーの応答とユーザーに表示されている情報が異なる可能性が生まれ、観測が大変になるから
    • このあたり、先人がどのように解決しているかが気になる
    • (キャッシュは、更新、削除のタイミングまで考えた上で導入したい。人々はキャッシュを気軽に追加し過ぎる)
  • ログ出力時のコミットハッシュが含まれていること
  • レイテンシの大きいrpcがあるとき、その原因を調査する土台が整っていること
AIが安全に調査できるような土台を整えること(可観測性はここにも繋がる)
  • AI用のアカウントを作成し、適切な権限を与える
  • PostgreSQL上の個人情報を含むテーブルにはアクセスできないようにする
システムに一貫性があること
  • あらゆる新規機能について、機能の追加場所が自明であること(悩まないこと)
    • クリーンアーキテクチャを採用する予定
      • PoCとは、ドメインから見たインフラのインタフェースを定め、それを実装することである
    • webhookをどのように追加するか
      • webhookのパス、リクエスト形式は外部サービスの定める仕様である。これをクリーンアーキテクチャで適切に扱う方法が分からない
      • webhookのログも、protobufのログと同じように出力する
  • コードには、ユーザーの利用する機能を達成するためのコードと、それ以外のコードの2種類存在する。それ以外のコードには次のようなものがある
    • ロギング
    • 自動テスト
    • コードの健全性を確認するコード(Linterプラグインなど)
  • GoのLinterにはexhaustructをつける
  • 不要なコードが残されていないこと
    • これがかなり難しそう(言うは易く行うは難し)
テストが十分に行われていること
  • 状態の組み合わせは限りなく存在する。少ないテストであらゆる状態を網羅できるように心がける
  • 最近、AIの発達により、単体テストよりもe2eテストが重要視されてきているが、個人的には実行が不安定なe2eテストは極力増やしたくないので、やはり単体テストで網羅できる部分は網羅したい
  • また、単体テストにはRDBの状態のテストも含まれる
    • RDBの整合性はアプリにおいて最重要事項であるため、単体テストの段階でしっかりとテストすべきだと考える
  • フロントエンドでは、ダミーデータでもいいのでAPIへのアクセスなしでUIの確認を行えるようにするというのを考えたが、やりすぎだろうか
テストがしやすいこと
  • 外部のサービスに依存することなくユーザーを作成できること
    • 個人的には、dev環境でもいいので、ユーザーを気軽に作れることはテストのしやすさに多大な影響を与えると思っている
  • ドメインとインフラの境界を明確にし、ドメインのテストを外部サービスに依存させない
セキュリティが保たれていること(依存モジュールの脆弱性の検知が行える、第三者に誤って機能を提供しないなど)
  • 開発者が入社したとき、必要以上の情報を見せないこと
  • シークレットな情報の扱いが適切
個人情報の扱いが適切であること
  • アカウント削除申請があったとき、一定期間後に必要な情報を削除する
  • 個人情報の含まれるリクエストの一部をマスクする
障害対応についての仕組みが整っていること(週によって対応する人を変えるなど)
  • 不要なアラートを鎮める手段が自明であること
システムの開発フローが現在のチームに合っていること
変更しやすいこと
  • 可観測性を上げたり、テストを十分に行うことで変更しやすいコードになる
  • 人がシステムについて理解していることも重要である
  • ファストフェイルであること
外部サービスが使えなくなったときのシステムの挙動を理解していること
言語設定、タイムゾーンについての考慮がなされていること
  • BI周りは特に、タイムゾーンの扱いに気を使う必要がある
エラーが十分に網羅されていること

外部サービスへの依存について

今日は朝の9時に起床した。昨日の夜はすぐに寝つけた気がする。僕のいつも見ている配信者が配信していなかったのが大きかったかもしれない。

朝起きてから、Goのエラーについて考えていた。

システムは自社内で完結するものではなく、ほとんどの場合、外部のサービスを利用している。どのサービスもユーザー認証を必ず行うし、最初に実装し始めるのはたいていユーザー認証になるが、このユーザー認証も、FirebaseやSupabaseなどの外部サービスを利用する。そうすることで、パスワードを自社で持つ必要がなくなるからだ。

外部サービスの利用方法はいくつか選択肢がある。まずは公式のSDKが用意されている場合。公式のSDKが存在するのであれば、それを利用するのが最良の選択肢である場合が多い。

話はそれる

…ここは特に深く考えていなかったが、本当にすべての場合において、公式のSDKが最良の選択肢なのだろうか。まあ、Googleサービスの公式SDKであればほぼ間違いなく問題ないと判断して使える安心感がある。ここにも信頼性が関わっている。

過去にSpannerを使ったことがある。Spannerにも、公式が用意したライブラリがあり、通常はそのライブラリを使ってSpannerにアクセスする。Spannerはたまにクエリ実行に失敗することがあるが、それはSpannerが常にクエリ実行に成功するわけではないからである。それを理解しているライブラリは、エラーがもし一時的なものであれば再実行してくれる。

ライブラリが一時的なエラーを吸収してくれるため、ライブラリの利用者まで漏れ出すエラーというのは、ほぼ間違いなく利用者側が原因のエラーである。

話を元に戻す

外部サービスのそのほかの選択肢は、公式のSDKは用意されておらず、APIだけが提供されている場合である。非公式のSDKが存在するかもしれないが、こちらの場合、僕はたいてい自社システムから直接APIを利用する。このとき、APIはHTTPのルールに則っているため、こちら側のエラーは400台、向こう側のエラーは500台におおよそなるが、必ずしもそうではない場合がある。

現時点で、外部サービスを利用する選択肢は公式SDKorAPIくらいしか思いつかないから一旦これで話は進めるとする(ここでAIに「他に選択肢はある?」と聞くと、そのやり取りに脳のリソースが割かれてしまい、頭の中にある思考を書きだすという本来の目的からそれてしまう。だからやりたくない)。公式SDKが行っている主な仕事は、Spannerの例そのものだと思っている。つまり、利用者側に知らせる必要のない外部サービス側のエラーをSDK内で吸収することであると思う。それが主な目的であるため、抽象化についてはあまりされていない気がする。

可用性をわざと下げるGoogleの策略

ここでまた話は変わる(今は本当に、頭の中にある思考をそのまま書き出すことに全神経を使っているので、あまり読み手のことを考えられていない)。

Googleが提供する、可用性がそれほど高くない(99%とする)と明言しているサービスがあった。しかし実際の可用性は高かった(99.999%とする)。このようなときサービス利用者は、99%のほうではなく99.999%のほうを前提としてシステムを組み立てるらしい。99.999%であれば、サービスが使えないのは1年の内の5分程度なので、無視してもいいレベルである。

99.999%の方を信じていたサービス利用者は、その後サービスの可用性が下がったとき、不満を漏らしたようだ。可用性が下がったといっても、明言している99%以下になったわけではない。それなのに不満が出てきたらしい。

Googleはこの不満をなくすため、実際に高い可用性となってしまっているサービスの可用性を意図的に下げるため、たまにサービスを止めるらしい。こうすることにより、サービス利用者は可用性99%を前提としてシステムを組む。

可用性をわざと下げるというのが面白いと思った。これは、『SRE サイトリライアビリティエンジニアリング ―Googleの信頼性を支えるエンジニアリングチーム』という本に書かれていた。

また話を戻す

公式SDKが提供されていない場合、代わりにAPIを利用することになる。APIがHTTPであるとき、レスポンスにはステータスコードなどが含まれている。

…少し仕事のことが頭をよぎった。最近僕が追加した機能がある。その機能は、毎日10時から16時まで1時間間隔で、とある作業を行うジョブである。そのジョブは2つの外部サービスに依存している。一方は公式のSDKを利用しているが、もう一方はAPIを利用している。APIを利用している方は、ドキュメントがあまり整備されていないので、実際に動かし続けないとどんな問題が発生するかわからない。そのようなジョブが毎日10時に実行される。今は2026/09/27 9:58:31であるため、約1分後にそのジョブが実行される。もしかするとそこで何か問題が発生するかもしれない。

公式SDKを利用しているかどうかにかかわらず、この2つの外部サービスについては、どちらも自社システムで再試行を行うようにしている。APIを利用する方では、500台のエラー、または429エラーが発生したときに再試行するようにしている。再試行のアルゴリズムにはExponential Backoff+Jitterを採用している。

今確認すると、朝10時のジョブは正常終了し、データベースの状態も正しかった。ひとまず安心である。…休みの日に自社サービスの状態を確認するのはあまり良くない。

ということでまた話を戻す。いや、一旦休憩しようかな。1時間くらい頭の中にあることを書き出す作業をした。少し疲れた。これらの考察が、いずれ実装レベルにまで落とし込めるといいのだけど。

長く使われるシステムについての考察

久しぶりに日記を書く。

開発段階でどこまで考える必要があるか

ソフトウェア開発では、基本的に長く使われることを想定して作られる。長く使われるソフトウェアの開発では、短命のプログラムを書くときに比べて多くのことを考える必要がある。安定して稼働できるか、変更に強いか、すぐに復旧できるか、セキュリティ的に大丈夫か…。

これらの考慮は、まだそのソフトウェアが広まっていない段階で、いったいどこまで考えればいいのだろうか。どこまで考えるべきで、どこからが考えすぎなのか。少なくとも、サービスがローンチされた段階で、そのシステムを監視する人は必要だろう。よって、システムのスケールに限らず、ページャーの設計はきちんとする必要がある。

また、どれだけユーザーが少なくても、お金を扱う部分の不整合は許してはいけないだろう。とりあえずAIに実装させ、それらしく動作するからリリースして良い、というものではない。このように、機能によって、しっかりと作りこむ必要のあるものと、ある程度手を抜いても大丈夫なものがある。これらをどのように考えればいいのだろう。

「ページャー」と「可観測性」は開発段階で考える必要がありそう

なぜお金を扱う部分についてはしっかりと作る必要があるのか。失敗したときを考える。ここでは、僕がユーザーである場合を考える。僕がサブスク加入しようとして、クレジット払いを行った。しかし、支払いを済ませてもサービスを利用できない。そのとき、僕はサービスの問い合わせフォームを使ってそのことを運営に伝える。…もしすぐに対応してくれたのであれば、僕はそのサービスを使い続けるかもしれない。しかも、問い合わせに対する返信に根本原因の調査、対策を行った旨が書かれていれば、逆にそのサービスに好感が持てるかもしれない。…あれ、お金周りで失敗しても意外と大丈夫かもしれない。

もしかすると、失敗することがダメなのではなく、失敗したときの復旧速度が遅いことが駄目なのではないか。つまり、何か問題が発生したときに、その問題が重大なものであれば人に通知する仕組みと、調査する土台が整っていることが重要なのではないか。SREの用語を使うと「ページャー」と「可観測性」あたりが重要ではないか。また、問い合わせ導線もしっかりと整備されている必要があるだろう。

AIに「ユーザーの問い合わせ導線を整備することはSREに含まれているか」を質問したところ、「問い合わせは最終手段。ユーザーが気付く前に、開発者がそれに気付けることが重要」という返答を貰った。つまり、アラートを設計するときは、ユーザーが問題だと感じる部分に忍び込ませればいいということになりそうだ。開発者は、システムの価値やユーザーの行動についてしっかりと理解しておく必要がある。

複数の独立した状態の不整合の扱い方

システムの機能は、どれも単純ではない。サブスク機能を実装する際、必ずStripeなどの外部決済サービスを利用する必要がある。ここでトランザクションが分かれる。Stripeの状態と自社DBの状態の更新を1トランザクション内で行うことはできない。よって、必ず不整合は発生し得る。そこで、不整合をどのように復旧するかや、不整合をエラーログとして出力してアラートで検知できるようにするか、そういったことを考える必要がある。

外部サービスから取得した情報は古いかもしれない、webhookは飛んでこないかもしれない、一時的な障害により情報が取得できないかもしれない…外部サービスの様々なパターンについて、自社サービスはいったいどのように振舞うべきか、そこについてはしっかりと考える必要がある。

一旦終わり

今日は夜遅いので一旦ここまで。…やはり、文章を書きながら物事について考えるのが好きだ。特に、人目に触れる文章を考えているときに考えに集中できる。

近況報告

最近競プロを再開した。
とりあえずAtCoderの過去問をひたすら解いているのだけど、以前よりも沢山の問題が解けるようになっている気がする。
ここ数年間はあまり脳を働かせていなかったので衰えているかと思ったけど、そうでもないみたいだ。

といっても、1日かけても解けない問題もちらほらあり、昨日は双対セグ木の考え方を用いた方法が想定解である問題を解こうとして、解けなかった。
この問題から学んだことをどうしても書き残したいと思い、今回の記事を書いている。

というのも、双対セグ木、もう少し抽象化した完全二分木は、頂点数が O(N) である。
そして、そのうち半分の頂点は区間の情報を持つ。
範囲に対する更新操作を行ったとき、その操作の大半は各要素ではなく区間に対して行われるため、これにより保有している情報が変更される頂点の数は高々 logN 個程度である。

これは、たとえば各頂点がmultisetを持ち、範囲 [0, N) に対して A←max{A, x} という操作を Q 回行ったとしても、空間計算量が O(QlogN) 程度に収まるということである。
一度の更新操作ですべての頂点のmultisetが更新されることはない。

よって、multisetなどの「操作回数に応じてメモリ使用量が増えるようなデータ構造」を各頂点に持たせても問題はない。

問題を解いているときはこの事実に気付かなかった(「頂点にmultisetやpriority_queueを持たせるのは良くない」と思い込んでいた)。
この気付きは自分にとって大きいものだったので今回は書き残した。

広告を消すための記事

90日以上記事を投稿していないと広告が表示されるため、とりあえず記事を投稿する。

11月末に今の会社を辞める。その後の予定は未定。
フリーランスになりたい気もするし、別の会社に正社員として就職するかもしれないし、農業をするかもしれない。
就職するなら Rust を触れるところがいいな。

とりあえず、少なくとも半年はニートでいたいので、その間は仕事のことは全く考えず、貯金を切り崩す生活をする。

この貴重な空白期間に、今までする余裕のなかったゲーム作りなどをしたい。
ゲームじゃなくて、有用なソフトウェアとかでもいい。
息抜きにボランティアをしてもいいかも。

近くに市民プールがあるので、運動不足を感じたらそこに行こうと思う。
今月だけで4回ほど行っているのだけど、僕の泳ぐスピードが遅く、気軽に利用できるのは何でもOKのレーンのみ。
平泳ぎが一番泳ぎやすいんだけど、顔を水に沈めたときに吐きそうになる。いつか治って欲しい。
ずっと顔を出した状態で泳いでいれば吐きそうにはならないけれど、常に背筋を伸ばした状態なので疲れやすく、顔を沈めるのに比べて泳ぎが遅くなる。

顔を水に沈めても吐きそうにならない、何かいい方法はないだろうか…。
というのが最近の悩み。

今は Unreal Engine のインストールが終わり、Unreal Editor を起動している最中。シェーダのコンパイルに時間がかかっているようだ。
起動が完了した後は、とりあえず以下のチュートリアルを進めていこうと思っている。

docs.unrealengine.com

2021年10月26日 日記

今日は733円、体重は84.6kg、問題数は11だった。
典型90の問題を解き進めている。

計算量が O(N2) かかると思い込んでいたら、エラトステネスと似た理屈で O(NlogN) (×コンビネーションの計算量)に抑えられることに3時間くらい気付けなかった。