Azure Cosmos DB JavaScript SDK 4.9.3解説:ORDER BYページング実装と移行手順

2026年4月20日に公開された @azure/cosmos 4.9.3 は、大きな新機能追加というより、Azure Cosmos DB JavaScript SDK で ORDER BY と継続トークンを使うページング処理の正確性を高める重要なバグ修正です。特に、一覧API、検索結果、エクスポート、ETL、監査ログ取得などで ORDER BY + fetchNext() + continuationToken を使っている開発チームは、早めに影響範囲を確認する価値があります。

結論から言うと、4.9.3 への更新で楽になるのは「特殊文字を含むソート値でページング結果が欠落しないよう、アプリ側で不自然な回避策を抱えなくてよくなる」点です。リリースノートでは、継続トークンを使う ORDER BY クエリで SQL フィルター生成が正しくない問題が修正され、orderByItem に含まれるバックスラッシュやシングルクォートが WHERE 句へ埋め込まれる前に適切にエスケープされるようになったと説明されています。(GitHub)

目次

Azure Cosmos DB JavaScript SDK 4.9.3 の更新点を整理する

Azure Cosmos DB JavaScript SDK 4.9.3 の対象パッケージは @azure/cosmos です。Microsoft Learn の SDK ドキュメントでも JavaScript/TypeScript 向けクライアントライブラリは version 4.9.3 として掲載されており、SQL API のデータベース、コンテナー、JSON ドキュメント、SQL風クエリを扱うためのパッケージとして説明されています。(Microsoft Learn)

今回の修正は、ORDER BY を使ったページング処理の内部実装に関わります。PR の説明では、継続トークン内の orderByItem 値が SQL の WHERE 句に埋め込まれる際、バックスラッシュシーケンスやシングルクォートが未エスケープのまま扱われることで、Cosmos DB SQL パーサーがリテラル文字ではなく Unicode エスケープや制御文字として解釈し、結果としてフィルター不一致や行のスキップにつながる可能性があるとされています。(GitHub)

観点4.9.3 でのポイント開発現場への影響
対象処理ORDER BY クエリと継続トークンを組み合わせたページング一覧、検索、エクスポート、バッチ取得で重要
修正内容orderByItem のバックスラッシュとシングルクォートを SQL 文字列化前にエスケープアプリ側の独自エスケープや再取得処理を減らしやすい
影響が出やすい値\u2013、\n、\t、Windowsパス、引用符を含む文字列などグローバル向けデータ、ファイルパス、商品名、顧客名で注意
移行の性質パッチバージョン更新API変更よりも回帰テストが重要

重要なのは、これは単なる「表示上の不具合修正」ではないという点です。ページング中に一部の行が静かにスキップされる可能性がある場合、画面では気づきにくく、帳票、同期処理、請求データ、監査データに影響が出てから発覚することがあります。

影響を受けるコードの見つけ方

まず、リポジトリ内で @azure/cosmos を使っている箇所と、ページング付きクエリを使っている箇所を洗い出します。特に見るべきキーワードは次の通りです。

npm ls @azure/cosmos

rg "ORDER BY|continuationToken|enableQueryControl|fetchNext|maxItemCount|forceQueryPlan" src test

rg が入っていない場合は、grep でも代用できます。

grep -R "ORDER BY\|continuationToken\|enableQueryControl\|fetchNext\|maxItemCount" ./src ./test

影響度は、単に ORDER BY を使っているかどうかだけでは判断しません。継続トークンを使って複数ページを取得しているか、ソート対象の値に特殊文字が入り得るか、結果の欠落が業務上どれだけ重いかで優先順位を決めます。

優先度条件推奨対応
高ORDER BY + continuationToken を使い、検索結果・エクスポート・ETL・監査・請求に利用している4.9.3 へ更新し、特殊文字データで即回帰テスト
中管理画面やAPIの一覧で ORDER BY ページングを使っているステージングでページング結果の件数・順序・重複を検証
低point read、create、upsert、delete が中心で、ページング付き ORDER BY を使っていない通常の依存関係更新として計画的に適用
要注意古い v3 系、または 4.9.0 より前から一気に更新する4.9.3 だけでなく、途中バージョンの変更点も確認

4.9.0 では enableQueryControl を使ったクエリ継続トークンのサポートが追加されています。4.9.3 だけを見て判断するのではなく、4.9.0 以降の継続トークン関連の変更も合わせて確認すると、移行判断を誤りにくくなります。(GitHub)

4.9.3 で実装が楽になるポイント

アプリ側でソート値を無理に加工しなくてよくなる

