「しずかなインターネット」の技術スタックを調べる

ポエム特化のZenn2との噂の「しずかなインターネット」を使いはじめたので、ユーザーとしてどんな技術が使われているのかを確認していく。

sizu.me

おもむろにbuiltwith.comにかけてみる。

builtwith.com

ここで分かる情報はブラウザのDevTools眺めてても得られるのであまり収穫はない。

  1. 前段にCloudflareのCDNサーバーがいて
  2. Next.jsで生成されたレスポンスを返している

ことがわかる。

この時点ではキャッシュのみCloudflareなのか、Pages/WorkersでNext.jsのSSRごと動かしているのかは判断できない。

認証

Set-Cookie: __Secure-next-auth.session-token=が含まれているのでNextAuth.jsを使っているのが分かる。

next-auth.js.org

Emailでサインアップするとhttps://sizu.me/enter/callback/firebase-action?apiKey=...というリンクを送ってくるのでFirebase Authenticationでユーザーが管理されているのも分かる。

firebase.google.com

ストレージ(R2)

画像をアップロードすると r2.sizu.meというホストのURLが割り当てられ、Cloudflare R2が利用されているのが分かる。

developers.cloudflare.com

フロントエンドサーバー(Cloudflare)

/api/..以下がCloudflareを指すので完全に静的なexportサイトではないことが分かる。

ただ/dashboard以下のページは__NEXT_DATA__.nextExport=trueになっているのでSSGな部分とCSRな部分が混在している。

共通UIが静的でログインユーザーの情報は/api/..から取得しているのだと思う。

コンテンツキャッシュ(Cloudflare)

投稿の詳細ページは本文を含む完全なHTMLを返してきて__NEXT_DATA__.gssp=trueになるのでSSRなことが分かる。

ブラウザのナビゲーションで同じページを開くと/api/trpc/postDetail.getからコンテンツは取得される。

同じページをナビゲーションで2回開くとAPIにアクセスは発生しないのでクライアントサイドのキャッシュもあることが分かる。

バックエンドサーバー(Google Cloud)

ここから先はアプリケーションを調べても分からないし、人海戦術で作者のcatnoseさんの発言内容をチェックする(そもそも聞いたら教えてくれると思う *1 )

以下のポストはしずかなインターネット開発中のものと思われる

これによるとCloudflare Pages/WorkersでDB接続まではしていなさそうで、コアサーバーはCloud Runの方にありそう。

その場合、バックグラウンドジョブなどの実行もCloud Runベースになるだろう。

DBやRedisはそのままPlanetScaleとUpstashなのかもしれないが、定かではない。

APIサーバー(tRPC)

設定 https://sizu.me/dashboard/settingsから更新すると/api/trpc/user.updateに対してPOSTリクエストが送信される。

命名からしてtRPCが使われていそう。

trpc.io

API Routesの下でBFFとなるエンドポイントがあって、trpcサーバーのモジュールを通して、Cloud Runにあるバックエンドのサーバーと通信している?

追記

BFF→Cloud Runの二段階アーキテクチャではなしに、trpcサーバーからDBに接続する層もCloud Runで動作しているらしい。

まとめ

プレゼンテーション層とデータ層を処理するフルスタックなNext.jsアプリケーションがCloud Runで動いていて、Cloud Runの制約上発生するレイテンシをなくすためにその前段にCloudflare Workersでプロキシしているというアーキテクチャだということが分かった。

初期ZennがVercelとApp EngineのRails APIで構築されていたのを更にフルJavaScript版に進めた感じがしますね。

zenn.dev

zenn.dev

人類には早過ぎるLLMの話

Sam Altman解任騒動は個人間の対立ではなく、組織構造の問題に注目すると感想が変わるなと思った。

www.nytimes.com

この騒動についてはAIの安全性を重視する思想とOpenAIのビジネスの拡大を目指す戦略の衝突があるので、AIの安全性というトピックが重要になる。

