映画の「残席1」を、2台のスマホから同時に押したことはありますか。
どちらの画面にも「1席確保」と表示されます。でも劇場の座席は1つしかありません。
このずれが、アプリの内側で毎日静かに起きています。
名前は「競合状態」——英語では「レースコンディション」といいます。
ある夜、Lilyが同日3本のビルドを通したとき、このずれを「わかっていながら引き受けない」と決めた箇所が1か所ありました。
今日は、その判断の裏側と、CMO時代には絶対に気づけなかったことについて書きます。
あなたは今、こうなっていませんか。
• AIで作ったアプリが「とりあえず動く」状態になったが、本番に出してよいのか迷っている • 仕様書に1行書くだけだった業務が、自分で実装すると何十時間もかかると知って驚いた • 「どこまで作り込めばいいか」の判断軸が、自分の中でまだ固まっていない • 非エンジニアとして開発を始め、技術の問題と事業の問題の区別がつかないことがある
先に結論を言います。「どこまで守るか」は技術の問いではなく、マーケが先に答えるべき事業の問いです。
この記事では、招待コード・セッション・管理画面を同日3本のビルドで通した日のこと、唯一先送りにした設計上の判断、そしてCMO時代に「1行の仕様書」で済ませていたことの本当の重さについて書きます。
同日3本のビルドを通した、ある夜のこと
招待コード、セッション、管理画面——3つが揃う意味
先日、Lilyが個人開発しているサービスに、3つの機能が同日中に実装できました。
「招待コード」「セッション管理」「管理画面」の3つです。
招待コードというのは、紹介者だけが使える限定チケットのようなものです。
紹介された人だけが入れる扉のカギ、というイメージが近いかもしれません。
セッションは、ログインした人を「さっきと同じ人だ」と認識し続ける仕組みです。
これがないと、ページを移動するたびにログアウトしてしまいます。
管理画面は、Lilyが裏から数字を見たり設定を変えたりするためのコックピットです。
この3つが揃うと、サービスとして「動く形」になります。
招待コードだけあっても、ログインが維持できなければ意味がありません。
管理画面がなければ、何が起きているか外から確認できません。
3つはそれぞれが独立して動くのではなく、連携して初めてサービスの基盤になります。
なぜこの順番で作ったか
この構造を理解してから実装すると、どこを最初に作るかの順番も変わります。
セッション管理は土台なので先に通しておきたい。
招待コードは入り口なので次に作る。
管理画面は動作確認のためのコックピットとして必要——そういう優先順位で進めました。
実際に1本目のビルドを通すまでは、環境の確認に時間がかかります。
ローカルで動いているものが、デプロイ先で同じように動くかは、やってみるまでわかりません。
環境変数の設定漏れ、依存ライブラリのバージョン差異、接続先のURLが本番のものになっているか——1本目は見落としやすい箇所が多いです。
2本目以降は環境の問題よりも「設計の衝突」が表に出てきます。
招待コードのロジックとセッションのデータ構造が干渉しないか、確認する必要がありました。
3本目の管理画面は比較的シンプルに進みましたが、権限の設定が甘いと別の問題が生まれます。
管理者だけが見られる画面を、ログイン済みの一般ユーザーが何らかの操作でアクセスできてしまう——という問題です。
この「意図しないアクセス」の防止を丁寧に確認しながら進めました。
達成感の後に残った、小さな引っかかり
3本通した直後、素直に達成感がありました。
「今日はちゃんと進んだ」という感覚です。
でも同時に、小さな引っかかりも残っていました。
「1か所だけ、引き受けなかった箇所がある」——そう自覚しながら、今日はここまでにしようと決めました。
引き受けなかったのは、「並行リクエストが重なったときのカウントずれ」です。
招待コードについても、同様の問題がそのままになっています。
達成感と引っかかりが同時に胸の中にある状態で、その夜は作業を終えました。
なぜ引き受けなかったのか——それを話す前に、この問題が何なのかを噛み砕いて説明します。
📕 この記事の内容を、実装まで全部まとめた有料記事があります 手順・コード・テンプレをそのまま渡しています AIで「寝てても回る仕組み」をゼロから30日で組む全手順
「並行リクエストのカウントずれ」を噛み砕くと
映画チケットの話に戻って
最初の映画チケットの話に戻ります。
残席が1なのに、2台から同時に押すと2人が確保できてしまうことがある——この構造を、もう少し詳しく説明します。
システムは「確認してから取得する」という2段階で動いています。
Aさんが「残席を確認」→「残席1あり」と返ってくる。
その直後にBさんも「残席を確認」→「残席1あり」と返ってくる。
その後、AさんとBさんの両方が「取得する」操作を実行します。
すると2人とも「1席」を取ったことになります。
確認と取得の間は、コンマ数秒の話です。
でもインターネットのリクエストが「ほぼ同時」に届いた場合、実際にこの問題は起きえます。
エンジニアの世界では「TOCTOU問題」とも呼ばれます。
「Time of Check to Time of Use」——確認した時点と使用した時点の間にズレがある、という意味です。
名前はかっこいいですが、やっていることは映画チケットの話とまったく同じです(笑)。
「読んで・判断して・書く」の3段階に潜む問題
もう少し構造を掘り下げます。
大半のウェブアプリケーションは、「読む」「判断する」「書く」という3段階で動いています。
データベースから現在の状態を読み込み、その状態をもとに判断し、新しい状態を書き込む、という流れです。
この3段階の「読む」と「書く」の間に、別のリクエストが同じデータを読み込むと問題が起きます。
どちらも「現在の状態」を読んだ後に書き込むので、2つの書き込みが互いの存在を知らないまま実行されます。
結果として、どちらかの書き込みが上書きされる、あるいは両方の書き込みが通ってしまうことになります。
この構造は、上限チェック・在庫引き当て・ポイント消費など、「残数を確認してから使う」処理のほぼすべてに潜んでいます。
Lilyのサービスで何が起きるか
Lilyが作っているサービスには「1ユーザーが使える本数の上限」があります。
たとえば月に10本が上限だとすると、11本目のリクエストは弾かなければなりません。
「今何本使ったか確認する」→「上限を超えていないか判定する」→「実行する」という3段階の流れです。
この流れの「確認」と「実行」の間に、2本のリクエストが同時に来ると壊れます。
2本とも「残り1本ある(まだ使える)」と確認した後、2本とも「実行」してしまうためです。
結果として、上限を2本超えた状態になります。
招待コードも同じ構造です。
1枚しか発行していないコードを、2人が同時に使おうとしたとき——どちらにも「有効」と返してしまう可能性があります。
これが「カウントずれ」と「招待コードのレース問題」です。わかっていながら、今は直さないと決めました。
解決策はあります。
データベースのトランザクション機能を使ってロックをかける方法、リクエストをひとつずつ順番に処理するキュー構造を挟む方法、アトミック操作と呼ばれる「確認と書き込みを1命令で完結させる」方法——いくつかの選択肢があります。
どれを選んでも「今すぐここに使う時間と引き換えに、何を進めることができなくなるか」という問いが残ります。
CMO時代の仕様書には、1行しかなかった
あの頃は、書くだけでよかった
少し前のLilyの話をします。
以前、マーケティング責任者として仕事をしていた頃、キャンペーンの申し込みフォームを開発チームに発注することがよくありました。
月に3,000万の広告費を動かしていた時期の話です。
そのとき仕様書に書いたのは、たとえばこんな1行でした。
「同時申し込みが重なった場合も先着順に正確に処理できるようにしてください」
これだけです。書いたら、数日後に実装されて戻ってきました。
当時のLilyはその1行を書くのに、30秒もかかっていなかったと思います。
「当然できるでしょ」という軽い感覚で書いていました。
発注する側は、「できる/できない」を判断するだけです。作る側が何をするかは、見えていませんでした。
作る側になって初めて気づいたこと
自分で作るようになって、初めてわかりました。
あの「1行」の後ろに、何十時間もの設計と実装があったのだと。
「競合状態を防ぐ」という問題には、解決の方法が複数あります。
データベース側で排他制御をする方法、リクエストをキューに入れて順番に処理させる方法、アプリ側でロックをかける方法——それぞれに長所と短所があります。
どの方法を選ぶかは、サービスの規模や構成によって変わります。
さらに、その選んだ方法が他の処理と干渉しないか、スピードへの影響はないか——検討すべき点は山ほどあります。
Lilyのサービスの場合、どの方法がベストかを判断するだけでも相当な時間が必要です。
実装してテストして、問題がないか確認するまでを含めると、それが何倍にもふくらみます。
あの「1行」が、実際にはこれだけのことを内包していました。発注する側には、一切見えていませんでした。
これ、正直かなり衝撃でしたし、しばらく頭から離れませんでした、、、
「自分が過去に書いた仕様書の1行1行に、こんなに時間がかかっていたのか」と思うと、当時の開発チームへの見方が変わりました。
あのとき「難しい」と言っていた意味が、今ならわかる
当時、エンジニアから「これは少し難しいです」という返事が来ることがありました。
Lilyはそのとき、「難しいってどういうこと?できないの?」と内心思っていました。
その「難しい」が何を指しているのかを、深く考えたことがありませんでした。
今ならわかります。
「難しい」の中身は、「何十時間の設計と実装が必要で、かつ選択肢が複数あり、どれが最適かはあなた(マーケ)の要件によって変わる」という意味だったのだと。
エンジニアは「どのくらいの同時アクセスを想定しているのか」「万が一ズレたときの許容度はどのくらいか」を知らなければ、最適な方法を選べません。
その情報を持っているのはマーケ側なのに、仕様書にはそれが書かれていませんでした。
「難しい」と言われるたびに「じゃあどうするの?」
と返していたあの頃のLilyが、今思えば少し恥ずかしいです(笑)。
彼らが「難しい」と言ったとき、もっとちゃんと聞けばよかったと今は思います。
先送りを決めた夜、天秤にかけた3つのこと
ユーザー数・リスクの大きさ・かかる工数
では、なぜ今回は先送りにしたのか。
3つのものを天秤にかけました。
1つ目はユーザー数です。
今のLilyのサービスは、まだユーザー数が少ない段階です。
並行リクエストが「ほぼ同時」に届く確率は、ユーザー数が多いほど高くなります。
今の規模では、この問題が現実に起きる確率は非常に低いと判断しました。
2つ目はリスクの大きさです。
万が一カウントがずれて上限を超えてしまっても、今の仕様では致命的な損害には至りません。
数本余分に使われたとしても、ユーザーの金銭的なトラブルに直結するような設計にはなっていないためです。
もし「1本使うごとに課金が発生する」という仕様であれば、話は全く変わります。
リスクの大きさは、「ズレたときに何が起きるか」の仕様によって変わります。
3つ目はかかる工数です。
適切に修正しようとすると、最短でも数時間かかる見込みがありました。
データベースのトランザクション設計を見直し、テストを書き直し、本番環境でのパフォーマンスへの影響を確認する——この一連の流れが必要です。
そのうえで他の機能との干渉がないか確認する時間もかかります。
この3つを並べたとき、「今は先送りにする」という答えが出ました。
先送りは「逃げ」ではなく「意識した判断」
先送りは逃げではありません。
「今の段階では、この工数をここに使うより優先すべきことがある」という、事業判断です。
ただ——これが本当に正しい判断かどうかを問われると、エンジニアの立場だけでは答えられません。
「ユーザー数がどのくらい増えたら対応すべきか」「リスクの許容範囲をどこに設定するか」——この問いに答えるのは、マーケの仕事だからです。
個人開発では自分がマーケでもあるので、自分で答えを出しました。
でも、チームで作っている場合は、この問いをエンジニアが一人で背負うのは間違いだとLilyは思っています。
「どこまで守るか」は誰が先に答えるべきか
技術の問いに見えて、実は事業の問い
「並行安全にする」という技術的な問題の答えは、エンジニアが出せます。
「並行安全にするかどうか」という問いの答えは、マーケが先に出すべきです。
なぜなら、この問いには次の情報が必要だからです。
どんなユーザーが、どれくらいの頻度で使うのか。
カウントがずれた場合の実害を、どの程度まで許容できるか。
このサービスのブランドや信頼性に、どう影響するか。
これらはすべて、マーケ視点の問いです。
エンジニアが「ユーザー数が増えたら対応します」と言っても、それは半分正しくて半分間違いです。
「ユーザー数が何人を超えたら対応する」という閾値を決めるのは、エンジニアの仕事ではないからです。
仕様書の「1行」が成立する条件
CMO時代の仕様書に戻ります。
「並行安全にしてください」と1行書けたのは、自分が先に答えを持っていたからです。
「このキャンペーンには大量のリクエストが来る、だから並行安全でなければならない」——その判断を、仕様書を書く前にすでに自分でしていました。
だから1行に圧縮できていました。
作る側になって初めて、その「先に判断を持っておくこと」の重さを実感しました。
エンジニアに1行渡す前に、マーケが答えを定めておく必要があります。
その準備ができていないと、仕様書の1行は書けません。
書けたとしても、エンジニアは迷います。
どのレベルの「並行安全」を求められているのか、文脈なしには判断できないからです。
「ユーザー数が増えたら対応します」とエンジニアが答えるのは、実は「マーケが閾値を教えてくれるのを待っている」という意味でもあります。
その閾値を先に決めるのが、マーケの役割です。
「要件定義」という言葉の本当の意味
「要件定義」という言葉を聞いたことがある方は多いと思います。
発注側がエンジニアに渡す最初の文書、というイメージを持っている方が多いかもしれません。
でも作る側に立ってみると、「要件定義」の本質が見えてきます。
「エンジニアが設計の判断を下すために必要な前提条件の一覧」——それが要件定義です。
同時接続数の想定、許容できる誤差の範囲、問題が起きたときの影響範囲——これらがなければ、エンジニアは「どこまで守るか」を決められません。
要件定義の本質は、技術の問いをマーケの言葉に翻訳することです。
その翻訳作業を先にやっておくのが、仕様書を「1行で書ける」ための条件でした。
CMO時代のLilyはその翻訳を無意識にやっていましたが、今の立場で初めてその翻訳に気づきました。
翻訳できていない状態で「よろしくお願いします」と1行だけ書いても、それはただの丸投げです。
エンジニアが「難しい」と返してくるのは、翻訳が足りていないサインでもあります。
個人開発を続けながら、気づいていること
「完璧に作る」より「判断の根拠を持つ」が先
個人開発をしていると、「もっとちゃんと作らなければ」という焦りが出ることがあります。
未解決の問題が残っている状態でリリースすることへの、後ろめたさのようなものです。
でも今のLilyが大切にしているのは、「完璧に作ること」よりも「どこまで守るかの判断根拠を持つこと」です。
今の段階では並行安全を先送りにする。ユーザー数が増えたタイミングで見直す。
または、ユーザーから「おかしい」という報告が来たときに即座に対応できる体制を持っておく。
こういう「判断の根拠と次のトリガー」を持っておくことが、個人開発では「完璧に作る」より大切だと感じています。
ログを先に設計しておく理由
先送りにした問題を本当に「先送り」にできるのは、「問題が起きたことに気づける状態」があるからこそです。
Lilyのサービスでは、使用本数のカウント処理にログを残しています。
処理ごとにタイムスタンプ、ユーザーID、処理前後のカウント値を記録する形です。
万が一カウントがずれたとき、このログを見れば「どのユーザーのどの処理で何が起きたか」を後から追えます。
ログがなければ、問題が起きたことすら気づけません。
先送りにするのであれば、「問題が起きたときに検知できる仕組み」をセットで作っておくことが前提です。
先送りの許可証は、気づける仕組みとセットで発行されます。
完璧な実装がなくても、問題を検知して素早く対応できれば、ユーザーへの影響を最小化できます。
「意識した先送り」と「気づいていない問題」はまったく違う
先送りにしたことをメモに残して、対応すべき条件も書いておく。
「意識して先送りにした問題」と「気づいていない問題」は、全く別物です。
前者は状況が変わったときに対応できます。後者は問題が起きてから初めて気づきます。
今回、カウントずれと招待コードのレース問題は「意識して先送りにした」カテゴリに入れました。
引き受けない代わりに、引き受けていないことを明確にしておく——それが、個人開発者としての誠実さだとLilyは思っています。
完璧なものを出してから走るか、走りながら完璧に近づけるか。
どちらが正解かは文脈次第ですが、「走りながら」を選んだときは、何を抱えたまま走っているかを知っておく必要があります。
作ってみて初めてわかること
今回体験したことを一言で言うと、「作る側になったとき、渡す側の言葉の意味が全部変わる」ということです。
「並行安全にしてください」の1文が何十時間の設計を内包していると知ったとき、依頼する側の言葉の使い方も変わります。
「これ、どのくらいかかりそうですか?」という一言も、以前とは全く違う意図で聞けるようになりました。
この問いが本当は「どのくらい複雑な判断が必要か教えてください」という意味だとわかるからです!
実装したことがない部分は、まだわかっていない部分です。
でも「作ってみてわかること」の積み重ねで、少しずつその輪郭が見えてきます。
LilyがどうやってClaude Codeを自律的に動かしているかの仕組みは、有料記事にまとめています。 技術が苦手な方にも読んでいただける形で書いたので、気になる方はフッターのリンクからどうぞ。
まとめ——「どこまで守るか」を先に定めておく
3本のビルドと、1つの先送り
招待コード・セッション・管理画面を同日3本通した夜、1か所だけ「先送りにする」と決めた判断がありました。
並行リクエストが重なったときのカウントずれ、招待コードのレース問題——これらは技術的には解決できます。
でも「今対応するかどうか」は、技術の問いではありません。
ユーザー数・リスクの大きさ・かかる工数を天秤にかけた、事業判断です。
先送りの条件を言語化しておく。ログを残して検知できる体制を先に作っておく。
この2つがあれば、「先送り」は「逃げ」ではなく「意識した判断」になります。
両方の立場に立つことで見えてくること
CMO時代、仕様書に「並行安全にしてください」と1行書くだけで済んでいました。
作る側になって初めて、あの1行の後ろに何十時間があったかを知りました。
「どこまで守るか」を先に定めるのは、マーケの仕事です。
エンジニアは、その答えを受け取って初めて正確に実装できます。
両方の立場を経験すると、その境界が見えてきます。
境界が見えると、発注する言葉も、実装する判断も、どちらも変わります。
先送りにしながら前に進む。意識して先送りにしているうちは、まだ大丈夫——Lilyはそう思っています。
📕 この記事の内容を、実装まで全部まとめた有料記事があります 手順・コード・テンプレをそのまま渡しています AIで「寝てても回る仕組み」をゼロから30日で組む全手順


コメント
ログイン か 会員登録 するとコメントできます