[アップデート] Amazon Redshift の Top-K クエリ向けのパフォーマンス最適化を試してみました
クラウド事業本部の石川です。Amazon Redshift / Amazon Redshift Serverless で ORDER BY 句と LIMIT 句を組み合わせた、いわゆる「Top-K クエリ」の実行が大幅に高速化されるパフォーマンス最適化が導入されました。無関係なデータブロックをインテリジェントにスキップすることで、クエリ実行速度の向上と処理データ量の削減が同時に実現できる、運用に直結するうれしいアップデートです。
Top-K クエリ向けのパフォーマンス最適化とは
今回のアップデートは、ORDER BY 句と LIMIT 句を含む「Top-K クエリ」(上位 K 件を取得するクエリ)の処理を最適化するものです。Amazon Redshift が ORDER BY 列の最小値・最大値(min/max)を活用し、結果に影響しないデータブロックを実行時にスキップすることで、必要最小限のデータのみを処理します。
主な変更点は以下のとおりです。
ORDER BY列の最小値・最大値に基づいて、読み取るデータブロックを並べ替え・調整- 上位 K 行に該当する候補行のみをメモリ内に保持
- ソート済み、または部分的にソート済みの列ではテーブル全体スキャンを回避
- 不要な I/O とコンピュートオーバーヘッドを削減
- データが降順で永続的に格納されている場合(例:
ORDER BY ... DESC LIMIT K)に特に大きな効果
公式アナウンスでは、「処理データ量を劇的に削減し、結果をより高速に返すことで Top-K クエリのパフォーマンスが大幅に向上する(dramatically reducing the amount of data processed / top-k query performance improves dramatically)」と表現されています。
仕組みのイメージ
たとえば、数億〜数十億件のトランザクションテーブルから「直近の注文 10 件」を取得したいケースを考えてみます。order_date で降順にソートして格納されている場合、従来であれば多くのデータブロックを走査する必要がありました。
今回の最適化では、ORDER BY order_date DESC LIMIT 10 のようなクエリに対して、各データブロックの min/max メタデータから「上位 10 件に入りうるブロック」だけを優先して読み取り、上位 10 件が確定した時点で残りのブロックは読み飛ばす、というイメージで動作します。
利用可能なバージョン
本最適化は、Amazon Redshift のパッチリリース P199 以降で利用可能です。クラスターのメンテナンストラックに従って自動的に適用されます。
なお、パッチリリースノートには次のような記述があります(クラスターバージョンドキュメントより)。
Enhanced query execution performance when using ORDER BY and LIMIT clauses by eliminating unnecessary data scans
Top-K クエリ向けのパフォーマンス最適化を試す
本最適化は、対象となるクエリに対して 自動的に適用 されます。クエリの書き換えや、特別な設定変更は一切必要ありません。
クラスターまたはサーバーレスワークグループが P199 以降のパッチに更新されれば、既存の ORDER BY ... LIMIT K クエリがそのまま高速化の恩恵を受けます。
検証環境
| 項目 | 値 |
|---|---|
| サービス | Amazon Redshift Serverless |
| ワークグループ | devio-wg |
| データベース | dev |
| リージョン | ap-northeast-1 |
| スペック | 4 RPU |
| クエリ実行 | Redshift Data API(aws redshift-data execute-statement) |
| 計測値 | describe-statement の Duration(ナノ秒) |
| キャッシュ制御 | 各実行前に SET enable_result_cache_for_session TO OFF; |
| 計測方針 | 各クエリを連続 2 回実行(1 回目 = cold, 2 回目 = warm) |
検証データ
| 項目 | 値 |
|---|---|
| ソース | s3://<bucket_name>/ssbgz/lineorder/ |
| ファイル数 | 8(GZIP 圧縮 PSV) |
| 圧縮済みサイズ | 約 26.9 GB |
| ロード行数 | 600,037,902 行 |
| ロード時間 | 約 644 秒 |
テーブル定義
CREATE TABLE IF NOT EXISTS "public"."lineorder" (
"lo_orderkey" BIGINT ENCODE AZ64,
"lo_linenumber" INTEGER ENCODE AZ64,
"lo_custkey" INTEGER ENCODE AZ64,
"lo_partkey" INTEGER ENCODE AZ64,
"lo_suppkey" INTEGER ENCODE AZ64,
"lo_orderdate" DATE ENCODE AZ64,
"lo_orderpriority" VARCHAR(32) ENCODE ZSTD,
"lo_shippriority" VARCHAR(2) ENCODE ZSTD,
"lo_quantity" INTEGER ENCODE AZ64,
"lo_extendedprice" BIGINT ENCODE AZ64,
"lo_ordertotalprice" BIGINT ENCODE AZ64,
"lo_discount" INTEGER ENCODE AZ64,
"lo_revenue" BIGINT ENCODE AZ64,
"lo_supplycost" BIGINT ENCODE AZ64,
"lo_tax" INTEGER ENCODE AZ64,
"lo_commitdate" DATE ENCODE AZ64,
"lo_shipmode" VARCHAR(16) ENCODE ZSTD
)
DISTSTYLE AUTO
SORTKEY AUTO;
検証クエリ
A. シンプル Top-K(フィルタなし、LIMIT 10)
SELECT lo_orderkey, lo_custkey, lo_orderdate, lo_revenue
FROM public.lineorder
ORDER BY lo_revenue DESC
LIMIT 10;
B. フィルタ付き Top-K(1997 年・LIMIT 10)
SELECT lo_orderkey, lo_custkey, lo_orderdate, lo_revenue
FROM public.lineorder
WHERE lo_orderdate BETWEEN DATE '1997-01-01' AND DATE '1997-12-31'
ORDER BY lo_revenue DESC
LIMIT 10;
C. GROUP BY + Top-K(顧客別売上 Top-10)
SELECT lo_custkey,
SUM(lo_revenue) AS total_revenue,
COUNT(*) AS orders
FROM public.lineorder
GROUP BY lo_custkey
ORDER BY total_revenue DESC
LIMIT 10;
D. Window 関数 Top-N per group(年別 Top-3)
SELECT yr, lo_orderkey, lo_custkey, lo_revenue
FROM (
SELECT EXTRACT(YEAR FROM lo_orderdate) AS yr,
lo_orderkey, lo_custkey, lo_revenue,
ROW_NUMBER() OVER (PARTITION BY EXTRACT(YEAR FROM lo_orderdate)
ORDER BY lo_revenue DESC) AS rn
FROM public.lineorder
) t
WHERE rn <= 3
ORDER BY yr, rn;
E. K サイズ違い(K = 10 / 10,000 / 1,000,000)
-- K を変えながら同形のクエリを実行
SELECT lo_orderkey, lo_revenue
FROM public.lineorder
ORDER BY lo_revenue DESC
LIMIT <K>;
計測結果
| # | パターン | cold (秒) | warm (秒) | warm の cold 比 |
|---|---|---|---|---|
| A | シンプル Top-K(LIMIT 10) |
5.382 | 1.430 | 約 3.8 倍速 |
| B | フィルタ付き Top-K(1997 年・LIMIT 10) |
1.522 | 1.292 | 約 1.2 倍速 |
| C | GROUP BY + Top-K(顧客別 Top-10) | 17.842 | 16.293 | 約 1.1 倍速 |
| D | Window 関数 Top-N per group(年別 Top-3) | 556.053 | 564.281 | ほぼ同じ |
| E | K=10 | 6.203 | 0.903 | 約 6.9 倍速 |
| E | K=10,000 | 0.783 | 0.788 | ほぼ同じ |
| E | K=1,000,000 | 468.471 | (未計測 / 中断) | - |
warm 値は同じ SQL を直後にもう 1 回実行したときの計測値。コンパイルキャッシュ+ブロックキャッシュの両方が効いた状態に相当すると考えられる。
K のサイズと実行時間(cold)
| K | 実行時間 (cold) | K=10 比 |
|---|---|---|
| 10 | 6.203 秒 | 1.0 倍 |
| 10,000 | 0.783 秒 | 0.13 倍 |
| 1,000,000 | 468.471 秒 | 約 75 倍 |
K=10 と K=10,000 でほぼ同じ時間(むしろ K=10 のほうが遅い: cold ペナルティ)ですが、K=1,000,000 で約 600 倍以上に膨らみ、ある閾値を超えると急激に跳ね上が状態を観察しました。
主要な知見
1. Top-K 最適化(ORDER BY ... LIMIT)は強力
- 6 億行から上位 10 件を取り出すクエリ(A)が warm 1.4 秒、E_K10 では warm 0.9 秒
- 各並列スライスで K 件のヒープを保持しマージするだけのため、フルソートを回避
2. K のサイズによる指数関数的増加
- K=10 → 10,000 :実行時間がほぼ変わらない(むしろ K=10,000 cold は約 0.78 秒)
- K=10,000 → 1,000,000 :468 秒以上に膨張
- 各スライスのヒープサイズと最終マージコストが許容量を超えると、Top-K 最適化のメリットが失効する
3. Window 関数 Top-N per group の注意
ROW_NUMBER() OVER (PARTITION BY ... ORDER BY ...)は 全件にランクを付与してからフィルタ する処理- Top-K 最適化の対象外で、6 億行に対しフルソート + PARTITION 処理が走り、約 9 分(556 〜 564 秒)
4. GROUP BY + Top-K のボトルネックは集計
- C は cold/warm ともに 16〜18 秒
- ソート/LIMIT は秒未満で、HashAggregate(6 億行を
lo_custkeyで集約)が支配的 - DISTKEY を集計キーに合わせると分散シャッフルが減って改善余地がある
5. キャッシュ効果は軽量クエリほど顕著
| クエリ | cold/warm 比 | 解釈 |
|---|---|---|
| A シンプル Top-K | 約 3.8 倍 | 軽量クエリではコンパイルキャッシュの寄与が大きい |
| E_K10 | 約 6.9 倍 | 同上、ブロックキャッシュも効きやすい |
| C GROUP BY | 約 1.1 倍 | 集計コストが支配的なためほぼ変わらない |
| D Window 関数 | ほぼ同じ | フルスキャン + ソートが支配的 |
軽量で頻繁に実行される Top-K クエリは 2 回目以降の体感値(warm) で評価するのが実態に近い、ということが確認できました。
効果が出やすいユースケース
公式アナウンスで示されている例を含め、以下のような Top-K クエリで特に効果が高くなります。
- 数百万〜数十億件のトランザクションから 直近 K 件の注文 を抽出する
- 売上やアクセス数の 上位 K 件の商品・コンテンツ を取得する
- LLM ワークロードで 直近の K 件のプロンプト をモニタリングする
- スコアやレイテンシの 上位(または下位)K 件のレコード を抽出する
特に、ORDER BY 対象列がソートキーで降順方向に追記され続けるようなテーブル(時系列ログ、注文履歴など)では、不要なブロックスキャンが大きく削減されるため、効果を体感しやすい構成と言えます。
最後に
当初はあまりにも情報がなくて、なぜ速くなるのがイメージできませんでしたが、様々なクエリの実行を通じて、「無関係なデータブロックをインテリジェントにスキップ」をイメージできるようになりました。
今回のアップデートにより、Amazon Redshift の Top-K クエリは追加料金も クエリ書き換えも不要のまま 、内部最適化だけで実行速度と処理データ量が改善されるようになりました。パッチリリース P199 以降であれば、既存ワークロードがそのまま恩恵を受けられます。
ダッシュボードや BI レポートで ** 「直近の N 件」「上位 N 件」を表示する SQL を多用 ** している環境では、応答時間と消費リソースの両面で効果が見込めます ので、対象パッチへの更新状況を確認してみてはいかがでしょうか。