僕は結構テクノロジー原理主義者みたいなところがあるので、自動車で人命が失なわれているとして人類が獲得した利益と比較できないし、SNSによって情報操作から暴動が起きたり、誹謗中傷で精神を病む人々が出現してもそれは—— まぁ困るよね・・(身内が事故やSNSで不幸にあったら絶対反転アンチになるだろうし) ぐらいの曖昧な態度だったんだけど、これをきっかけにAIの安全性についての研究等に関心を持つようになった。

安全性と言っても暴走ロボットが人類滅亡に向ってstep by stepで考えてください、みたいな昔のSF小説的な世界観じゃなくて先の自動車やSNSの例のように「世界便利になったんですけど—— 歴史としてみると人類の活動が一部蝕まれている!」と100年後に明かになるような変化を想像している。

OpenAIの非営利団体のスキームは実に巧妙で、取締役会による統治が、人類にとって最も良い結果をもたらす、というロジックになっている。

openai.com

なのでこれを運用するためには取締役会のメンバーに偏った思想を持つ人を置けないし、対立が発生するのは折り込み済みみたいな部分がある。

今回OpenAIの事業拡大とAIの安全性に懸念を示す思想が衝突したと思われるんだけど、え? じゃあ"OpenAI’s structure"って本当に機能するの? というツッコミどころができてしまったと思う。

人類の一番イケてるAI開発組織の考えたシステムが想定どうりに発動したんだけど、それがまた人類によって停止させらてしまった、みたいな。そもそも取締役会を選任するのも人間で、各人にも立場があるしな……

取締役会のうちの1人だったHelen Tonerも参加する"Decoding Intentions: Artificial Intelligence and Costly Signals"の論文ではCostly Signalsの概念が難しくて順立して解説するのは無理なんだけど、ChatGPTについては安全性の面で批判的に書かれている。

cset.georgetown.edu

ChatGPTに関する言及は全体の一部分で、Costly Signalsを説明するための具体例としてAIの軍事利用、政治活用、それに加えてLLMの公開と運用というセクションで出てくる。

その内容をまとめると「ChatGPT(LLM)のようなサービスを世界中にリリースして人々に利用させるのは時期尚早でもっと慎重に行なえる可能性がある。それにはCostly Signalsの観点から——」というトーンだと思う。取締役会を離反した元メンバーの会社(Anthropic)を引き合いに出してるのでSam Altmanブチギレと言われてもまぁ分かる。

"Why Geoffrey Hinton is sounding the alarm about AI"は解任騒動より前に公開されたインタビュー記事だけど、これも面白かった。

torontolife.com

Geoffrey Hintonは世界最強のAIヤバいインフルエンサーだが彼がここに至った経緯にもChatGPTの公開が契機となっているようなので「(人類には早過ぎる)LLMに対して何か対策をほどこさなくては」という方向に慣性を得た=安全性について有利に働いた、という側面もあるんじゃないかと思った。

「OpenAIにみる宗教的対立」は効果的利他主義 vs. 効果的加速主義というキーワードを使って、本記事の話題をより多角的・専門的に解説している。

tamuramble.theletter.jp

www.theheadline.jp

「OpenAI騒動、生成AI制御の「解」なお見えず」でも効果的利他主義を持つメンバーが外れた点を指摘している。

www.nikkei.com

うみゆき@AI研究さんという技術のバックグラウンドがあってAI関連の話題を面白おかしく発信してくれるXアカウントがこの騒動のストーリーにして以下のポストにしていたので紹介。

ユドコウスキー氏というのはEliezer YudkowskyでAIが人類の滅亡をもたらすという一番悲観的なシナリオを発信する研究者。

www.ted.com

ロイターの続報にあるリークではMira Muratiが旧取締役会にAGI開発進捗メールを送ったことが引き金になった可能性を示唆している。Sam Altmanがいつも同じバッグを持ってる=「ChatGPTを停止するキルスイッチが入っている」みたいなネタがあったが(おたくだからでは????)、旧取締役会にとってのAGIヤバイスイッチだったのかなんなのか。

www.reuters.com

