TECH JOURNAL

Amazon 在庫同期アーキテクチャ — マルチモール展開で在庫過剰販売をゼロにする設計

ShareXB!
Amazon 在庫同期アーキテクチャ — マルチモール展開で在庫過剰販売をゼロにする設計
目次

はじめに

Amazon に加えて楽天・自社 EC を並走させる EC 事業者が直面する最大の課題のひとつが 在庫の過剰販売(Overselling)。「Amazon で売れたのに楽天の在庫を更新するのが遅れて、楽天でも同じ商品が売れてしまった」というケースは、手動運用では防ぎようがない。

ここでは SP-API Feeds API を中心に、マルチモール間で在庫を一元管理するアーキテクチャを解説する。

設計の大前提: 在庫の「単一の真実の源」を決める

まず設計の前に決めるべきことがある。在庫数のマスターをどこに置くか。

  • WMS(倉庫管理システム)
  • 自社 DB
  • Amazon の FBA 在庫

弊社案件では自社 DB をマスターとし、Amazon・楽天への在庫通知を Push 型で行うパターンが最も管理しやすかった。FBA 在庫をマスターにすると、FBA の在庫確認 API を都度叩く必要があり、レートリミットとの戦いになる。

アーキテクチャ全体像

注文確定イベント(各モール)
        ↓
SQS: 在庫差し引きキュー(FIFO)
        ↓
在庫更新 Lambda
  ├── 自社 DB: stock_quantity を atomic decrement
  ├── Amazon: Feeds API で在庫通知
  └── 楽天: RMS API で在庫数更新

SQS FIFO を挟むことで、複数モールから同時に注文が入っても在庫差し引きが順序保証される。

Amazon への在庫通知: Feeds API

Amazon への在庫更新は POST_INVENTORY_AVAILABILITY_DATA フィードを使う。REST 的な直接更新 API は存在しないため、XML フィードを Submit してジョブ完了をポーリングする非同期フローになる。

// フィード XML の生成
function buildInventoryFeed(sku: string, quantity: number): string {
  return `<?xml version="1.0" encoding="UTF-8"?>
<AmazonEnvelope>
  <Header>
    <DocumentVersion>1.01</DocumentVersion>
    <MerchantIdentifier>${MERCHANT_ID}</MerchantIdentifier>
  </Header>
  <MessageType>Inventory</MessageType>
  <Message>
    <MessageID>1</MessageID>
    <OperationType>Update</OperationType>
    <Inventory>
      <SKU>${sku}</SKU>
      <Quantity>${quantity}</Quantity>
    </Inventory>
  </Message>
</AmazonEnvelope>`
}
 
// フィードの Submit
const createFeedResponse = await feedsApi.createFeed({
  feedType: "POST_INVENTORY_AVAILABILITY_DATA",
  marketplaceIds: [MARKETPLACE_JP],
  inputFeedDocumentId: feedDocumentId,
})

ポーリングとタイムアウト設計

Feeds API の処理は数秒〜数分かかる。Lambda の同期待ちは現実的でないため、Step Functions で非同期ポーリングするか、SQS の DelaySeconds で一定時間後にステータス確認ジョブを投入するとよい。

// 結果確認を 30 秒後に再スケジュール
await sqs.sendMessage({
  QueueUrl: FEED_STATUS_CHECK_QUEUE,
  MessageBody: JSON.stringify({ feedId }),
  DelaySeconds: 30,
})

過剰販売防止の安全弁: 安全在庫バッファ

在庫数が 1 個以下になったら Amazon 在庫を 0 にセットする「安全弁」を入れておく。同時注文で在庫を使い切った後、同期が追いつくまでの数秒〜数十秒の間に別の注文が入るリスクを防ぐ。

const safeQuantity = Math.max(0, currentStock - SAFETY_BUFFER)
await notifyAmazonInventory(sku, safeQuantity)

SAFETY_BUFFER は SKU の 1 日あたり注文速度(velocity)から動的に計算するのが理想。売れ筋は 2〜3、遅いものは 0 でいい。

リカバリー: 在庫差し引きに失敗したケース

SQS の可視性タイムアウトを超えると、メッセージが再処理される。このとき在庫の二重差し引きを防ぐために、注文 ID に対して「差し引き済みフラグ」を DynamoDB で管理する。

// DynamoDB で差し引きの冪等チェック
const key = `inv:${orderId}:${sku}`
const alreadyProcessed = await ddb.get({ TableName: "idempotency", Key: { pk: key } })
if (alreadyProcessed.Item) return // 既処理
 
await ddb.put({
  TableName: "idempotency",
  Item: { pk: key, ttl: Math.floor(Date.now() / 1000) + 86400 },
})
// 実際の在庫差し引き処理
await decrementStock(sku, quantity)

まとめ

課題解決策
同時注文による過剰販売SQS FIFO で在庫差し引きを直列化
Amazon 在庫更新の遅延Feeds API + 非同期ポーリング
Lambda リトライ時の二重差し引きDynamoDB 冪等チェック
同期遅延中の過剰販売安全在庫バッファ
ShareXB!

この記事を書いた人

渡部 誠也

執行役員 / CTO

独立系 SIer で Web・組み込み・基幹システムの開発を経験し、2017 年に illustrious へ。CTO としてシステム開発事業を立ち上げ、要件定義からコーディングまで一貫して担う。EC に特化した Web アプリケーションを数多く手がける。

ECの業務やシステムについて、
ご相談ください。

いまの運用で困っていること、実現したいことから、一緒に整理します。