あかね
売り切れたのに、また待てる?メロジョイの待機画面から考える状態管理
2026年10月18日
見出しはありません
要約を生成中...
先日、娘に言われてメロジョイを買おうとしていたら、待機時間が長すぎて、私はいつの間にか通信ログを読んでいました。
メロジョイは若い子たちの間で流行ってるスクイーズで、小3の娘にも「メロジョイチャレンジして!」と懇願されました。で、やってみたら違う意味で面白かったのです。
メロジョイチャレンジとは、公式ECサイトで毎日昼の12時に在庫が更新されるのですが、その時間に発生する争奪戦のことです。
購入操作を進めると、Shopifyのチェックアウト待機画面でカウントダウンが始まります。
ところが、途中で売り切れ表示に。今回も買えなかったかと思いつつ引き続き通信ログを見てたら、なぜか再びカウントダウンが始まったのです。
売り切れたはずなのに、なぜまた待機できるんだ?
購入者としては、まだ買える可能性があるのかを知りたい。エンジニアとしては、何の状態が変わるとこの表示になるのかが気になります。
この記事では、そのときの体験と保存した観測記録をもとに考察してみます。
内部実装や公式仕様を解説するものではなく、どこまでがShopifyの標準機能で、どこからが店舗側の設定や実装なのかも特に切り分けていません。実際に見えたものから、「こういう設計なのかな?」と考えたことを整理してみます。

待機中には、pollというリクエストが繰り返し並んでいました。待機時間が勝手に更新されていたので、ポーリングしてるんかなって思いつつ裏でどうやって状況を確認してるんだろうと思って見てみたら、リクエストが繰り返し送られているので、なるほど、やはりブラウザからポーリングしてるんだろうなと。
実際のスクショ⇩

待機が続いているときのレスポンスには、PollContinueという型と、他にもいろんな情報が含まれていました。
最初に注目したのはqueueEtaSecondsです。
画面のカウントダウンに関係しそうな、推定待ち時間の値でした。
レスポンスには次回問い合わせ時刻を示すpollAfterも含まれていました。
これを見ると、サーバーが「あとどれくらい待つか」と「次はいつ確認するか」を返して、フロントエンドがそれを使って画面を動かしてるのかな、と思いました。
画面上の毎秒の減算をどう実装しているかや、指定時刻にどれだけ厳密に従うかまでは、確認していません。
保存したスクリーンショットから読み取れた値を並べると、次のようになりました。
全リクエストの連続記録ではなく、保存できた時点の抜粋です。
観測 | queueEtaSeconds | available | レスポンス型 |
|---|---|---|---|
1 | 261秒 | true | PollContinue |
2 | 237秒 | false | PollContinue |
3 | 137秒 | true | PollContinue |
4 | 112秒 | true | PollContinue |
5 | 89秒 | true | PollContinue |
6 | 38秒 | true | PollContinue |
7 | 18秒 | true | PollContinue |
最終 | — | — | PollComplete |
推定待ち時間は、記録に残った範囲では261秒から18秒へ減っています。ただし、未観測区間の増減や実際の総待機時間はこの表からは分かりません。
むしろ気になったのは、availableがtrue → false → trueと変化していることでした。「売り切れたのに待機が復活した」という画面上の体験がこの値と関係ありそうです。(画面とレスポンスの時刻を厳密に照合したわけではないので、わからんけど関係ありそうって思っちゃう体験でした)
さらに、availableという名前だけでは、その意味は決まりません。実在庫を反映した値かもしれないし、販売条件を含めた可否かもしれない。少なくとも、在庫数そのものはこの抜粋には出てきません。
一方、available: falseの記録でも型はPollContinueでした。trueのときにも待機は続いています。これを見ると、「商品を買えるか」と「待機を続けるか」は別で持ってるのかな?と気になります。
falseになる

再びtrueに戻るw

