どうも、こんばんは。マイクです。
今日も一日、おつかれさまでした。
七月十九日、日曜日の朝七時を回りました。「zenncast」、今日も元気にお届けしていきます。
この番組では、エンジニアのみなさんに向けて、Zennに上がっている今ホットな記事や、役に立つ知見を、ラジオ感覚でゆるっとご紹介していきます。
今日も、Zennで話題になっているトレンド記事をピックアップしてご紹介していきますね。
さて、きょう紹介する記事は、ぜんぶで五本です。
AI文章の“AI臭さ”の話から、Aurora PostgreSQLのちょっと怖い挙動、GitHubのリリース運用、Lambdaで小さなオープンLLMを動かす話、そしてPlaywrightでがっつりEツーEテストを回していく事例まで、けっこうバラエティ豊かです。
順番にいってみましょう。
まず一本目。
「AIが書いた日本語の『AI臭さ』を、語彙じゃなくて“文のリズム”から機械的に検出して直す」という、Agent Skill「natural-japanese」を紹介している記事です。
AI文章って、「禁止語を抜けば自然になるでしょ」と思いがちなんですけど、筆者いわく、ほんとの違和感の正体は、語彙じゃなくて“リズム”だろうと。
つまり、文の長さがやたら均一で、メリハリがない。その結果、どうしても眠たく感じてしまう。そこに着目しています。
で、おもしろいのが、日本語のリズムを測るために「モーラ」、いわゆる日本語の拍ですね、これを使って文の長さを数値化して、そのばらつき具合をチェックするlintを組み込んでいるところです。
自然な文章って、短い文がトンと出てきたり、少し長めの説明が入ったり、リズムに揺らぎがあるんですけど、AIが書くと、ここが妙にそろってしまう。そこを機械的に検出する、というアプローチですね。
さらに、人間が書いた文章百三十七本と、AIが書いた文章四百六本を解析して、モデルごとの特徴も見ています。
その結果、gptごーてんろくは、語彙そのものはとてもきれいなんですが、文の長さとか段落構造のリズムが極端に均一で、このlint上いちばん「リズムでバレるAI」だったそうです。
一方で、Fableファイブは段落構造はかなり自然なんだけど、それでも文長のリズムはそろいがちで、「モデルを変えればAI臭が消える」というほど甘くはない、という結論になっています。
加えて、よく言われがちな「体言止めが多いとAIっぽい」「同じ言葉で文頭を繰り返すとAIっぽい」「『最後に』『まさに』はAI臭い」といったイメージも、統計的に見ると逆で、人間の文章のほうがよく使っていたらしいんですね。
つまり、われわれ人間も、わりとくどいリズムとか、機械的なパターンを平気で書いているのに、自分では気づきにくい。ここがポイントです。
そこで筆者が提案しているのが、
機械でリズムの問題を検出する → AIに修正案を書かせる → 最後に人間が読んで「直すか、そのまま残すか」を判断する、という三段構えのループです。
“AIくささ”を消したいけど、全部手で微調整はしんどい、という現場に対して、かなり現実的な落としどころを示している記事でした。
。。。。
続いて二本目。
Aurora PostgreSQLで、「大量デリートしたのに、リーダーインスタンスだけが、削除済み行をなんと数日間も返し続ける」という、ちょっとゾッとする事例の切り分けをまとめた記事です。
まず筆者は、Auroraの仕組みから整理を始めます。
Auroraのレプリカラグって、通常は百ミリ秒未満なんですよね。なので、「数日間続く矛盾は、ラグだけでは説明できない」と。
さらに、PostgreSQLのMVCCと、visibility map、いわゆるVMの仕様を確認して、「VMが古かろうが、普通は“間違った結果”は返さないはず」という前提もおさえます。
そのうえで、実際に問題が起きているSELECT文の実行計画を見ていくと、問題のクエリは主キーだけを返す単純なものなんですが、リーダー側では index only scan が選ばれていた。
で、このreader側で index only scan を無効化すると、削除済み行が返ってこなくなる。さらに、VACUUM の直後も一時的に症状が消える。
ここから、「大量DELETEでデッドタプルがたくさん溜まっていること」と、「readerでindex only scanを選んでいること」が、かなり濃厚に関係しているだろう、という結論にたどり着きます。
ただ、なぜリーダー側だけで、visibility mapが長期間おかしな状態になったのか、その決定的な原因までは特定できていません。
キャッシュされたVMページの不整合など、いくつか仮説は挙げているものの、「ここだ」とまでは言い切れない、モヤっとしたところは残っています。
とはいえ、同じような事象に遭遇したときの確認のステップはかなり整理されていて、
まずレプリカラグを疑う、次にMVCCやvisibility mapの仕様をちゃんと押さえる、そのうえで実行計画とルーティングを確認する。
さらに、dead tupleの蓄積具合やVACUUMの状態を見ていく、という流れが提案されています。
あわせて、監視や設定の観点で、「どこをモニタリングしておくと、この手のトラブルを早期に察知できるか」も整理していて、Aurora Postgresをプロダクションで使っているチームにはかなり実務的な内容になっていました。
。。。。
三本目。
GitHubの「Immutable Releases」、つまり一度公開したリリースのタグやアセットを削除したり、同じ名前で再利用できなくなったことで、従来よくあったリリースフローが一気に“危険ルート”になっている、という話です。
昔からよくあるのが、「GitHub Releaseの公開をトリガーに、エヌピーエムパブリッシュを走らせる」みたいな構成ですよね。
これが今の仕様だと、もしパッケージの公開に失敗しても、タグとリリースの記録だけは永遠に残ってしまう。
そうなると、そのバージョン番号は二度と正しく使えないし、「実物の成果物は世の中に存在しないのに、『リリースした』という記録だけが残っている」という、ちょっと気持ち悪い状態になります。
これに対して筆者は、「何をトリガーにするか」じゃなくて、「何が成功したら“リリースした”と言えるのか」、ここから考え直すべきだと主張しています。
その鍵になるのが、「成果物の本体はどこにあるのか」という視点で、これによって三つのパターンに分けています。
ひとつめ。NPMやPyPIのように、レジストリ上にあるパッケージこそが“本体”の場合。
このケースでは、まず publish を先に成功させる。
パッケージ公開がちゃんと成功したことを確認してから、その結果としてGitタグやGitHub Releaseを作る。
つまり「本体 → 記録」の順番にするのが正しい、と説明しています。
ふたつめ。GitHub Releaseにアップロードしたバイナリが本体で、それを各所からダウンロードしてもらうタイプのツールの場合。
ここでは、全バイナリが揃ったGitHub Releaseの公開が「成功点」になるので、まずそこでリリースを完了させてから、そのReleaseを起点にHomebrewだったりNPMのミラーだったりに反映させていく、という流れにするべきだと。
みっつめ。PackagistやGo Modulesのように、「Gitのタグ自体がリリースそのもの」に相当する場合。
このケースだけは、これまで通りタグプッシュが正しいトリガーになるし、そこを中心にフローを設計すれば問題ない、という整理になっています。
全体として、「依存される本体(親のデータ)を先にちゃんと作る。
その親を指すタグやReleaseといった“記録(子のデータ)”は、あとから作る。」
こういう、データベースの参照整合性みたいな考え方でリリースフローを組み立てよう、というメッセージが印象的な記事でした。
。。。。
四本目。
Lambdaの制約、たとえばメモリは最大十ギガバイト、CPUのみ、実行時間は十五分、コンテナイメージも十ギガバイトまで、という条件の中で、「小さなオープンLLMを量子化してLambdaの上で動かせるのか」をガチで検証した記事です。
結論としては、GGUF形式の、パラメータ数が一から数十億くらいの小さめモデルであれば、コンテナに同梱してCPU推論するのは現実的だ、という結果になっています。
特に、Qwenスリー・ワンポイントセブンビーの「nothink」モードが、品質、速度、コストのバランスがかなり良かったそうです。
実測では、一から三ビリオンパラメータ級の量子化モデルなら、ウォーム状態、つまりコールドスタートじゃない状態であれば、一リクエストあたりのコストはだいたい零点零五円から零点二円くらい。
このくらいのコストで、「テキストの整形」「要約」「軽めの分類」くらいのタスクには十分な品質が出ていたという話です。
一方で、課題としては、コンテナのプルとモデルのロードがとにかく重い。
デプロイ直後などのコールドスタート時には、レイテンシが数百秒、つまり数分単位に膨らむこともあった、ということで、「Lambdaに小さいLLMを詰め込めばなんでもハッピー」というわけではない、というのが正直なところです。
なので著者は、「継続的に呼ばれるワークフローには向いているけれど、たまにしか呼ばれないジョブとか、単発用途だと、コスト面でかなり不利になりがち」と指摘しています。
常にある程度のトラフィックがあるバッチ処理やバックエンドの裏方タスクに載せるのはアリだけど、「一日に一回だけ叩く管理系機能」みたいなのには、あまり向いていない。
LambdaでLLMを動かすときの現実的な線引きが、よく見える記事でした。
。。。。
最後、五本目。
EツーEテストをめぐる、「遅いし、壊れやすいし、誰もレポートを見ない」という、よくある悩みに対して、筆者がPlaywrightとGitHub Actionsを組み合わせて、百スペック前後のEツーEを、プルリクごとに六〜八分で回せるように作り替えた事例です。
ポイントは、「設定をいじれば速くなる」という話ではなくて、「テストそのものを、並列実行を前提に再設計した」ことにあります。
まず、各テストが使うデータは、全部DBへの直接INSERTを行うFactoryで自前生成。
テストが終わったら、関連レコードやストレージ内のファイルまで、きれいさっぱり削除してしまう。
これで、テストごとのデータが完全に独立するようになって、並列に動かしても互いに干渉しません。
ログイン処理も、いちいちUIからログインさせずに、事前にセッションを作ってCookieを読み込ませるだけで済ませています。
さらに、POM、ページオブジェクトモデルとか、レイヤー構造をきちんと設計して、「待機の書き方」などのルールを共通化。
テストごとに書きぶりがバラバラ、という状態をなくして、読みやすさと保守性を上げています。
並列実行の設計も凝っていて、Playwright側では、負荷の重いテストと軽いテストをprojectで分けて、ワーカー数を調整。
GitHub Actionsでは、八コアのランナーを使って、matrix構成で二段階の並列化をかけることで、全体の時間をギュッと短縮しています。
もちろん、並列にするとインフラ側の問題も出てきます。
PostgRESTのコネクションプールが枯渇したり、Kongでkeep-alive関連のバグが顔を出したり。
こうした問題に対しても、コンテナをテストごとに再生成するようにしたり、keep-aliveの設定を見直したりと、根本的な対策を行っています。
さらに、「どこまで並列度を上げられるか」を感覚ではなく数字で判断するために、PSIなどを使った軽量な計測スクリプトも用意。
CPUやIOの圧迫具合を見ながら、限界ギリギリを攻めずに済むようにしています。
flakyテスト、いわゆる“たまに落ちるテスト”を防ぐためのルールも強力で、
waitForTimeoutは禁止、「状態に基づいた待機を徹底する」「まず“陽性ランドマーク”を待ってから検証に入る」といった“鉄の掟(Iron Law)”を文書化。
しかもそれをAIエージェントにも組み込んで、このルールを破る書き方を機械的に止めるようにしています。
人間のレビューだけに頼らず、ツールレベルで“ちゃんとしたEツーE”を書かせる仕組みにしているのが、おもしろいところです。
最後に、影響範囲だけを自動判定して、その部分のテストだけを回すImpact Testsだったり、動画やPDF、実ファイル付きのHTMLレポートを生成して、「規制業務で要求される、要件ごとの検証記録」としてもそのまま使えるようにしている。
単なる“動作確認の自動化”を超えて、プロダクトのエビデンス基盤にまで昇華させている事例でした。
。。。。
ということで、きょうの「zenncast」、五本のお話を駆け足でおさらいしておきましょう。
まずは、日本語の“文のリズム”からAI臭さをlintして、機械検出 → AI修正案 → 人間判断、という三段ループで文章を磨いていく「natural-japanese」の話。
次に、Aurora PostgreSQLのreaderだけが、削除済み行を数日返し続けた事例を、MVCCやvisibility map、index only scanの観点から徹底的に切り分けた話。
三本目は、GitHubのImmutable Releases時代に、「なにが成功したら“リリース完了”なのか」を軸に、NPMやPyPI、Packagist、Go Modulesそれぞれで正しいフローを考え直そう、というリリース設計の話。
四本目は、Lambdaの制約の中で、量子化した一〜数十億パラメータ級LLMをGGUFで動かし、Qwenスリー・ワンポイントセブンビーのnothinkモードが、品質とコストのいい落としどころだったという検証。
そして最後は、PlaywrightとGitHub Actionsで、百スペックを六〜八分で回すために、データ独立、並列前提の設計、インフラ調整、Iron Law、Impact Testsなどを駆使して、EツーEを“ちゃんとしたプロダクト基盤”にした事例をご紹介しました。
気になった記事があれば、この番組のショーノートに元記事へのリンクをまとめておきますので、ぜひ本編をじっくり読んでみてください。
番組の感想や、「こんなテーマも取り上げてほしい」といったリクエストも、どしどしお待ちしています。
それでは、そろそろお時間です。
お相手はマイクでした。次回の「zenncast」で、またお会いしましょう。
それでは、いってらっしゃい。