どうもー、おはようございます。マイクです。
朝七時になりました。「zenncast」今日も始めていきましょう。
今日は二〇二六年九月二十六日、土曜日。
連休明けの方も、これからお仕事に向かう方も、そしてお休み満喫中の方も、よかったらこの時間、おつきあいください。
この番組では、Zennで話題になっているトレンド記事をピックアップして、ゆるっと、でも中身はしっかりめにご紹介していきます。
今日は全部で五本、記事を紹介していきます。
技術的な話もあれば、設計の失敗談、AIゲートウェイの話、そして競馬と機械学習みたいな面白い組み合わせまで、バラエティ豊かにそろってますので、通勤中や朝ごはんのお供に、耳だけ貸してもらえればうれしいです。
それでは、さっそく一つ目からいきましょう。
一つ目は、「連休明けにうっかり本番データベースを初期化しちゃった話」です。
Prismaを使っている方にはおなじみの、`migrate reset` というコマンドがありますよね。本来はローカル開発用に、データベースをまるっと初期化して、マイグレーションをやり直すためのものなんですが……。
この記事の筆者は、連休明けにこの `migrate reset` を「ローカルのつもりで」実行したところ、なんと `.env` に本番の `DATABASE_URL` が残っていて、誤って本番データベースを初期化してしまった、というかなり冷や汗モノの体験談を共有してくれています。
幸い、Neonというサービスのバックアップ&リストア機能があって、一日前の状態まで戻すことができたので、被害はほぼなかったそうです。
ただ、サービスがもっと育っていて、ユーザーもデータも増えているタイミングで同じことをやっていたら、取り返しのつかないレベルの事故になっていただろう、とかなり真剣に振り返っています。
原因を冷静に整理していて、ポイントはいくつかあります。
まず、Turborepoの配下にある `.env` ファイルが「どんなURLでも入ってしまう」状態になっていて、安全側に倒れていなかったこと。
それから、`migrate reset` を打つ前に、今の `DATABASE_URL` が本当にローカル向けなのか、本番ではないのかをチェックする仕組みが何もなかったこと。
さらに、連休前に「ちょっと本番のURLを `.env` に入れて動作確認して、そのまま中途半端な状態で放置してしまった」ことが重なって、事故につながってしまいました。
じゃあどう対策するのか、というところも具体的に書かれています。
たとえば、破壊的なコマンドを実行するときは、必ずスクリプトを一枚かませて、`DATABASE_URL` がローカルホスト、つまりローカルのデータベースを指しているかどうかをチェックするようにすること。
そもそも、本番環境の接続情報はローカルの `.env` には置かない、という運用ルールを徹底すること。
Neon側のバックアップ保持期間も、プランごとにちゃんと確認しておいて、「もしものとき、どこまで戻せるのか」を事前に把握しておくこと。
さらに面白いのが、最近よく使われるCursorみたいなAIコーディングエージェントにも、「本番のURLは扱わない」「破壊的な操作は自動でやらない」といったルールを明示しておく、という視点です。
人間だけじゃなくて、AIに対してもガードレールを用意しておくべき時代になったんだな、というのが伝わってくる内容でした。
そして最後に、「長期休み前には、そのときの作業状態をちゃんと片付けて、メモも残しておこう」という、すごく人間くさい、でも超大事な教訓で締めくくっています。連休明けあるあるにヒヤッとした方、多いんじゃないでしょうか。
。 。 。 。
二つ目は、AzureのAPIM、API Managementを使ってAIゲートウェイをどう構成するか、という記事です。
これ、説明の仕方がめちゃくちゃ上手くて、「飲み放題の居酒屋」にたとえながら、APIMの各要素を理解できるようになっています。
まず、APIM全体を「お店」、Backendを「厨房」、ここではFoundryのGPTが動いている場所ですね。
APIは「ホールスタッフ」、お客さんからの注文を受けて、厨房に伝える役。
Productは「飲み放題コース」、たとえば松・竹・梅みたいなコースごとのプラン。
サブスクリプションキーは「リストバンド」で、「あなたはこのコースで飲めますよ」という証明書、みたいなイメージで説明しています。
この対応づけがあるおかげで、「APIMのどの設定が何をしているのか」が直感的に頭に入ってきます。
構成としては、BackendにFoundryのGPTを登録して、マネージドIDとIAMを設定することで、「利用者にはFoundryのAPIキーを配らない」ようにしています。
つまり、実際にGPTにアクセスできるのはAPIMだけ。お客さんはお店に注文を出すだけで、厨房の中身や秘密のレシピには触れられない、という安全な構成です。
API側では、例えば `/openai/v1/responses` といったオペレーションを定義しておきます。
利用者がリクエストを投げてくるときには、その人のサブスクリプションキー、つまりリストバンドをゲートウェイ側でしっかり検査します。
一方で、Backendに渡すときには、そのキー情報は削除して、代わりにマネージドIDで認証してGPTにアクセスする、というポリシーを設定しています。
「お客さんのリストバンドは店の入り口だけで確認して、厨房に注文を持っていくときには、店員の権限でまとめて出す」みたいなイメージですね。
さらに、Productごとにレート制限とクォータを組み合わせて、「松・竹・梅」みたいなコース別の上限を作る話も出てきます。
一分あたりのリクエスト回数に制限をかけつつ、`llm-token-limit` という形で、「一分あたりのトークン数」と「一日あたりのトークン数」をコントロールできるようにする。
ちょっとリッチな「松コース」はたくさんトークンを使えて、「梅コース」は控えめ、みたいに段階的にプランを作るやり方が紹介されています。
ログ周りもちゃんとしていて、GatewayのログをLog Analyticsに送りつつ、レスポンスヘッダーに `x-tokens-consumed` みたいな値を仕込んでおくことで、どのProductやどのサブスクリプションが、どれくらいトークンを消費しているのかを、KQLで集計できるようにしています。
単にゲートウェイを通すだけじゃなくて、「誰がどれくらい使ったか」「どれくらいコストがかかっているか」を後から分析しやすくしているわけですね。
最後に、Backend・API・Productの役割や、レート制限とクォータの違いなども、この「飲み放題居酒屋」の比喩で整理されています。
そして、最近出てきているAI専用のAI Gateway Tierとの違いにも軽く触れつつ、「APIMでこういう構成を組むと、こういうことができるよ」という位置づけがわかりやすくまとめられていました。
AIゲートウェイをどう設計するか悩んでいる人には、かなり腹落ちしやすい内容だと思います。
。 。 。 。
三つ目は、フォームの画面遷移をxStateのステートマシンで表現しようとして、うまくいかなかった失敗談と、その中から得られた教訓のお話です。
入力内容によって、次にどのページに進むか、どの選択肢を見せるかが変わる、ちょっと複雑なフォームってありますよね。「エビアレルギーがありますか?」とか、「ベジタリアンですか?」みたいな条件で、出す項目が変わるタイプのやつです。
この記事の筆者は、これをxStateでモデル化しようとして、「一ページを一つの状態」としてステートマシンを組み立てていきました。
ところが、「エビNG」「ベジタリアン」みたいに条件が増えていくと、それぞれの組み合わせごとに状態が増え続けてしまって、「状態の組み合わせ爆発」が起きてしまいます。
ページ数が増えるたびに、状態がどんどん増殖して、もう管理しきれないレベルになってしまったわけです。
さらに、既存のストアとxStateを併用していたために、「分岐ロジックとデータの場所が二重になってしまう」という問題も出てきました。
あるページから次のページに進む条件と、前のページに戻る条件、それぞれに似たようなロジックを書かなきゃいけなかったりして、ロジックがあちこちに散らばってしまいます。
結果として、「どこを直せばいいのか」「この挙動はどこで決まっているのか」が、わかりづらくなってしまったんですね。
そこで方針転換として、「どのページを表示するか」と「そのページでどの選択肢を見せるか」という情報は、ステートマシンの状態としては持たない、という設計に変えています。
代わりに、フォームに入力されたデータを一元的に持っておいて、そこから「今どのページを見せるべきか」「この条件ならどの選択肢を出すべきか」をcomputed、つまり計算で導き出すようにしました。
このやり方だと、条件が増えても「入力データから計算する処理」が増えるだけで、ステートマシンの状態そのものは増えません。結果として、組み合わせ爆発が起きなくなり、ロジックも一か所にまとまってスッキリした、というわけです。
筆者が最後に強調しているのは、「ステートマシンは、決済や認証のように、状態のパターンが有限でちゃんと列挙できる場面に絞って使うべきだ」という点です。
逆に、「データから計算して決められるもの」を無理に状態として増やしてしまうと、今回のように状態だらけになって破綻してしまう。
だから、状態と計算可能な値はしっかり分けて、ステートマシンには「本当に状態として持つべきもの」だけを載せようね、という教訓になっています。
フォーム設計で迷っている方にも、「あ、ここはcomputedに逃がした方がいいんだな」と気づかせてくれる内容でした。
。 。 。 。
四つ目は、Angularのバージョン二十二・二・ゼロで入った、「コンポーネントクラスのprivateフィールドをテンプレートから参照できるようになった」変更について、その意味づけを考え直している記事です。
背景にあるのが、TypeScriptの `isolatedDeclarations` への対応です。
テンプレート向けのフィールドを `protected` にしておくと、型宣言ファイルを生成するために、いちいち面倒な型注釈を書かないといけない、という問題がありました。
今回、テンプレートから `private` なフィールドも参照できるようにしたことで、そのあたりの型注釈の負担を減らして、型推論を素直に使えるようにした、という狙いがあります。
この新しい前提のもとで、「アクセス修飾子の意味をどう整理し直すか」というのが記事のテーマです。
筆者はまず、「テンプレートもコンポーネント実装の一部とみなす」という立場に立って、こう整理しています。
`public` はコンポーネントの外部から呼び出すAPI。
`protected` は継承用だけれど、コンポーネント設計ではあまり出番がない。
`private` はクラスとテンプレートで共有する内部API、という位置づけです。
つまり、「テンプレートから見える=外部公開」ではなくて、「テンプレートも含めて一つの実装の中」と考えるわけですね。
じゃあ、「テンプレートには見せたくない、もっと奥の実装詳細はどう隠すのか?」という疑問に対して、二つの方針が紹介されています。
一つ目の方針は、コンポーネント内にも情報隠蔽の段階を残すやり方です。
TypeScriptの `private` は、テンプレートとクラスが共有する内部APIとして使う。
一方で、ECMAScriptの `シャープprivate`、シャープ付きのフィールドは、クラスの中だけで使う、本当の実装詳細用にする。
こうすることで、「テンプレートから見えるprivate」と「テンプレートからは絶対に見えないシャープprivate」という、二種類のプライベートを使い分けて、テンプレートからの可視性をコントロールできます。
二つ目の方針は、コンポーネント内部ではあえて隠蔽をせず、「テンプレートから隠したい処理や状態は、別オブジェクトに切り出す」というやり方です。
具体的には、ViewModelとかFacadeといったオブジェクトを用意して、そこに複雑なロジックや状態をまとめる。
コンポーネントからは、そのオブジェクトのインターフェースだけをテンプレートに見せることで、「オブジェクトの境界」で情報を隠す、という設計になります。
どちらのアプローチを取るにしても大事なのは、「コンポーネントとテンプレートを同じ文脈として扱うのかどうか」をチームでちゃんと決めておくこと。
そのうえで、「publicはこういう意味」「privateはこう扱う」といったルールをプロジェクトの中で一貫させておくことが重要だ、と記事はまとめています。
Angularを本番で使っているチームには、設計指針としてかなり役に立ちそうな内容でした。
。 。 。 。
そして五つ目、最後は少し毛色が変わって、「競馬の売買戦略候補を探すために集めた論文を、自動でふるい分ける話」です。
ここで登場するのが「Jev」というモデルで、これは「はい/いいえと、その確率だけを返す」専用のモデルになっています。
これを、いわゆる普通の大規模言語モデル、LLMと、速度やコストの面で比較した実験が紹介されています。
やっていることとしては、論文の要旨から、
「これは競馬ベッティング、つまり競馬の賭けに関する論文かどうか」
「実装可能な売買ルールが書かれているか」
「戦略のタイプはどんなものか」
「日本のJRAに応用しやすいか」
といったポイントを、Jevに評価させます。
その結果、百本ある論文の中から、「候補になりそうなもの」を十四本まで絞り込むことができました。
キーワードだけを使った簡単なルールだと、「実装可能」と誤判定してしまう論文が結構あったのに対して、Jevを使うと、同じ論文を何度判定させてもほぼ同じ結果が返ってきて、ノイズをかなり減らせたそうです。
「はい/いいえに特化したモデル」にしたことで、判定のブレが小さくなっている、というのがポイントですね。
性能面の比較では、Cloudflare Workers AIで動かしているLlama三・三、七十ビリオンのモデルと比べています。
中央値で見ると、Jevの方が約三倍速く、遅延のばらつきも少なかった。
一方で、一件あたりの推定コストは、Jevの方がLlamaよりやや高め、という結果でした。
公式でうたわれている「四十倍から二百倍速い」といったほどの差は出なかったものの、論文一万本規模でまわしても、数ドル・数時間で終わるくらいのレンジなので、実務では「わずかなコスト差よりも、速さと判定の安定性を重視してJevを選ぶのが良さそうだ」という結論になっています。
ただし、ここでおしまいではなくて、仕分けした十四本の論文を実際に読み込んで、バックテストまでやってみたところ……。
データの制約などがあって、論文どおりに再現できないものが多く、最終的に「本当に利益が出る戦略」は見つからなかったそうです。
そこも正直に書かれていて、「モデルでフィルタリングしたからいきなり儲かる」という話ではないよ、という冷静な視点が印象的でした。
最終的には、「とにかく論文を広く集めて、そのあとでJevを使って“後続の手作業に回す本数”を絞り込むフィルタとして使う」のが現実的で有効、というまとめになっています。
大量の情報から「人間が読むべき候補」を絞り込む役割に、はい/いいえ専用モデルがけっこう効く、というのがおもしろいポイントでした。
。 。 。 。
というわけで、きょうのzenncast、五本まとめてご紹介してきました。
おさらいすると、
連休明けにPrismaの `migrate reset` で本番データベースを初期化してしまったけれど、Neonのバックアップで救われたという冷や汗体験と、その安全策の話。
それから、APIMと飲み放題居酒屋の比喩で、AIゲートウェイをどう構成するかをわかりやすく解説した記事。
三つ目は、xStateでフォームをステートマシン化しようとして、状態が爆発してしまい、「状態にしないで計算で求めるものはcomputedに寄せるべき」と学んだ失敗談。
四つ目が、Angularでテンプレートからprivateが触れるようになった変更をきっかけに、アクセス修飾子の意味や情報隠蔽をどう再定義するかを整理した記事。
そして最後に、競馬の論文を自動でふるい分けるために、Jevというはい/いいえ専用モデルをLLMと比べた実験と、その現実的な使いどころ、というラインナップでした。
気になった記事があれば、詳しい内容や元の記事へのリンクはショーノートにまとめてありますので、ぜひそちらもチェックしてみてください。
この番組「zenncast」では、感想や質問、「こんなテーマを取り上げてほしい」なんてリクエストもお待ちしています。
開発でハマった話でも、最近読んでおもしろかった技術記事でも、ライトな内容で大丈夫なので、気軽に送ってもらえるとうれしいです。
それでは、そろそろお時間です。
ここまでのお相手はマイクでした。
また次回のzenncastでお会いしましょう。ではでは、いってらっしゃい。