今回の修正対象は、アプリが発行する元の SQL クエリそのものではなく、SDK が継続トークンから内部的に生成する ORDER BY 用のフィルター処理です。

たとえば、ソートキーに次のような値が入るケースを考えます。

Gold\u2013Foran
C:\temp\O'Reilly
line\nfeed
user\tname

4.9.3 では、こうした値が継続トークン経由で次ページ取得に使われる際、バックスラッシュやシングルクォートが適切に扱われるよう修正されています。PR では、バックスラッシュを先にエスケープし、その後シングルクォートを \u0027 として扱う方針が説明されています。(GitHub)

これにより、アプリ側で「ソートキーからバックスラッシュを除去する」「別のソート用フィールドを作る」「取得後に再ソートして欠落を補正する」といった不自然な回避策を減らしやすくなります。

グローバルデータのページングで安心感が増す

日本語圏だけのシステムでも、Azure Cosmos DB はグローバルサービスとして使われることが多く、データには英語名、欧文記号、ファイルパス、エスケープシーケンス風の文字列が混ざります。

特に注意したいのは、次のような項目です。

データ例起きやすい場所
O'Reilly のようなシングルクォート顧客名、出版社名、商品名、組織名
C:\Users\... のようなパスログ、エージェント情報、ファイル同期
\n、\t のような文字列ログ本文、テンプレート、設定値
\u2013 のような文字列外部システム由来の文字列、インポートデータ
正規表現風の値検索条件、ルール管理、監視設定

画面上は「次ページに進める」だけの処理でも、内部ではソート順と継続位置が正しく対応していないと、重複や欠落が発生します。4.9.3 はこの種の問題に対する修正なので、グローバル向けSaaSや多言語データを扱うチームほど価値があります。

4.9.3 への更新手順

パッケージを固定して更新する

本番環境で再現性を重視する場合は、まず @azure/cosmos を明示的に 4.9.3 へ固定して検証します。

npm install @azure/[email protected]
npm ls @azure/cosmos

pnpm を使っている場合は次の通りです。

pnpm add @azure/[email protected]
pnpm list @azure/cosmos

Yarn の場合は次のように更新します。

yarn add @azure/[email protected]
yarn why @azure/cosmos

package.json では、検証段階では次のように exact version にしておくと、CI/CD とローカル環境の差分を減らせます。

{
  "dependencies": {
    "@azure/cosmos": "4.9.3"
  }
}

検証が安定した後に ^4.9.3 を許容するかどうかは、チームの依存関係更新ポリシー次第です。運用が厳しいシステムでは、lockfile と CI のバージョンチェックで意図しない更新を防ぐ方が安全です。

Node.js 実行環境も確認する

Microsoft Learn の Node.js クイックスタートでは、Azure Cosmos DB for NoSQL の Node.js サンプルにおける前提条件として Node.js 22 以降が示されています。既存システムでは LTS 方針やアプリケーションの対応状況もあるため、SDK 更新と同時に Node.js の実行バージョン、ビルドターゲット、テスト環境を確認しておきましょう。(Microsoft Learn)

node -v
npm -v

特に Azure Functions、コンテナー、GitHub Actions、Azure Pipelines で実行している場合、ローカルだけ更新しても本番ランタイムが古いままということがあります。

ORDER BY と継続トークンの安全な実装例

Microsoft Learn の SDK ドキュメントでは、継続トークンを使うクエリで enableQueryControl: true を設定する例が示されています。また、ORDER BY クエリでは初期呼び出しで resources が空、continuationToken が undefined になる場合があるため、resources.length > 0 を確認してから継続トークンを扱うことが説明されています。(Microsoft Learn)

以下は、APIの一覧取得やバッチ処理で使いやすい TypeScript の実装例です。

import { CosmosClient, SqlQuerySpec } from "@azure/cosmos";

const client = new CosmosClient({
  endpoint: process.env.COSMOS_ENDPOINT!,
  key: process.env.COSMOS_KEY!,
});

const database = client.database(process.env.COSMOS_DATABASE!);
const container = database.container(process.env.COSMOS_CONTAINER!);

type ListItemsInput = {
  tenantId: string;
  pageSize: number;
  continuationToken?: string;
};

