RORK LABEN
SEED — Rork は Left Lane Capital 主導で1,500万ドルのシードラウンドを調達しました。プロンプトからモバイルアプリを生成する領域に資金が集まっていますPAPER — アプリビルダーの Paperline を買収しました。エンジニアリング人材の獲得を目的として、今後も買収を続ける方針が示されていますMAXSWIFT — Rork Max は React Native ではなくネイティブ Swift を生成する別系統の製品です。iPhone・iPad・Apple Watch・Apple TV・Vision Pro・iMessage に対応しますSCALE — Rork は月間743,000訪問、成長率85%と報告されています。ノーコードのアプリビルダーが実運用の選択肢として定着しつつある水準ですEXPOAI — Expo は Claude Code・Expo Skills・Expo MCP・Argent と AI が読みやすいドキュメントを組み合わせ、エージェントにフレームワークの文脈を渡す開発体験を用意していますAGENT — Expo Agent はブラウザ上で動き、プロジェクトやリポジトリを直接触りながらプロンプトでアプリを生成・改変できますSEED — Rork は Left Lane Capital 主導で1,500万ドルのシードラウンドを調達しました。プロンプトからモバイルアプリを生成する領域に資金が集まっていますPAPER — アプリビルダーの Paperline を買収しました。エンジニアリング人材の獲得を目的として、今後も買収を続ける方針が示されていますMAXSWIFT — Rork Max は React Native ではなくネイティブ Swift を生成する別系統の製品です。iPhone・iPad・Apple Watch・Apple TV・Vision Pro・iMessage に対応しますSCALE — Rork は月間743,000訪問、成長率85%と報告されています。ノーコードのアプリビルダーが実運用の選択肢として定着しつつある水準ですEXPOAI — Expo は Claude Code・Expo Skills・Expo MCP・Argent と AI が読みやすいドキュメントを組み合わせ、エージェントにフレームワークの文脈を渡す開発体験を用意していますAGENT — Expo Agent はブラウザ上で動き、プロジェクトやリポジトリを直接触りながらプロンプトでアプリを生成・改変できます
記事一覧/開発ツール
開発ツール/2026-08-01上級

実験の割り当てが同じ端末に偏る — 100万件のIDで確かめたハッシュ選定と salt の位置

端末側で実験の変種を決める割り当てが、実験を2本並べた途端に同じ端末へ集中しました。100万件のIDで4つのハッシュを測り、原因がハッシュの一様性ではなく salt の連結位置にあったこと、そして採用した実装までを実測値とともに残します。

Rork524React Native216A/Bテスト10フィーチャーフラグ2アーキテクチャ21

プレミアム記事

ペイウォール文言のA/Bと、オンボーディングを3画面から2画面へ縮める検証。この二つを、同じ週にどちらも配信比率10%で走らせました。

一週間後、集計を見て手が止まりました。オンボーディング短縮に入っていた端末の6割以上が、ペイウォール文言の変種側にも入っていたのです。二つの実験は互いに何の関係もないはずでした。10%と10%なら、両方に入る端末は全体の1%前後に落ち着くと踏んでいました。

最初に疑ったのは端末IDの偏りでした。次に疑ったのはハッシュの質です。どちらも外れでした。原因は、割り当てキーを組み立てるときの文字列の連結順という、コードレビューで誰も止めない一行にありました。

腑に落ちるまでに丸一日かかりましたので、同じところで迷う方のために、100万件のIDで測った数字と、最終的に採用した実装をそのまま残します。

端末側で割り当てを決める、という前提

個人開発で運用している壁紙アプリは、収益が AdMob と定期購読で、実験のためだけにサーバーを立てる構成にしていません。割り当てサーバーを1本増やすと、可用性・レイテンシ・コストの三つが同時に増えます。App Store の審査に出す前から、そこは避けたい部分でした。

そこで割り当ては端末側で完結させています。求めた性質は四つです。

  1. 決定論的であること — 同じ端末は、アプリを何度起動しても同じ変種を見る
  2. 実験どうしが独立であること — ある実験に入ったことが、別の実験に入りやすさへ影響しない
  3. 段階拡大で入れ替わらないこと — 10%から20%へ広げたとき、すでに入っていた端末が外へ出ない
  4. 同期に評価できること — 起動パスで await を挟まず、最初の描画までに変種が確定する

4番目は地味ですが、体感に直結します。ペイウォールの見出しが一瞬だけ既定文言で表示され、次のフレームで差し替わると、ユーザーからは不具合に見えます。

割り当ての単位になる端末IDは、expo-secure-store に保存した UUID を使っています。identifierForVendor を直接使うと、同じ端末でも再インストールで値が変わり、同じ人が別のコホートへ移ってしまいます。再インストール時に何が残り何が消えるかは再インストールしたユーザーが「初回」に戻らない — Rork(Expo) アプリの初回判定と状態永続の非対称設計で整理しました。

4つのハッシュを100万件で比べる

候補は4つ。文字コードの単純加算、31を基数とする多項式ハッシュ(Java の String.hashCode と同型)、FNV-1a の32ビット版、そして SHA-256 の先頭4バイトです。

