1. はじめに
Autonomous Database Serverless(ADB-S)で Oracle Database In-Memory を使うとき、公式ドキュメントがサイズについて書いているのは次の一文だけです1。
自律型AIデータベース・インスタンスに16以上のECPUがある場合、データベース・インメモリー機能はデフォルトで有効になり、SGAの最大50%をインメモリーで使用できます。
「SGA の最大 50%」と読むと、あとは SGA のサイズさえ分かれば見積もれそうに思えます。インメモリー領域のサイズは DBMS_INMEMORY_ADMIN.SET_SGA_PERCENTAGE で指定し、指定できるのは 0〜70 の範囲です。この上限である 70 を指定したときに実際いくら割り当てられるのかを 16 ECPU と 32 ECPU で測り、Star Schema Benchmark(SSB)のデータを 3つのデータ量でポピュレートしてみました。
なお本記事では、インメモリー列ストアへデータを読み込むことをポピュレーションと書きます(Oracle の日本語ドキュメントでは「移入」と訳されています)。
1.1. 結論(先出し)
- 上限の 70% を指定して割り当てられたのは
sga_targetの 17.5% だった。割り当てられるのは指定した割合の 1/4 で、ECPU を変えても指定する割合を変えても比率は一定だった - 16 ECPU で保持できる生データは、
FOR CAPACITY LOWで約 36 GiB、FOR QUERY LOWで約 22 GiB - サイズは ECPU と指定した割合だけで計算できる。見積もりが外れる原因は圧縮率のほうにある
- ポピュレートしきれなくてもエラーにはならず、実行計画は
TABLE ACCESS INMEMORY FULLのままになる
本記事はサイズと、割り当て・ポピュレーションの挙動に絞ります。性能の測定結果は扱いません。
1.2. 検証ゴール
| # | 確かめること | 確認できた状態 |
|---|---|---|
| 1 | 指定した割合と、実際に割り当てられるサイズの関係 | ECPU 2点 × 指定する割合 2点で sga_target と割り当てサイズが取れ、比率を式で書ける |
| 2 | 圧縮率がデータ量で変わるか | 同じ表を 2つ以上のデータ量で測り、圧縮率に差が出る |
| 3 | インメモリー領域が不足したときに何が起きるか | 領域を超えるデータをポピュレートしたときの状態を、値で説明できる |
2. 検証環境
2.1. ADB-S のメモリは ECPU で決まる
前提として、ADB-S ではメモリのサイズを直接指定できません。割り当てられるメモリは ECPU 数で決まり、sga_target も ECPU に比例します。インメモリー領域はその SGA の一部を切り出したものなので、サイズの話は必ず ECPU が起点になります。
そのため本記事では、ECPU 数と SET_SGA_PERCENTAGE に指定する割合の 2つを動かして測っています。ECPU あたりの実測値は 4.1 章で示します。
なお In-Memory を使うには 16 ECPU 以上が必要です1。16 ECPU 未満では機能自体が使えません。
2.2. 構成
| 項目 | 内容 |
|---|---|
| DB | Autonomous Database Serverless(ADB-S)、Oracle AI Database 26ai Enterprise Edition Release 23.26.3.1.0 |
| ECPU | 16 / 32(Compute Auto Scaling は無効化) |
| データ | Star Schema Benchmark の非正規化フラット表 lineorder_flat(SF1 / SF10 / SF20。SF はデータ量の倍率で、SF1 が約 600 万行) |
2.3. 確認するビュー
インメモリー領域の状態は GV$INMEMORY_AREA と GV$IM_SEGMENTS で見ます。Oracle RAC ではノードごとに独自の IM 列ストアを持ち、ポピュレートされたオブジェクトはデフォルトでクラスタ内のすべての IM 列ストアに分散されるので2、INST_ID 別に見ないと全体が分かりません。
今回の環境は GV$INSTANCE が 1行(INST_ID = 2)で、GV$INMEMORY_AREA も INST_ID = 2 の行しか返しませんでした。以降の値はすべて 1インスタンス分です。GV$INMEMORY_AREA が複数の INST_ID を返す構成では、インスタンスごとに見る必要があります。
なお ADB-S では GV$SGA / GV$SGAINFO がどちらも 0件を返します(V$ 版も同じく 0件でした)。SGA の内訳から割り当てサイズを逆算する経路は使えません。
3. 有効化から割り当てまで
3.1. インメモリー領域は事前に割り当てられない
有効化したはずなのに ALLOC_BYTES が 0 のまま、という状態から始まります。これは異常ではなく、仕様として書かれている初期状態です1。
インメモリー領域のメモリーは事前に予約されておらず、インメモリー領域の初期サイズは0です。インメモリー領域は、インメモリー移入に十分な領域がないたびに徐々に増加します。
起動直後の GV$INMEMORY_AREA は次の値を返します。POPULATE_STATUS の OUT OF MEMORY も「まだ領域が無い」という初期状態の表示です。
| INST_ID | POOL | ALLOC_BYTES | USED_BYTES | POPULATE_STATUS |
|---|---|---|---|---|
| 2 | 1MB POOL | 0 | 0 | OUT OF MEMORY |
| 2 | 64KB POOL | 0 | 0 | OUT OF MEMORY |
1MB POOL / 64KB POOL は POOL 列が返す名前をそのまま載せています。1MB プールの割り当ては実測値がすべて 1,048,576 B の倍数だったので、この "MB" は 2 進(1 MiB)の意味です。
この状態でも V$OPTION の In-Memory Column Store は TRUE を返します(V$OPTION は機能が使えるかどうかを返すビューで、インスタンス別の状態ではないため V$ で見ています)。ALTER TABLE ... INMEMORY と DBMS_INMEMORY.POPULATE を実行すると、inmemory_size パラメータだけが 0 から 0 以外の値に変わります。この値は再起動すると 0 に戻ります。OCI 側の in-memory-area-in-gbs も ADB-S では常に null です。
割り当てが行われたかどうかを判定できるのは GV$INMEMORY_AREA.ALLOC_BYTES だけです。
3.2. 確実に使うなら再起動を入れる
公式はインメモリー領域が拡張される方法を 2つ挙げています1。PRIORITY 表句を使うか、連続した表スキャンを使うか、です。PRIORITY CRITICAL については「オブジェクトは、データベースがオープンされた直後に移入されます」と書かれています。ここに DBMS_INMEMORY.POPULATE は出てきません。
そこで POPULATE を一度も呼ばず、公式に書かれている方法だけで測りました。
| 試したこと | 1MB プールの ALLOC_BYTES
|
|---|---|
SET_SGA_PERCENTAGE(70) を実行する |
0 のまま(inmemory_size も 0) |
表に INMEMORY MEMCOMPRESS FOR QUERY LOW PRIORITY CRITICAL を付ける |
0 のまま |
| その状態で ADB を stop / start する | 4.785 GiB |
再起動後の最初の確認時点で inmemory_size は 5.469 GiB(sga_target の 17.5%)まで確定し、対象表も POPULATE_STATUS = COMPLETED / BYTES_NOT_POPULATED = 0 まで進んでいました。PRIORITY は再起動をまたいで CRITICAL のまま保持されます。
いっぽう再起動を挟まずに POPULATE だけで待った場合は、割り当てが即時に行われるタイミングと即時に行われないタイミングがありました。ALTER TABLE も POPULATE も成功して返るので、実行結果からは区別が付きません。
確実に使いたいなら、PRIORITY CRITICAL を付けたうえで再起動を 1回入れるのが良いと考えてます。
3.3. 手順
DBMS_INMEMORY_ADMIN は権限を持つ ADMIN ユーザーで実行します。
-- 1. インメモリー領域に使う割合を指定する(0〜70。範囲外は ORA-20000)
exec dbms_inmemory_admin.set_sga_percentage(70);
-- 2. 表をポピュレーションの対象にする
alter table admin.lineorder_flat
inmemory memcompress for capacity low priority critical;
# 3. ADB を停止して起動する。オープン直後に割り当てが行われる
oci db autonomous-database stop --autonomous-database-id <ADB_OCID> --wait-for-state STOPPED
oci db autonomous-database start --autonomous-database-id <ADB_OCID> --wait-for-state AVAILABLE
-- 4. 割り当てサイズとポピュレーションの状況を確認する
select inst_id, pool, alloc_bytes, used_bytes, populate_status from gv$inmemory_area;
select segment_name, bytes, inmemory_size, bytes_not_populated, populate_status
from gv$im_segments where segment_name = 'LINEORDER_FLAT';
指定した設定は再起動をまたいで保持されるので、再起動は 1回で済みます。追加で表をポピュレートする場合は DBMS_INMEMORY.POPULATE を呼びますが、1回目の呼び出しでは GV$IM_SEGMENTS に行が出ません(2回目で STARTED に進みます)。FOR QUERY LOW と FOR CAPACITY LOW の両方で同じ挙動になったので、2回呼ぶ前提にしています。
順序の注意点がもう 1つあります。一度確定した割り当てサイズは、後からより大きい割合を指定しても増えません。再接続しても変わらず、増やすにはもう一度 stop / start が要ります。割合を変えるときは、変えてから再起動する順序にしてください。
4. 計測結果
4.1. 指定した割合と、実際に割り当てられるサイズ
ECPU 2点 × 指定する割合 2点で測った結果です。32 ECPU の 2点と 16 ECPU / 40% は、割り当てを行わせるための小さな表(100万行、136 MiB)を同じものにして、ECPU と割合だけを変えています。16 ECPU / 70% は別セッションで同じ形の表を使って取った値です。
| ECPU | 指定した割合 | sga_target |
inmemory_size |
1MB プール | 64KB プール |
sga_target 比 |
|---|---|---|---|---|---|---|
| 16 | 70% | 31.25 GiB | 5.469 GiB | 4.785 GiB | 0.746 GiB | 17.5% |
| 32 | 70% | 62.5 GiB | 10.938 GiB | 9.570 GiB | 1.491 GiB | 17.5% |
| 32 | 40% | 62.5 GiB | 6.25 GiB | 5.469 GiB | 0.852 GiB | 10.0% |
| 16 | 40% | 31.25 GiB | 3.125 GiB | 2.734 GiB | 0.426 GiB | 10.0% |
両方の軸で線形でした。2.1 章で書いた「メモリは ECPU で決まる」が、そのまま数字に出ています。バイト単位で見ると値はきれいな MiB 単位に揃います。
sga_target = 2,000 MiB × ECPU (16 ECPU なら 32,000 MiB = 31.25 GiB)
inmemory_size = sga_target × 指定した割合 ÷ 4 (70% 指定 → sga_target の 17.5%)
1MB プール = 70% 指定時で 4,900 MiB / 16 ECPU
32 ECPU / 40% の各値をちょうど 1/2 にすると 16 ECPU / 40% の値と一致します。指定できる上限が 70% なので、本記事の検証環境(26ai 23.26.3.1.0、16 / 32 ECPU)で実際に割り当てられたのは sga_target の 17.5% まででした。公式が書いている「SGA の最大 50%」との差はここです。
ALLOC_BYTES は 1MB プールと 64KB プールの合計で返りますが、表のデータが入るのは 1MB プールです。このあとの見積もりはすべて 1MB プールの値で計算します。
4.2. 圧縮率はデータ量で変わる
同じ lineorder_flat を 3つのデータ量でポピュレートし、GV$IM_SEGMENTS の BYTES(ディスク上のサイズ)と INMEMORY_SIZE(メモリ上のサイズ)の比を取りました。いずれも 16 ECPU / 70% 指定のとき(1MB プール 4.785 GiB)の測定です。
| データ量 | 行数 | BYTES |
FOR QUERY LOW の圧縮率 |
FOR CAPACITY LOW の圧縮率 |
|---|---|---|---|---|
| SF1 | 6,001,171 | 2.369 GiB | 5.59倍 | 8.92倍 |
| SF10 | 59,986,214 | 23.757 GiB | 4.59倍(参考値) | 7.61倍 |
| SF20 | 119,994,746 | 47.609 GiB | 未実施 | 7.17倍(参考値) |
参考値と書いた 2つは、1MB プールが不足して一部しか入らなかったものです。入った分(BYTES − BYTES_NOT_POPULATED)を INMEMORY_SIZE で割って出しています。
FOR CAPACITY LOW のほうが圧縮率が高いのはドキュメントどおりです。FOR QUERY LOW は問合せ性能が最適になるレベル、FOR CAPACITY LOW は領域節約寄りにバランスを取ったレベル、と定義されています3。
データ量による差のほうが今回の主題です。FOR CAPACITY LOW で 8.92倍 → 7.61倍 → 7.17倍、FOR QUERY LOW で 5.59倍 → 4.59倍と、データ量を増やすにつれて比率が下がりました。SF1 で測った比率をそのまま SF10 に当てはめると、圧縮率を 2割ほど高く見積もることになります。
4.3. インメモリー領域が不足するとどうなるか
1MB プール(4.785 GiB)に対して大きすぎる SF20(BYTES 47.609 GiB)を FOR CAPACITY LOW でポピュレートしました。エラーにはならず、入った分だけが使われます。
INMEMORY_SIZE 5,178,261,504 (4.822 GiB)
BYTES_NOT_POPULATED 13,977,452,544 (13.017 GiB。全体の 27.34%)
POPULATE_STATUS OUT OF MEMORY (72.66% が入った状態)
POPULATE_STATUS = OUT OF MEMORY は「IM 列ストアの領域不足でポピュレーションのタスクが失敗した」という意味です4。値が変化しなくなるまでポーリングすると、この状態のまま安定しました。アクセス頻度に応じた入れ替えは起きず、残りはストレージから読まれます。
問題は、この状態が実行計画から分からないことです。72.66% しか入っていなくても、In-Memory 経路を使う側は TABLE ACCESS INMEMORY FULL のままでした(inmemory_query = DISABLE にすると TABLE ACCESS STORAGE FULL に変わります)。全部入ったかどうかは GV$IM_SEGMENTS.BYTES_NOT_POPULATED を見るしかありません5。
ALLOC_BYTES も、大きい表をポピュレートしている最中はサイズの根拠になりません。SF20 のポピュレーション中に 1MB プールの ALLOC_BYTES は 19.141 GiB まで増えましたが、USED_BYTES は 4.813 GiB で止まり、そこで OUT OF MEMORY になりました。実際に使えたのは、小さい表で割り当てを行わせたときの値(4.785 GiB)とほぼ同じです。
5. 考察
5.1. どれだけのデータを保持できるのか
4.1 章の式と 4.2 章の圧縮率をつなぐと、保持できる生データの量が出ます。
70% を指定したときの 1MB プールは、ECPU あたり約 0.30 GiB でした(4.785 GiB ÷ 16 ECPU)。ここに圧縮率を掛けた値が、保持できるディスク上のデータ量になります。
| 圧縮レベル | ECPU あたり | 16 ECPU | 32 ECPU |
|---|---|---|---|
FOR CAPACITY LOW(圧縮率 7.61倍で計算) |
約 2.28 GiB | 約 36 GiB | 約 73 GiB |
FOR QUERY LOW(圧縮率 4.59倍で計算) |
約 1.37 GiB | 約 22 GiB | 約 44 GiB |
この数字は、ポピュレートする対象を選ぶときの基準として使えます。1,024 GiB(1 TiB)のデータを全部入れようとすると FOR CAPACITY LOW でも約 450 ECPU が要る計算になります。In-Memory は表・列・表領域の単位で指定できるので3、対象を選ぶ前提の機能だと考えるほうが設計に合います。
5.2. サイズは計算できる。外れるのは圧縮率
割り当てサイズは 4点の実測で線形に決まりました。外れるのは圧縮率のほうです。実際に 2回とも見積もりを外しています。
| 見積もり | 使った圧縮率 | 予測 | 実際 |
|---|---|---|---|
SF10 を FOR QUERY LOW で |
SF1 の 5.59倍 | 4.25 GiB 必要 → 4.79 GiB に収まる | 収まらず、93.0% で OUT OF MEMORY
|
SF20 を FOR CAPACITY LOW で |
SF10 の 7.61倍 | 76.5% が入る | 72.66% が入った |
SF10 の FOR QUERY LOW は、SF1 で測った 5.59倍で計算すると足りるはずでした。実際の圧縮率は 4.59倍だったため、必要量が 5.18 GiB となって 4.79 GiB を超えました。ここで FOR CAPACITY LOW に切り替えると、必要量が 3.12 GiB まで下がって足りました。SF20 は、1つ下のデータ量で測った比率を使ったぶん外れ方が小さく、予測 76.5% に対して実測 72.66% でした。
ここで「データが大きいほど圧縮が悪くなる」と読むのは誤りです。圧縮率が決まるのは MEMCOMPRESS のレベル3と、データ自体の列の性質(値の種類の多さ、繰り返しと偏り、データ型と幅、行の並び順)で、行数そのものではありません。
今回の lineorder_flat は非正規化した表なので、SF を上げても、元になる表ごとに行数の増え方が違います。明細行と customer(3万 → 30万 → 60万行)はデータ量に比例して増えますが、part(20万 → 80万 → 100万行)は比例せず、dates は 2,556行で固定です。データ量を変えたときに圧縮率が下がったのはこの違いによると考えられますが、列単位の圧縮率は測っていないので、どの列で下がったのかまでは言えません。
言えるのは次の 1点です。圧縮率は自分のデータの列の性質で決まるので、対象のデータ・対象のデータ量で実測するしかありません。別のデータなら、データ量を増やすと比率が上がることもありえます。
6. まとめ
ADB-S の In-Memory は、割り当てサイズは計算できますが、圧縮率のほうは実測しないと決まりませんでした。サイジングの手順としては次の 3ステップになります。
- ポピュレートする対象を先に選ぶ。16 ECPU で保持できる生データは
FOR CAPACITY LOWでも約 36 GiB なので、全件を入れる前提にしない - 対象のデータ・対象のデータ量で圧縮率を実測する。小さいデータ量で測った比率を大きいほうに当てはめると外れる(今回は 2回とも外した)
- 1MB プールと突き合わせる。約 0.30 GiB × ECPU(70% 指定時)に対し、実測した圧縮率で割った必要量が足りるかを確認する
手順として気をつけるのは、領域の割り当てと表のポピュレーションが別ものだという点です。割り当てはデータベースのオープンで行われるので、SET_SGA_PERCENTAGE と PRIORITY CRITICAL を設定してから ADB を stop / start し、ALLOC_BYTES が 0 でないことを確認してから測ります。全部入ったかどうかは実行計画では分からないので、BYTES_NOT_POPULATED を見ます。
-
自律型AIデータベースによるDatabase In-Memoryの使用(Oracle Autonomous AI Database Serverless ドキュメント、「Database In-Memoryについて」「インメモリー移入に対するオブジェクトの有効化」「インメモリー列ストアのサイズの設定」) ↩ ↩2 ↩3 ↩4
-
Database In-Memoryガイド 第12章「Oracle RACでのIM列ストアのデプロイ」(「すべてのOracle RACノードには、独自のインメモリー(IM)列ストアがあります」「すべてのOracle RACノードでIM列ストアを同じサイズにすることをお薦めします」) ↩
-
Database In-Memoryガイド「インメモリー・オブジェクトの圧縮レベル」(表5-2 IM列ストアの圧縮レベル) ↩ ↩2 ↩3
-
POPULATE_STATUSのOUT OF MEMORYは 23ai のリファレンスに記載があります(V$IM_SEGMENTS、"A populate task for the segment failed due to lack of space in the IM column store")。上記の日本語版(21c 相当)の同ページには、この値の記載が見当たりませんでした ↩