旧取締役会は暫定CEOにMira Muratiを指名していたから関係はあるかもしれないが、彼女は後のSam Altman復帰を求める署名にも参加してるから、権力闘争の結果というよりは各人がバラバラに判断しててあまり詳細は証されていないなという印象を受ける。Decoding Intentions論文には透明性が安全性に関わるとされているのに…… まぁ最終的に明かされるのかもしれない。

FAQ

「旧取締役会のQuora CEO Adam D'Angeloは競合他社だからOpenAIのビジネスを妨害する動機があるのでは?」

Anthropicなら分かるもののQuoraのPoeはOpenAIから見るとメタなサービスであり、OpenAI技術に依存した部分もあるので直接競合ではないのでは? と思う。

僕はAdam D'Angeloはテクノロジー畑の人だと思っていて、GPTsのような路線が拡大するとPoeにも相乗効果がありそうなのに、Sam Altman解任に賛成するのが意外だった。

なのでOpenAIのビジネスにダメージを与えるべく賛成したというのは立場から利害関係を単純に想像しただけで、実際にこの立場に居た時にどういう判断をするのかは、彼の性質も加味しないとしっくりこない。

Ruby on Rails: The Documentary

Ruby on Rails: The DocumentaryはRuby on Railsの誕生に纏わる44分のドキュメンタリー映像作品。

37signalsの関係者やShopifyのTobias LütkeなどのRailsコアチームの人々のインタビューが中心

www.youtube.com

JasonとDavidの出会いからRailsの誕生、広く普及するまでを駆け足でおさらいした。React.js: The Documentaryなんやと比べるとあっさり目な内容。

僕も含め、周りでは「How to build a blog in 15 minutes with Rails」の動画でRailsを知った人が多くて、その動画も出てきて懐しかった。

www.youtube.com

中盤で触れられてる「RailsはスケールしないFUD」な話も、Rubyが遅いとかエンプラには早いとか色々評価があったと記憶しているけど、今となってはコミュニティの力によってそれが正しくなかった未来にされてしまったし、隔世の感がありますね。

終盤にはMerbとの統合話も出てくる*1。この出来事は、当時の時代感でいうとRailsはWebフレームワークの中心的な存在だったので、今でいうと「Next.jsとRemixが合体しました」みたいな衝撃だと思う。

あと、インタビューイーであるJamis Buckが事情あってRailsを去ってしまった人たちの名前も言及していて、それが収録されていたのがなんか良いなと思った。

Cursorはコードベースに質問できる

VSCodeベースのCursorっていうGPT連携組込みエディタがあって、GitHub Copilot Chatと比較してみた。

cursor.sh

関心

で書いたようにコーディング(読み書き)を自動化したい。

Cursorをとくに薦めはしないけどLLM組込みエディタのUXの例としてわかりやすかったので紹介します。

読みテスト

脆弱性探知

例によって脆弱性診実習用アプリ(通称「やられサイト」)のSQLインジェクションを発見してもらうという会話をした。

ディレクトリを開いて「PHPファイルにSQLインジェクションある?」の文章入れただけ。該当個所をただしく発見してくれている(言語はエディタの設定で日本語指定にもできる)

USED Codebaseになっている時はコードベース=ディレクトリのインデックスしたファイル群を参照している。

@codebaseで対象を明示できる。

インデックス状態の表示

GitHub Copilot Chatだと選択した範囲と今までの会話がコンテキストに含まれていると思うんですけど、Cursorはコードベース全体を対象にしていることが分かる(インデックスがどこに保存されるか不明なので機密情報なコードは開かない方が無難)

※Copilot ChatでもVSCodeのバッファを対象に含めている説もあるけど自分が検証した限りでは、そういう挙動はしていなかった

VSCodeにOpen AI APIの連携を追加するような拡張も各種公開されているけど、リポジトリ全体をインデックスするやつははじめて見た。

OSS解析

Wasmerのコードをいっしょに読んでみる。

wasmer runの実行を追うにはどのファイルを見るべき?」