ここからは、「こういう仕組みなら、この動きになるかも」と考えた話です。
正解はわからないんですが、分けて考えると「それならありそう」というパターンがいくつかありました。
まず、待機キューが「チェックアウトへ進む人数を調整するもの」なら、商品を買えるかどうかとは別に、待機だけ続けてもおかしくなさそうです。別サービスで動いてるという話ではなくて、管理する状態や役割が分かれてるのかも、という話です。
それなら、一時的に買えなくなっても、すぐ列から外す必要はなさそう。あとでまた買えるようになるかもしれないので、待機資格や順番を残しておく設計もありそうです。
ただし、今回保存されているのは推定待ち時間であって、キューの順位ではありません。時間が減り続けたことをもって「同じ位置が維持された」「並び直していない」とまでは言えません。PollContinueと可否フラグが併存していたことが、この仮説を考えるきっかけです。
たとえば、誰かが在庫を仮確保したけど、購入を完了せず、その分が解放されたとか。そういう仕組みなら、一度買えなくなってから、また買える状態に戻るのもありそうです。
今回の記録には仮確保の発生や解放を示す情報はありません。なので「在庫が戻ったんだ!」とは言えません。販売条件をもう一度判定した結果が変わるなど、在庫数が変わらなくても起こる可能性はありそうです。
大事なのは、「falseになったら必ず終了」とは限らないことです。また買えるようになる可能性があるなら、買えない間もポーリングを続けて、状況を確認するのはわかります。逆に、もう販売が再開しないなら、待ち続けても仕方ない。ここは、どういう販売ルールなのかによりますね。
APIが複数の状態を返していても、画面では短い言葉にまとめる必要があります。もし「今は買えない」も「もう販売終了」も、画面では同じ「売り切れ」になっていたら?
内部では「待機は続いてるけど、今は買えない」から「待機は続いていて、商品も買える状態」に変わっただけでも、こっちからすると「終わったはずなのに復活したんだが?」となります。
別の可能性として、表示の更新タイミングや参照する情報の違いが影響していることも考えられます。
画面の実装まで確認したわけではないので、これも想像です。ただ、「在庫が戻ったのかも」だけじゃなくて、「状態の見せ方でそう感じたのかも」もありそうだなと思いました。
最終的に、レスポンスの型はPollCompleteへ変わりました!!!!!!!

その後、ブラウザはチェックアウト側の画面へ遷移しました。
通信を見た感じ、ThrottlePollで待機状況を確認して、PollContinueならそのまま待機、PollCompleteが返ってきたら次の画面へ進む、という流れっぽいです。
ただ、その先で即売り切れ表示。購入はできませんでした。
そこで、キュー通過後に確認したProposalのレスポンスを見てみます。
購入者側のbuyerProposalには、2,399円の商品1個、通貨JPY、配送方式SHIPPINGという提案がありました。
一方、商品部分には次の型が付いていました。識別子を置き換えた抜粋です。
{
"merchandise": {
"id": "[PRODUCT_VARIANT_ID]",
"digest": "null",
"__typename": "MissingProductVariantMerchandise"
}
}対する販売者側のsellerProposalでは、商品明細が空でした。
{
"merchandise": {
"merchandiseLines": []
}
}販売者側の小計・合計に相当する複数の金額フィールドも0円になっていました。
こっちは「これを1個買いたい」と言ってるのに、販売者側の商品明細は空。この食い違いが気になりました。
MissingProductVariantMerchandiseという型と空の明細からは、対象の商品バリエーションが、このチェックアウトで有効な商品として解決されていない様子が読み取れます。ただし、これだけで商品そのものが削除されたとは言えません。
さらに、session.negotiate.errorsには次のエラーが含まれていました。
MERCHANDISE_PRODUCT_NOT_PUBLISHED
The product is not published for the current context driver.ここで確認できたのは、その処理のコンテキストで商品が公開されていないと扱われたことです。店舗が明示的に非公開へ切り替えたのか、販売チャネルやその他の条件によるのかまでは分かりません。
また、このエラーはキュー通過後のものです。
待機中のavailable: falseと同じ原因だった、と時系列をさかのぼって結びつけることもできません。
ほかに、連絡先と顧客プロフィールの不一致、配送方法が見つからない、支払金額の不整合、必要な処理結果・データが利用できない、というエラーもありました。
商品明細が成立しないことで後続処理に影響した可能性も、独立した問題があった可能性も残ります。今回は商品部分に関係する手がかりを中心に読むことにしました。
実際に見た画面⇩

