#
820
どうも〜、おはようございます。マイクです。
時刻は朝七時を少し回ったところ、今日は二千二十六年八月十九日、水曜日ですね。
ここからの時間は「zenncast」、きょうもZennで話題になっているトレンド記事を、ゆったり楽しく紹介していきます。

きょうはですね、ぜんぶで五本の記事を紹介していきます。エンジニアさん向けのちょっとディープな内容が多いんですけど、できるだけ噛み砕いてお話ししますので、コーヒー片手にゆるっと聞いてもらえればと思います。

まず一つ目。
次世代の安い本番運用アーキテクチャの話です。
Next.jsと、Cloudflare Workers、それからデータベースにTurso、この三つを組み合わせて、実際に本番構成を作ってみたという経験談になっています。

キーワードは「月五ドルで、サーバーサイドレンダリングもリアルタイム機能も全部入り」。
めちゃくちゃ安くて魅力的なんですけど、その代わりに「従来のNext.jsの常識が通じない落とし穴がいっぱいあるよ」というところを、かなり丁寧に整理してくれてます。

たとえば、Next.jsの`next/image`コンポーネント。
これ、Cloudflare Workersの環境ではうまく使えないので、そもそもプロジェクトの中で使えないように、ESLintで利用自体を禁止してしまう、と。
「つい癖で書いちゃうものは、lintで物理的に封じる」という割り切りが出てきます。

それからデータベースのTurso。
HTTP越しにクエリを投げる形になるので、一回一回のクエリの往復がけっこう重いんですね。
なので、素直に一問一答でクエリを並べるんじゃなくて、`batch`とか`Promise.all`を使って、「往復の回数」を減らす設計がすごく大事になる。
「一発のクエリはそこそこ重くても、回数を最小限に抑えてトータルを速くする」という発想です。

セキュリティ面でもポイントがあって、React Server Components経由でデータをクライアントに渡すときに、うっかり不要なカラムまで全部投げちゃうと、クライアント側に見せたくない情報も漏れかねない。
なので、SELECT文自体をクライアント向けとサーバー内部向けで分けるなど、「出していいカラムだけを明示的に出す」という設計をちゃんとやろうね、という話も出てきます。

さらに怖いのが、「ビルドも型チェックも通るのに、本番でだけ五百エラーになる」系の罠。
たとえば、`"use client"`が付いているクライアントコンポーネントのモジュールに定義した関数を、サーバー側からうっかり呼んでしまうと、型的にもビルド的にも怒られないのに、本番Workers上だと実行時にコケる、みたいな問題があるそうです。

ほかにも、Workersは基本UTCタイムゾーンで動く一方で、ブラウザはユーザーのローカルタイムゾーンで動くので、その差で日時表示にズレが出てしまうとか、`unstable_cache`に`Date`オブジェクトを突っ込むと文字列化されて戻ってきてしまう、といった、実際に動かしてみないと気づきづらい挙動がいろいろ紹介されています。

こういう「ビルドでは検知できない実行時バグ」をどうやって潰すか、というところで、Playwrightを使ったスモークテストがすごく重要だよ、という話も出ています。
要は「CIで本番に近い環境をガンガン叩いて、変なところで五百を返してないかチェックしよう」というスタンスですね。

一方で、初期ロードのJavaScriptをできるだけ減らしたいので、Markdownの描画はサーバーコンポーネントでやってしまったり、ちょっとしたグラフは重いチャートライブラリを使わずに、素のSVGをサーバー側で生成してしまう方針を取っています。

リアルタイム機能についても、WebSocketサーバーを自前で構築するのではなくて、CloudflareのDurable Objectsを活用することで、スケーリングとかコネクション管理の面倒な部分をオフロードしている。
こうした工夫を積み重ねることで、「運用コストが安くて、速くて、しかもフルスタックで完結する構成」を作っていて、
「手間はそれなりにかかるけれど、一度覚えてしまえば色んなプロダクトに再利用できるよ」という、かなり実践的なおすすめ構成として紹介されていました。

。.。.

二つ目は、自作CLIツール「mikke」の紹介記事です。
これは、Markdownで溜めまくったノートを賢く検索したい人向けのツールですね。

mikkeは「関連度順の全文検索」をしてくれるコマンドなんですが、日本語もちゃんと扱えるのがポイントです。
裏側ではSQLiteのFTSファイブ、フルテキストサーチの機能を使っていて、その中でもトライグラム方式という仕組みで、日本語と英語が混ざったテキストを、辞書なしで検索できるようになっています。

実装はRust製で、単一バイナリ。
各ノートのリポジトリ、つまりフォルダごとに`.mikke`というディレクトリを作って、その中にインデックスファイルを置くスタイルなので、サーバーも、常駐のデーモンプロセスも要りません。
「好きなエディタでMarkdownを書いて、ディレクトリだけ決めておけば、その場で高速全文検索できる」という感じです。