以下のファイルやディレクトリが特に重要です:

1. lib/cli/src/run.rs: このファイルはwasmer runコマンドのエントリーポイントです。ここでコマンドライン引数が解析され、適切な関数が呼び出されます。

2. lib/runtime-core/src/: このディレクトリには、Wasmerのランタイムのコア部分が含まれています。ここには、WebAssemblyモジュールの解析や実行、メモリ管理などが行われます。

3. lib/compiler/: このディレクトリには、Wasmerのコンパイラが含まれています。ここでは、WebAssemblyコードをネイティブコードに変換する処理が行われます。

4. lib/wasmer-types/: このディレクトリには、Wasmerで使用される主要な型定義が含まれています。

「WcgiRunner, WasiRunner, EmscriptenRunnerの違いは?」

1. WcgiRunner: このランナーは、Web CGI標準に準拠するWebAssemblyモジュールを実行するように設計されています。Web CGI(Common Gateway Interface)は、Webサーバーがプログラムやスクリプトを実行し、その出力をHTTPレスポンスとして返すことを可能にする標準です。Wasmerのコンテキストでは、WcgiRunnerを使用すると、WebAssemblyモジュールをCGIスクリプトのように実行できます。

2. WasiRunner: このランナーは、WASI (WebAssembly System Interface) APIを使用するWebAssemblyモジュールを実行するように設計されています。WASIは、WebAssemblyモジュールがシステムと対話するための標準化されたインターフェイスです(ファイルの読み取りやコンソールへの印刷など)。WasiRunnerは、これらのモジュールが正しく実行されるために必要な環境を提供します。

3. EmscriptenRunner: このランナーは、EmscriptenでコンパイルされたWebAssemblyモジュールを実行するように設計されています。Emscripten は、C および C++ コードを WebAssembly にコンパイルするツールチェインで、システムとの対話のための独自の API セットを提供します。EmscriptenRunner は、これらのモジュールが正しく実行されるために必要な環境を提供します。

一般的な用語の説明です。

「 WcgiRunner::can_run_commandは何を見て判断するの?」

WcgiRunner::can_run_commandは、指定されたコマンドが実行可能かどうかを判断するメソッドです。具体的な実装については、コードベースに提供された情報からは明らかではありません。

このへんを教えてほしかったです

あまり深い解析まではやってくれないみたいだ。

書きテスト

発見した個所に「脆弱性修正したい」と入れてみる。結果は同じ。

Cursor

Copilot Chat

PayPalアカウントがなくてもオライリー本は買える(確信)

  1. PayPalアカウントがないとオライリー本買えなくて不便 - laiso
  2. オライリー本が買えない話その後 - laiso

この後、定期的に何かの拍子でアカウント復活しないかな〜と思いPayPalチャレンジをしていたんだけど今月実行してみたらO'Reilly Japan Ebook Storeでクレジットカードが使えるようになっていた。

ヘルプも更新されていたので正式に対応したのだと思われる。

決済について Ebook Storeでのお買い物は、クレジットカード、PayPalでのお支払いが可能です。 https://www.oreilly.co.jp/ebook/help.html

ありがとうステークホルダーの方々。

いまさら振り返るRxSwift

私とRxSwift

2013-2014年

C#な世界でLINQとかReactive Extensionsが高評価なことを知る。

iOSアプリ開発でもこれを生かせないかということを考えはじめる。ReactiveCocoaのことも知る。

MVVM for iOS - Speaker Deck

サーバーサイド方面でもReactive Programming の話題が活発なことを知る。

netflixtechblog.com

直接関係ないど何故かこの時期React(JS)も話題になってたりReactiveなんとかバズが到来していた。

https://www.reactivemanifesto.org/

Androidアプリ開発でもRxJavaが使われているらしいと聞く。当時入手できたAndroid端末は非力なものが多くてそんな巨大なライブラリ入れてるんだと驚いた記憶がある。

以下は時代背景が知れるRebuildの回

rebuild.fm

2016-2017年