export async function listItemsBySortKey(input: ListItemsInput) {
  const query: SqlQuerySpec = {
    query: `
      SELECT c.id, c.tenantId, c.sortKey, c.name
      FROM c
      WHERE c.tenantId = @tenantId
      ORDER BY c.sortKey ASC
    `,
    parameters: [
      {
        name: "@tenantId",
        value: input.tenantId,
      },
    ],
  };

  const iterator = container.items.query(query, {
    maxItemCount: input.pageSize,
    continuationToken: input.continuationToken,
    enableQueryControl: true,
    forceQueryPlan: true,
  });

  while (iterator.hasMoreResults()) {
    const { resources, continuationToken } = await iterator.fetchNext();

    if (resources.length === 0) {
      continue;
    }

    return {
      items: resources,
      continuationToken,
    };
  }

  return {
    items: [],
    continuationToken: undefined,
  };
}

この実装で大切なのは、次の4点です。

実装ポイント理由
SQL文字列へ値を直接連結しないユーザー入力や外部データはパラメーター化する
maxItemCount を指定する1ページあたりの件数を制御し、テストでもページングを再現しやすくする
resources.length === 0 のページを考慮するORDER BY とクロスパーティションでは空ページが返る場合がある
continuationToken を不透明な値として扱うアプリ側でパース・改変・短縮しない

4.9.3 の修正は、SDK内部で継続トークン由来の値を扱う際のエスケープ改善です。アプリ側のSQL組み立てで文字列連結をしてよい、という意味ではありません。 検索条件やテナントID、ステータスなどの値は、引き続きパラメーター化して渡します。

回帰テストで見るべき特殊文字パターン

4.9.3 への移行で最も重要なのは、単に npm install することではありません。ORDER BY とページングを使うコードに対して、特殊文字を含むデータを使ったテストを追加することです。

以下は Vitest や Jest で考え方を流用できるテスト例です。

const testItems = [
  {
    id: "item-001",
    tenantId: "tenant-a",
    sortKey: "Gold\\u2013Foran",
    name: "literal unicode sequence",
  },
  {
    id: "item-002",
    tenantId: "tenant-a",
    sortKey: "C:\\temp\\O'Reilly",
    name: "windows path and quote",
  },
  {
    id: "item-003",
    tenantId: "tenant-a",
    sortKey: "line\\nfeed",
    name: "literal newline sequence",
  },
  {
    id: "item-004",
    tenantId: "tenant-a",
    sortKey: "tab\\tvalue",
    name: "literal tab sequence",
  },
  {
    id: "item-005",
    tenantId: "tenant-a",
    sortKey: "normal-value",
    name: "normal",
  },
];

async function collectAllPages(tenantId: string) {
  let continuationToken: string | undefined;
  const allItems: unknown[] = [];

  do {
    const page = await listItemsBySortKey({
      tenantId,
      pageSize: 1,
      continuationToken,
    });

    allItems.push(...page.items);
    continuationToken = page.continuationToken;
  } while (continuationToken);

  return allItems;
}

test("ORDER BY pagination returns all items with special characters", async () => {
  await seedItems(testItems);

  const results = await collectAllPages("tenant-a");

  expect(results).toHaveLength(testItems.length);

  const ids = results.map((item: any) => item.id);
  expect(new Set(ids).size).toBe(testItems.length);
});

pageSize: 1 にしているのは、ページングを強制的に発生させるためです。通常の pageSize: 100 では、テストデータ件数が少ない場合に1ページで終わってしまい、継続トークンの問題を検出できません。

テストでは次の観点を確認します。

確認項目期待する結果
総件数投入した件数と一致する
IDの重複重複がない
欠落特殊文字を含む項目が消えない
順序ORDER BY の定義通りに並ぶ
再開保存した continuationToken から次ページを取得できる

DevOps と platform engineers 向けの自動化ポイント

CI で SDK バージョンを検証する

依存関係の更新は、ローカルでは成功していても、CIや本番ビルドで別バージョンが入ることがあります。まずは CI で @azure/cosmos の解決バージョンをチェックします。

name: cosmos-sdk-check

on:
  pull_request:
  push:
    branches:
      - main

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: "22"
          cache: "npm"

      - run: npm ci

      - name: Check @azure/cosmos version
        run: |
          node -e "const v=require('./node_modules/@azure/cosmos/package.json').version; console.log(v); if(v !== '4.9.3') process.exit(1)"

      - run: npm test

バージョン固定を長期運用したくない場合でも、少なくとも移行直後の数週間はこのチェックを入れておくと、意図しない依存関係の巻き戻りを防ぎやすくなります。

Renovate や Dependabot では自動マージを分ける

@azure/cosmos のようにデータ取得結果に影響し得る SDK は、単純なパッチ更新でも自動マージ条件を慎重に設計します。

更新種別推奨ルール
patch自動PR作成。回帰テスト通過後に手動または限定自動マージ
minorステージング検証を必須化
major移行計画、互換性確認、段階リリースを必須化
preview機能を含む変更本番ワークロードでは別途レビュー