ノートにはYAMLのフロントマターで、タイトル、タグ、サマリーなんかを任意で付けることができて、特定のアプリにロックインされないのも嬉しいところです。
将来アプリを変えたくなっても、MarkdownとYAMLさえ読み書きできれば、どこにでも持っていける設計ですね。

面白いのが、オプションで「意味検索」をローカルで実行できるところ。
BM二五という古典的な全文検索スコアと組み合わせた、ハイブリッド検索も用意されていて、
クエリとノートの内容を外部APIに送らずに、「ローカルで、意味ベースの検索ができる」ようにしてあります。

これを活かすと、過去のノートをAIエージェントの「長期記憶」として使えるんですね。
エージェントが何か判断したいときに、「mikkeでローカルノートを検索して、その結果だけを参照する」という形にすれば、プライバシーを守りつつ、賢く過去情報を活用できる、と。

記事の中では、既存ツールとの比較もされています。
例えば、テキスト検索の定番ripgrepに比べると、「もっと賢く、関連度順に返してくれる」。
一方で、AI検索ツールのkhojなんかと比べると、「もっと軽く、ローカル完結で扱いやすい」。
ObsidianのCLIツールとも違って、「特定アプリのエコシステムに縛られない」、そういうポジショニングを狙ったツールだという説明でした。

ノートをMarkdownで書き溜めてる人には、かなり刺さる内容だと思います。

。.。.

三つ目は、Rust製の静的解析ツール、BiomeとOxlintをガチ比較した記事です。
テーマは、「なんでこんなに速度差が出るのか?」というところですね。

通常の、型情報を使わないJavaScript・TypeScriptのlintだと、Oxlintの方がBiomeよりも、条件によってはおおよそ五倍くらい速いケースがある。
ところが、型情報をちゃんと使ったlintになると、逆にBiomeの方が速くなる、という面白い逆転現象が起きています。

その理由のひとつが、「そもそも何をどこまで解析対象にしているか」という設計の違いです。
Biomeは、JavaScriptやTypeScriptだけじゃなくて、CSSやJSONのlintもこなせますし、複数ファイルをまたいだ解析なんかも一回のパスでやろうとする、かなり多機能な設計です。
一方Oxlintは、基本的にJSとTS周りにターゲットを絞っているので、純粋にこなしている仕事量が違う、と。

AST、つまり構文木の持ち方にも結構差があります。
Oxlintは、ASTをアリーナと呼ばれる連続したメモリ領域にガッとまとめて確保して、lintが終わったら丸ごと破棄する前提で作られています。
それによって、一回の走査でたくさんのルールをチェックしていけるようになっていて、「とにかく速さ重視」の設計ですね。

対してBiomeは、元のソースコードを空白やコメントまで含めて完全に復元できるような木構造を持っていて、ノードごとに参照カウント付きで細かく管理しています。
そのおかげで、フォーマッタだったり、エディタとの高度な連携にはすごく強いんですけど、その分、ノード一個あたりのメモリ管理コストは高くなります。
「後からいろいろ使い回せるリッチなASTを取るか、速度を取るか」というトレードオフですね。

型を使うlintのところでは、方針がさらに違います。
Oxlint側は、本物のTypeScriptコンパイラを別バイナリとして高速に回す、いわば「tsgolint」という仕組みを使っています。
つまり、型解析そのものはTypeScript公式の型システムに全部任せる代わりに、「そこまで行くまでの手順」はそれなりに重くなる。

一方Biomeは、TypeScriptコンパイラには依存しない、独自の型推論エンジンを持っていて、
「検出できるエラーの範囲はちょっと妥協しつつ、その代わり高速に動かす」という方針を取っています。
この設計の違いが、型認識lintではBiome側の方が有利になる、という結果に繋がっているわけですね。

記事の結論としては、「Rustで書かれているかどうか」じゃなくて、
・どこまでを解析対象とするか
・ASTをどんなデータ構造とメモリ戦略で持つか
・型情報をどう扱うか
この三つの設計の違いが、用途ごとの速度差になって現れているんだ、という整理になっていました。

「とにかく速いJS/TS専用lintがほしい」のか、
「フォーマットやエディタ統合まで含めた全部入りがほしい」のかで、選択肢が変わってきそうですね。

。.。.

四つ目は、モバイルアプリ界隈の新星、「Whisker」というフレームワークの紹介です。
これはRustで、iOSとAndroid両方のアプリを一つのコードベースから作れるマルチプラットフォーム開発フレームワークになっています。

イメージとしては、FlutterとかReact Nativeみたいな立ち位置なんですけど、大きく違うのは「仮想マシンを使わない」ところ。
RustのコードをOSの上で直接動かすことで、アプリの起動時間も、実行速度も、ものすごく速くできます。
そのおかげで、アプリのサイズも小さく抑えられる、というのがセールスポイントになっています。