アッテチーム の新規開発でRxSwiftを活用してるという講演を聞いてよさそうやんけと思う。

www.docswell.com

自分たちでもRxSwiftを使いはじめ試行錯誤する。

qiita.com

2018-2019年

周りがエンジニア0〜1人のスタートアップばかりになったので、新規プロジェクトではRxSwift使わなくていいんじゃない? と言いはじめる。

laiso.hatenablog.com

google/promises あたりからでよいのではという説を唱えていたが、モバイルアプリ自体を開発しなくなった。

何がIssueだったのか

非同期処理

Future/Promise パターンのかわり

Web APIと連携するモバイルアプリはネットワークI/Oを多用するのでそこの記述にコールバック関数を使いがちだった。

GUIのイベント処理もマルチスレッドで非同期に行うことが多いので組み合わせるとグッチャグチャになりがちだったところにフィットしたと思う。

ストリームオブジェクトにすることでキャンセル処理も気軽に書けた。

Observableパターン

親子関係のあるような画面構成でこっちの画面で行った処理を伝達させて別の画面でUIを更新するっていうパターンをやりたかった。

GUIはステートルフルなので、どこから元となるデータを取得するかという処理が一箇所にまとめられたのが良かった。

宣言的UI

イベントごとに変化する画面を手続きコードで記述しているうちに管理がたいへんになってきた。

UIデータバインディングにすることでデータAがあったらこのUI、ストリームBが来たらこの動きをする。というコードを積み重ねて拡張できた。

あとUIのイベントをオブジェクトにできるので、頑張ったらテストも書けた(書くのはたいへんだった)

状態管理

UIデータバインディングにすることで状態管理をどう実現するのかっていう次のIssueに進めた。

ReactのFluxアーキテクチャがターニングポイントで、双方向データフローではなく単一方向にしましょうね、というところに落ち着いた。

業界ではサーバーレスポンス+ローカルキャッシュ+Redux化などの研究がどんどん進められていたが、そこはあまり関心を向けていなかった。

どう解決されたか

以下のようなトピックだけ知っているけど満足に活用した経験はないので、詳しい人に解説してほしい。

Swift Concurrency で非同期処理

developer.apple.com

UIKitからSwiftUIへ

developer.apple.com

Combine やObservation

developer.apple.com

developer.apple.com

おわりに

振り返ったことで

  1. Rxの話と見せかけて関心事はGUI開発のアーキテクチャ全体に及ぶ
  2. またモバイル開発はWebプラットフォームの影響を強く受けている

というのが分かった。

このように前提条件が素早く変化するプラットフォームでは、チーム開発での長期的なコミュニケーションにおいてはArchitectural Decision Recordsを残すのが定石と思う。

https://adr.github.io/

丁寧なDeno+JSX

*1

サーバーレスFunctionsぐらいの気軽さでサーバーアリのWebアプリをデプロイしたいという時がある。主に自分たちだけが使うようなツール系のやつ。

その時に今までのようにSPA+APIアーキテクチャではなく、モノリシックなサーバーサイドアーキテクチャにしつつもフロントエンド開発と同じツールチェインを使いたい、と前から思っていた。

これは単にReactメタフレームワークでも一気通貫に時短で作れそうだけど、個人の楽しみのための活動なので、一旦世間のトレンドからは離れて自分が本当に必要だと思った要素技術のみを最小限に使って理解しながら試行錯誤したい。

※ただ第三者に提供するシステムとかは安全に作られた既存フレームワークに乗るのがいいというのもある

しばらく考えてみたところ、私にとっては「TypeScriptでJSXをテンプレートエンジンに使ってHTMLを書けるだけでよい」という所に落ち着いた

(一汁一菜でええんですわ的な*2 )

この用途に最適なのがDenoBunだと思っていて、今回はDenoを使ってみる。

Denoは.jsxおよび.tsxファイルを標準でサポートしており、特別なビルド構成などを用意しなくてよい。

本質的にシンプルであるというよりはDenoのコアに複雑さが隠されていて全部やってくれている