今回のような修正では、依存関係更新PRに次のチェックを紐づけると実務的です。

npm ci
npm ls @azure/cosmos
npm run test
npm run test:integration

統合テストでは、Cosmos DB Emulator または検証用 Azure Cosmos DB アカウントを使い、ORDER BY + maxItemCount: 1 + 特殊文字データでページングを確認します。

移行時に失敗しやすいポイント

fetchAll だけで検証してしまう

fetchAll() は便利ですが、ページングの境界で起きる問題を見逃すことがあります。今回の更新を検証するなら、fetchNext() と continuationToken を使い、複数ページに分けて取得するテストを必ず入れてください。

const iterator = container.items.query(query, {
  maxItemCount: 1,
  enableQueryControl: true,
  forceQueryPlan: true,
});

while (iterator.hasMoreResults()) {
  const page = await iterator.fetchNext();
  // ここで resources と continuationToken を検証する
}

通常文字列だけでテストしてしまう

abc、001、2026-04-20 のような値だけでは、今回の修正対象を十分に確認できません。最低でも次のような値をソートキーに入れてください。

O'Reilly
Gold\u2013Foran
C:\logs\2026\file.txt
line\nfeed
tab\tvalue

継続トークンをクライアント側で加工してしまう

継続トークンは、アプリケーションが意味を解釈するための値ではありません。ページングAPIでフロントエンドへ返す場合も、トークンを分割、短縮、JSONパース、部分保存しないようにします。

外部公開APIでトークンをそのまま返したくない場合は、サーバー側に保存して短いカーソルIDを返す、または署名付きトークンとしてラップする設計を検討します。ただし、その場合も SDK から返された continuationToken 自体は不透明な値として保持します。

SDK更新だけでデータ品質を保証したつもりになる

4.9.3 は重要な修正ですが、アプリのページング設計全体を自動的に正しくするものではありません。次のような問題は、引き続きアプリ側で考慮が必要です。

問題対策
ページング中にデータが追加・削除される業務要件に応じてスナップショット的な取得、期間固定、再集計を検討
ソートキーが重複するORDER BY c.sortKey, c.id のように安定した順序を設計
クライアントが古いトークンを再利用するトークンの有効期限や検索条件との紐づけをアプリ側で管理
大量取得でRUが跳ねるmaxItemCount、パーティションキー、インデックス、実行時間を監視

実装・移行・自動化でどこが楽になるのか

開発者にとっては、ORDER BY ページングの特殊文字対応を SDK 側に任せやすくなります。これにより、ソート値の前処理や独自の再取得ロジックを減らし、クエリ実装をシンプルにできます。

platform engineers にとっては、グローバルデータや多言語データを扱うワークロードで、特殊文字によるページング不整合のリスクを下げやすくなります。特に、複数チームが同じ Cosmos DB アカウントや共通SDKラッパーを使っている場合、共通ライブラリ側で 4.9.3 へ更新しておく効果が大きくなります。

DevOps teams にとっては、移行作業を「SDK更新」だけで終わらせず、CIでのバージョン固定、特殊文字を含む統合テスト、段階リリース、監視ログの確認まで自動化しやすい更新です。

導入判断の目安

Azure Cosmos DB JavaScript SDK 4.9.3 は、すべてのプロジェクトで同じ緊急度になる更新ではありません。次の基準で判断すると、優先順位を付けやすくなります。

利用状況判断
ORDER BY + continuationToken を本番で使っている優先度高。早めに検証して更新
エクスポート、レポート、同期、監査でページング取得している優先度高。欠落検知テストを追加
特殊文字を含む名称、パス、ログ、外部データを扱う優先度高。今回の修正と相性が強い
CRUD中心でクエリページングを使わない通常の依存関係更新として対応
v3以前から移行する4.9.3単体ではなく、移行ガイドと既存API差分を確認

まずは npm ls @azure/cosmos で現在のバージョンを確認し、次に ORDER BY、continuationToken、fetchNext() を使う箇所を検索してください。該当コードがある場合は、4.9.3 へ更新した検証環境で、バックスラッシュやシングルクォートを含むデータを使ったページングテストを追加します。

最後に、本番展開では一括更新ではなく、検索API、管理画面、エクスポート処理、バッチ処理の順に影響度の高いワークロードから確認するのがおすすめです。Azure Cosmos DB JavaScript SDK 4.9.3 は派手な新機能ではありませんが、データ取得結果の信頼性を支える実務的な更新です。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次