Rust採用のメリットも大きくて、まずメモリ安全性が高い。
それから、tokioとかreqwest、SQLiteまわりのライブラリなど、既存のRustエコシステムをそのまま使えるので、バックエンド寄りのロジックなんかをかなり再利用できます。
画像処理とか、ローカルで動かすLLMみたいな重い処理も、Rustが得意な領域なので、そういうニッチも攻めやすい。

UIはRustのマクロで宣言的に書けるようになっていて、Rust Analyzerに対応しているので補完も効きますし、専用のフォーマッタも用意されているそうです。
subsecond、つまり一秒未満単位でのホットリロードも組み込まれていて、コードを変えたらほぼ即座にアプリに反映される、開発体験重視の作りになっています。

レイアウトやスタイルの仕組みも凝っていて、基盤にはiOS側はUIKit、Android側はAndroid Viewを使いつつ、ByteDanceがオープンソースで出している「Lynx」というCプラスプラス製のエンジンを流用しています。
これによってCSS互換のスタイリングができたり、Webフロントエンドに近い感覚で、かなり豊富な表現が可能になっています。

現時点ではまだアルファ版で、
今後は「ピュアRustの独自レンダリングエンジン」に移行して、さらに性能とバイナリサイズを良くしていきたい、というロードマップが語られています。
商用利用に耐えられるレベルを、年内のゴールにして開発が進んでいるそうなので、Rustでモバイルアプリやりたい人には、今からウォッチしておく価値はかなりありそうです。

。.。.

そして五つ目。
最後は、大規模言語モデルが「考えすぎてしまう問題」を、実験的に制御してみたお話です。

題材として出てくるのは、「Qwen三点八、パラメータ二十七ビリオン」をローカルで動かしたときの挙動。
曖昧なタスクを投げると、モデルが`<think>`タグの中で延々と推論を続けてしまい、なかなか回答やツールコールに到達しない。
そのうちトークン上限に達してしまって、結局何も答えを返せないまま終わる、という問題が起きていたそうです。

そこで筆者が使ったのが、llama.cppに用意されている、`reasoning_budget_tokens`と`reasoning_budget_message`という設定項目です。
これを使うと、一回のやり取りで、思考に使っていいトークン数に上限を設けることができます。
で、その上限に達しそうになったら、自動的に「そろそろ答えを出してください」というメッセージを`<think>`の中に挟み込むような制御を入れた、と。

どのくらいのトークン数がちょうどいいのかは、OptimalThinkingBenchというベンチマークを使って検証しています。
思考トークンの予算を、ゼロ、千二十四、二千四十八、四千九十六、八千百九十二、一万六千三百八十四、という複数パターンで試して、
UnderthinkingBenchという別の指標も使いながら、「正答率」と「かかる時間」のバランスを見たところ、
四千九十六トークンあたりが一番バランスが良かった、と。
この設定にすると、生成トークンの上限にぶつかって失敗するケースもなくなったそうです。

さらにこの設定を、OpenCodeの「バリアント」、たとえばロー、ミディアム、ハイみたいなモードにマッピングして、
タスクの性質に応じて、どのくらい考えさせるかを切り替えられるようにしています。
その結果、マインクラフトのクローンを作る、みたいな大きめのタスクでも、ユーザーが途中で口を挟まなくても、設計から検証まで最後まで走り切れるようになった、と報告されています。

そのほかにも、モデルの「考えすぎ」を抑えるテクニックがいくつか整理されています。
システムプロンプトで「推論は短めにまとめること」と明示する、とか、
`<think>`の中に強制的にメッセージを挿入して「そろそろ締めてね」と伝える、
logit biasを使って`</think>`トークンが出やすくなるように調整する、
あるいは追加学習で、「短く収束する思考パターン」をモデルに覚えさせる、といったアプローチですね。

まとめると、「性能の高い推論モデルほど、放っておくと延々と考え続けてしまうことがあるので、ハードウェア資源や時間とのバランスを見ながら、ちゃんと“考えすぎブレーキ”を設計してあげよう」という内容でした。

。.。.

というわけで、きょうのzenncastはこのへんでお時間となりました。
おさらいすると、
まずは、Next.jsとCloudflare Workers、Tursoで、月五ドルの安くて速い本番構成を組んだ経験談。
続いて、日本語対応のMarkdownノート全文検索CLI、「mikke」の話。
三つ目は、Rust製lintツールBiomeとOxlintの速度差を、設計の違いから解き明かす記事。
四つ目は、RustでiOSとAndroidを一気に書ける新フレームワーク「Whisker」。
そして最後に、大規模言語モデルの「考えすぎ」を、トークン予算やプロンプト設計でうまく制御する工夫、というラインナップでお届けしました。

気になった記事があれば、詳しい内容は番組のショーノートにまとめてありますので、あとでじっくりチェックしてみてください。
番組への感想や、「こんなテーマ取り上げてほしいよ」というリクエストも、どしどしお待ちしています。

それでは、きょうも良い一日をお過ごしください。
お相手はマイクでした。また次回のzenncastでお会いしましょう。

Related episodes

内容の近いエピソードを推薦しています