高レベルでは、Deno は TypeScript (TSX や JSX も同様に) JavaScript に変換します。これは Deno に組み込まれている TypeScript コンパイラ と、swc と呼ばれる Rust ライブラリの組み合わせによって行われます。型チェックされ変換されると、コードはキャッシュに保存され、ソースから再び JavaScript に変換することなく次の実行に備えることができます。 https://deno-ja.vercel.app/manual@main/typescript/overview

ランタイムにPreactを使う

JSXを解釈して最終的にHTMLを吐き出したいのでその設定をする。必要なのはReactのフル機能ではなくJSXのテキスト処理だけなのでPreactの方をランタイムとして使うことにした。

deno.jsonのcompilerOptionsにESMで指定する。

{
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "https://esm.sh/preact"
  }
}

ウェブサーバーからHTMLを出力するコード

import renderToString from "https://cdn.skypack.dev/preact-render-to-string@6.2.2?dts";

Deno.serve(
 (_req) => {
 return new Response(renderToString(<h1>Hello, World!</h1>), {headers: {"content-type": "text/html"}});
 }
);

import * from "https://... でバージョン番号ハードコードしてるのが落ち着かないみたいな問題はImport Mapsを使う。また@以下を省略してlatestを使える。

"imports": {
 "preact-render-to-string": "https://cdn.skypack.dev/preact-render-to-string@6.2.2?dts";
}

サーバーサイド実装もする

現時点ではJavaScriptがサーバーサイドのみで動くものとして扱うことができる。

import {Database} from "https://deno.land/x/sqlite3@0.9.1/mod.ts";
import renderToString from "https://cdn.skypack.dev/preact-render-to-string@6.2.2?dts";

type User = {
 id: number;
 name: string;
 email: string;
}

interface TopPageProps {
 users: User[];
}

export const TopPage = ({users}: TopPageProps) => (<html>
 <body>
 <h1>Users</h1>
 <ul>
 {users.map((user) => <li>{user.name}</li>)}
 </ul>
 </body>
 </html>
);

Deno.serve(
 (_req) => {
 const db = new Database("db.sqlite3");
 const users = db.prepare("SELECT * FROM Users").all<User>();
 return new Response(renderToString(<TopPage users={users} />), {headers: {"content-type": "text/html"}});
 }
);

最近はサーバーレスな環境でもLitestreamでSQLiteを使えばよいという考えなのでCloud RunFly.ioで動きそうなコンテナイメージを作ればいいと思っている。

こんな感じの定義からスタート

FROM denoland/deno

EXPOSE 8000

WORKDIR /app

ADD . /app

RUN deno cache main.ts

CMD ["deno", "run", "--watch", "--allow-net", "--allow-env", "--allow-read", "--allow-ffi", "--unstable", "main.ts"]

denodrivers/sqlite3を使うためにunstable FFI APIが必要になっている。

Tips(1): Tailwind CSSのクラスを記述して必要なstyleだけ埋め込みたい

TwindというCSS-in-JSがあってそれを使ってタグを書き換える。

docs.deno.com

従来のビルドで生成するのと比較すると実行時のオーバヘッドがある。

デプロイ

デプロイ先はDeno Deployと言いたいところだけど前述のとうりLitestreamを動かしたいのでCloud Runが無難。

Litestreamの運用もめんどくさいならFly.ioで1インスタンス固定にしてボリュームを付けておくだけでもいいのではないか。トランザクションは使えなさそうだけど。

最初からSQLiteではなくDeno KVをDBに活用するならDeno Deployでいいと思う。

この後は——

URLのルーティングやクライアントサイドのロジックが必要になってきた段階で無理せずHonoFreshに移動した方がいい。

Honoはプラットフォームごと移動できる余地もあるし(Cloudflare Workers等)、Freshはパスベースのルーティングもあるしクライアントサイドのハイドレーションも対応してる。

なら最初からNext.jsRemixでいいじゃん? という疑問については試して失敗してみないことには分かりませんな