ここまで見ても、「で、なんでカウントダウンが復活したの?」の答えは出ませんでした。
でも、最初とはちょっと見方が変わりました。
最初は「売り切れたのにまた待つの、おかしくない?」と思ってたんですが、設計によっては普通にあり得る動きなんですよね。
販売可否が回復する前提なら、待機を続けることには意味がある。一方、「売り切れは終了状態で、以後は復帰しない」という仕様なら、同じ見た目の挙動でも想定外かもしれません。
外から見える状態遷移だけでは、その遷移が許されているかまでは分からない。
結局、どう動くのが正解なのかを知らないと、バグなのか仕様なのかも判断できないんだなと。
「バグじゃなくて仕様や!!!」かもしれないし、ただのバグなのかもしれない。
期待する仕様がわからないとなんとも言えない。
今回のレスポンスを考えるうえでは、状態を少なくとも三つに分けると整理しやすくなります。
待機の状態:チェックアウトへ進める段階か
商品の状態:その時点・条件でどう扱われているか
注文の状態:商品・配送・支払などの条件が成立しているか
これは内部構成図ではなく、観測を読むための整理です。
待機中に商品情報が返り、通過後には商品明細や注文条件に関する結果が返っていることから、入場の判断と購入成立の判断には異なる責務がありそうだ、と考えられます。
ただ、商品判定が通過後に初めて行われることや、その時点だけで在庫が確保されることを意味しません。
そして、システム上で正常でも、利用者に分かりやすいとは限りません。
「売り切れ」と言われたら終わりだと思うし、カウントダウンが始まったら、まだ買えるのかなと思う。行ったり来たりされたら、結局待ってていいん?買えんのに待たされるのは時間無駄!!!ってなります。
仮に回復を待つ設計なら、「現在は購入できませんが、待機を継続しています」のように状態を分けて伝える余地があります。
もちろん、それが今回の正解かはわかりません。ただ、自分がこういう画面を作るなら、APIの状態をどういう言葉で伝えるかまで考えたいなと思いました。
今回確認できたのは、画面では売り切れ表示のあとにカウントダウンが再開したこと、保存した通信ではavailableがtrue → false → trueと変化したこと、最後にPollCompleteを経て遷移したものの、チェックアウトでは有効な商品明細が成立せず購入できなかったことです。
一方、画面とフラグの直接の対応、キューの順位、在庫確保や解放の有無、商品が非公開として扱われた具体的な理由は、手元の記録には残っていません。連続した全通信や表示の分岐条件、期待される仕様がそろわないと、ここから先の仮説を選び分けるのは難しそうです。
それでも、「なんでまた待てるの?」から、待機と購入をどう分けるか、一度ダメになった状態をどう戻すか、それを画面でどう伝えるかまで考えられたのは面白かったです。
Networkタブを見れば全部わかる、とはいかなかったんですが、「ここまでは見えた。ここから先はまだわからない」を整理できたのは収穫でした。
私は何をしていましたか???って感じですけどw
覚えてる限り、そしてメロジョイが手に入るまで、毎日メロジョイチャレンジしようと思います。(全然買えねぇ)
そしてこれ仕様じゃなくてバグだったパターンで調査してって言われる開発者の立場だったらクソだる案件だなって思いました。他の購入者による在庫確保と解放が関係しているなら、再現が、、、だるい、、🫨
挙動を正として仕様を変えたいw
要約
コメント
まだコメントはありません。