まず、単体での一様性を測りました。100万件の UUID v4 を生成し、hash(salt + ':' + id) % 100 で100個のバケットへ落として、カイ二乗統計量と最大偏差を見ます。

// bench.mjs — Node.js 22 で実行
import { createHash, randomUUID } from 'node:crypto';
 
const N = 1_000_000;
const ids = new Array(N);
for (let i = 0; i < N; i++) ids[i] = randomUUID();
 
function sumChars(s) {
  let h = 0;
  for (let i = 0; i < s.length; i++) h += s.charCodeAt(i);
  return h >>> 0;
}
function javaHash(s) {
  let h = 0;
  for (let i = 0; i < s.length; i++) h = (Math.imul(31, h) + s.charCodeAt(i)) | 0;
  return h >>> 0;
}
function fnv1a(s) {
  let h = 0x811c9dc5;
  for (let i = 0; i < s.length; i++) {
    h ^= s.charCodeAt(i);
    h = Math.imul(h, 0x01000193); // 32bit の丸めを保つため Math.imul が要る
  }
  return h >>> 0;
}
function sha256t(s) {
  return createHash('sha256').update(s).digest().readUInt32BE(0);
}
 
function uniformity(fn, salt) {
  const B = 100;
  const counts = new Int32Array(B);
  const t0 = process.hrtime.bigint();
  for (let i = 0; i < N; i++) counts[fn(salt + ':' + ids[i]) % B]++;
  const t1 = process.hrtime.bigint();
 
  const expected = N / B;
  let chi2 = 0;
  for (let b = 0; b < B; b++) {
    const d = counts[b] - expected;
    chi2 += (d * d) / expected;
  }
  return {
    ms: Number(t1 - t0) / 1e6,
    chi2,
    min: Math.min(...counts),
    max: Math.max(...counts),
  };
}

結果です。自由度99のカイ二乗分布で、上側1%点は135.8。これを超えると「一様とは考えにくい」と判断します。

ハッシュ100万件の所要時間カイ二乗最小バケット最大バケット判定
文字コード加算529.6 ms26,013.07,65012,483不合格
31進多項式(javaHash)535.9 ms103.09,78010,262合格
FNV-1a 32bit536.0 ms89.39,70510,320合格
SHA-256 先頭4バイト2,834.4 ms113.09,81310,287合格

期待値は1バケットあたり10,000件。文字コード加算は最大24.83%も外れており、ここで脱落します。UUID は使われる文字が16進とハイフンに限られるため、加算では取りうる合計値の幅が狭く、中央に山ができます。

残る三つは、この時点ではどれも合格でした。私が実際に使っていたのは、依存を増やさずに済む31進多項式のほうです。

ここまでお読みいただきありがとうございます。

この記事の続きを読む

この先には、実装コードやベンチマーク結果など、実務でお役に立てる内容をご用意しています。このサイトは広告を掲載しておらず、サーバーや開発にかかる費用はメンバーの皆様のご支援で成り立っています。もしお役に立てていましたら、ご支援いただけますと大変ありがたいです。

この記事で得られること
一様性テストに合格した31進ハッシュが、実験2本で P(B|A)=65.35%、3実験同時当選 7.239% まで相関した実測データ
salt を末尾へ動かすと今度は 0.00% の完全排他になる条件と、そうなる理由を式で追う手順
FNV-1a に最終攪拌を足して P(B|A)=10.00%・0.496µs/回・段階拡大の保持率100%へ収めた実装一式
Stripe による安全な決済 · いつでもキャンセル可能

この記事を購入する

この先の内容をすべてお読みいただけます。一度のご購入で、いつでも何度でもアクセスできます。このサイトは広告を掲載しておらず、皆さまのご支援がサーバー費用などの運営を支えています。

または
メンバーシップなら全記事が読み放題 →
シェア

お読みいただきありがとうございます

Rork Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • コピー&ペーストで使える実装コード付き
  • 毎日新しい上級ガイドを追加
  • ¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

関連記事

開発ツール2026-07-24
同じ通信が同時に何本も飛ぶのを1本に束ねる — 参照カウント付きシングルフライト層の設計
起動直後に同じ API へ何本ものリクエストが同時に飛ぶ多重フェッチを、進行中の Promise を共有して1本に束ねる設計です。失敗した Promise を配り続けてしまう罠、呼び出し側の中断が全体を巻き込む罠を避ける、参照カウント付きシングルフライト層の実装を、実際の通信ログとともに残します。
開発ツール2026-07-24
アプリの設定値を三層で解決する — 既定・ユーザー・リモートを境界付きにマージするレイヤー設計
既定値・ユーザー設定・リモート設定をひとつの型安全なレイヤーでマージする設計です。壊れたリモート値でもアプリが崩れないよう、キーごとに上書きの強さを変え、境界クランプと検証を通す実装を、実際の事故から起こしたコードで解説します。
開発ツール2026-07-14
AI再生成に壊されない設計 — Rork の生成コードと手書きロジックを分ける境界の引き方
Rork へ追加指示を出すたびに手書きのロジックが消える。プロンプトで守るのは対症療法です。生成コードと自分の所有コードを層で分け、再生成が届かない境界を設計で引く方法を、Zustand と service 層の実装例つきでお伝えします。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →