わかりやすいSQL Serverの内部構造 メモリ管理解説(第1章)

ちょっと、古い内容になりますが、SQL Serverはより良い性能を得るために、様々な目的でメモリを使用します。ディスクI/Oを減らすためや結果セットの並べ替え、コンパイル済みのクエリプランを再利用するための格納場所であったりと、そうしたメモリの使用と管理について記載しています。

SQLServerと仮想アドレス空間

Windowsオペレーションシステムの管理下で動作するプロセス(アプリケーション)は、それぞれが「仮想アドレス空間」を保持しています。ほとんどの場合はサイズ的な制約であるため、全プロセスの仮想アドレス空間をそのまま物理メモリ上に展開することは難しいことです。そのため、Windowsオペレーションシステムが各プロセスの仮想あそレス空間を管理して、必要に応じて物理メモリに展開したり、あるいはページファイルに書き込んだりします。実際にアプリケーションが使用できるメモリは、実装メモリの半分の程度です。(半分はカーネルが専有)

SQLServerと仮想アドレス空間の管理

仮想アドレス空間の状態

プロセスに割り当てられた仮想アドレス空間のすべての領域は、「Committed」、「Reserved」、「Free」のいずれかになります。

Committed

仮想アドレス空間内で実際に使用されている領域です。この領域に対してはWindowsオペレーションシステムが物理メモリの領域を割り当てます。(図1)

仮想アドレス空間の「Committed」状態にある領域は物理メモリに確保されている

図1

図2

Reserved

後の使用に備えて、仮想アドレス空間の領域が予約済みとなっている状態です。この状態では物理メモリは一切割り当てられていません。SQLServerのメモリサイズとして設定した値よりも、タスクマネージャーなどで確認した際にSQLServerのメモリ使用量が少なく見えることがあります。これは、SQLServerが起動時に必要最小限の領域だけをCommittedにして、残りの部分をReservedにしているからです。メモリ割り当ての動作で、Committedの状態にするには、より多くの時間を消費します。そのため、まず必要なサイズのみをCommittedに設定し、残りの部分は必要になったときに適宜ReservedからCommittedに変更するという方法がとられます。(図3)

SQL Server起動時には必要最小限のサイズのみが「Committed」にされる

図3

図3

Free

Freeな状態にある領域は、文字通り自由な割り当てが可能です。プロセスは、自分自身に必要な用途でこの領域を使用できます。ただし原則としてSQLServerはコンピュータに搭載されているサイズより大きなサイズの仮想アドレス空間を使用しません。そのため、2GB使用可能であっても物理メモリのサイズによっては、実際は多くの領域がFreeのまま残されることもあります。(図4)

SQLServerでは物理メモリ以上のサイズを「Reserved」もしくは「Committed」にしない。残りの領域は「Free」となる

図4

VirtualAlloc関数

SQLServerが仮想アドレス空間内の領域を操作する際には、Win32APIであるVirtualAlloc関数を使用します。VirtualAlloc関数をMEM_RESERVEフラグとともに使用すると、指定された仮想アドレス空間内の領域はReservedの状態になります。MEM_COMMITフラグとともに使用した場合には、指定された領域の状態はCommittedになります。

また、MEM_RESERVEフラグを指定していた場合には、確保する領域のサイズは64KBの倍数のみが許可されます。一方、MEM_COMMITフラグとともに指定できる領域のサイズは4KBのみをCommittedにして使用した場合、残りの60KBの領域は確保されたまま使用されません。これは仮想アドレス空間のフラグメント(断片化)を発生させ、メモリの効率的な使用の妨げとなるため、アプリケーションを作成する際には注意が必要です。

ページング

仮想アドレス空間と物理メモリのサイズの間にあるギャップを埋めるために、Windowsオペレーションシステムでは「ページング方式」と呼ばれる方法を使用します。ページング方式では、物理メモリが不足している場合、オペレーションシステムによってその時点で不要と判断されたメモリ上のデータが、ページ単位でハードディスク上のページファイルに書き込まれます。ページファイルに書き込まれたページは、別のデータを物理メモリ上に読み込むために使用されます。また、再度必要になった時点で、ページファイルから物理メモリに読み込まれます。

これは仮想記憶領域を実現するために必要な動作ですが、当然パフォーマンスに悪影響を与えます。たとえば、SQLServerはデータを効率的に処理するため、ディスクから読み込んだデータをメモリに保持します。そのメモリがページングの対象となってページファイルに書き込まれてしまうと、せっかくメモリ上に保持していたデータにアクセスしようとしたときに、ページファイルから読み込みが発生することになります。

そのような対処のために、SQLServerが使用するメモリの大部分を物理メモリからページファイルへ書き込むことを抑制させるため、「メモリ内のページのロック(Lock Pages in Memory)」特権をSQLServerの起動アカウントへ追加すればページングの対象となることを避けることができます。しかし、SQLServerが使用するメモリを物理メモリ上に常駐させることによって、別のプロセスのメモリ獲得に悪影響を与える可能性もあるため、慎重に対処する必要があります。

物理メモリサイズとSQLServerのメモリ使用量

SQL Serverのメモリ使用量のデフォルト設定の場合、動的管理されるように設定されています。動的管理の場合、SQLServerが使用するメモリサイズは、おおよそ「物理メモリのサイズ-5MB」まで拡張されます。これはSQL Serverがメモリリリークしているわけではありません。もしも一定量以上のメモリを使用させたくなり場合は、次のコマンドをクエリツールで実行することで制限することが可能です。

exec sp_configure 'max server memory', 1024 -- 最大値をMB単位で指定
go
reconfigure

(sqlcmd、ossql、クエリアナライザ、SQL Server Management Studio等で実行可能)

Address Windowing Extensions(AWE)

X86版のWindowsオペレーションシステムでは、ユーザーアプリケーションが標準で使用できる仮想アドレス空間のサイズは4GB中の2GBです。この制限はSQLServerにも当てはまりますが、中小規模サイトで運用されている場合、仮想アドレス空間サイズは2GBで十分であることがほとんどです。しかしながら、大規模サイトでより大きなサイズのデータベースを運用する場合、良好なパフォーマンスを実現するためには、もっと大きなサイズのメモリの使用が必要です。WindowsオペレーションシステムとSQLServer
をX64版もしくはIA64版へアップグレードできれば、その制限を大きく緩和することができます。必ずしも簡単にアップグレードできると限りません。そのような場合は、X86版SQLServerを使用してもアクセス可能なメモリを増加させることができるのが「Address Windowing Extensions」(AWE)です、なお、AWEが使用できるOSは制限されているので注意が必要です。

NUMA

NUMAはNon-Uniform Memory Archiectureの略称で、共有メモリアーキテクチャの1つの形態です。ほとんどの場合、小規模SMP(対象型マルチプロセッシング)コンピュータで実装されている共有メモリアーキテクチャではコンピュータに搭載されているすべてのCPUが、メモリとメモリアクセスに使用するためのバスを共有しています。(図5)

図5

図5

1つのCPUがメモリアクセスのためにバスを占有すると、ほかのCPUはバスが解放されるまでメモリにアクセスできません。そのため、搭載されているCPUの数が増えるほど、バスの解放待ち時間が長くなる可能性があります。また、CPU数の増加によってバスも物理的に長くなるため、メモリへのアクセス速度が落ちます。その結果としてコンピュータのスケーラビリティが損なわれることも考えられます。その問題への対処として実装されたのがNUMAアーキテクチャです。すべてのCPUがバスとメモリを共有するのでなく、少数のCPUがグループとなり、それぞれのグループが独自のバストメモリを保持します。各グループが保持するメモリは「ローカルメモリ」と呼ばれます。ローカルメモリを使用することによって、まずバスあたりのCPU数が抑制され、バス解放待ち時間が減少します。さらに物理的なバスの長さも抑制できます。(図6)

各CPUグループがローカルメモリにアクセス

図6

図6

また、各CPUグループは、ほかのグループが保持するリモートメモリにもアクセスできます。しかし、ローカルメモリにアクセスする場合と比較すると、アクセス経路の複雑化によってパフォーマンスは大幅に低下します(4倍の時間がかかるとも言われています)そのため、NUMAアーキテクチャを有効に活用するには、アプリケーションも各CPUグループとローカルメモリに配慮する必要があります。

SQLServerがNUMAアーキテクチャへの対応をはじめて実装したのは、SQLServer2000SP4ですが、その内容は非常に限定されたものでした。その機能を有効化するためにはトレースフラグを設定するなどの煩雑な作業でした。一方、SQLServer2005以降の場合は、NUMAアーキテクチャを踏査したハードウェアにインストールされると、自動的に検知し自分自身のNUMA対応機能を有効化します。

NUMA対応ハードウェアにインストールされた場合のSQLServer2005の動作の違いを説明します。

SQLServerは各CPUグループをNUMAノードという管理対象として認識します。メモリを必要とする処理が実行されると、そのスレッドが処理されるスケジューラと関連付けされたCPUのNUMAノードのローカルメモリに必要な領域が確保されます。(図7)

NUMAアーキテクチャのメモリ確保

図7

図7

また、各NUMAノード内で可能な限りメモリ管理を完結させるため、以下のコンポーネントがすべてのNUMAノードに割り当てられます。

レイジーライタスレッド

SQLServerのメモリの管理を行うコンポーネントの1つです。通常はSQLServer内で1プロセスのみ存在します。

I/O完了ポートスレッド

I/O完了ポートスレッドは、SQLOSスケジューラがネットワークI/OやディスクI/Oの状況を適切に判断できるように様々な動作を行っています。例えば、I/Oリクエストリストに追加されているワーカーにI/Oの完了を通知したり。通常ならば、SQLServer内に1つのみ存在していますが、NUMA対応機能が有効化されると、各NUMAノードにI/O完了ポートスレッドが用意されます。

これらの内部コンポーネントが各NUMAノードに割り当てられるとともに、それぞれのNUMAノードに割り当てされたワーカーが可能な限りローカルメモリを使用するようにデザインされています。その結果として、NUMAアーキテクチャを実装したハードウェア上でSQLServerが効率的に動作することが可能成っています。

ソフトNUMA

NUMAアーキテクチャを実装していないハードウェア上でも、その利点を使用するための機能が用意されています。それはハードウェアではなくソフトウェアの機能として実装されているという意味で「ソフトNUMA」と呼ばれています。非NUMA機でも複数のCPUもしくはコアを搭載している場合は、それらを疑似的にNUMAノードし、グループ化して管理できます。当然ながら、非NUMA機では、1つのメモリ、1つのバスをすべてNUMAノードで共有するため、ローカルメモリのメリットを受けることができません。つまり、バス解放待ちの発生や物理的なバスの長さといった問題は存在したままです。しかしながら、各NUMAノードにはレイジーライタスレッドとI/O完了ポートスレッドが用意されています。そのため、それぞれのコンポーネントの改善が得られるもしれません。

次の例では、4個のCPU(もしくはコア)が搭載されたコンピュータで、それぞれのCPUに対してNUMAノードを割り当てています。

クエリツール次のクエリを実行

exec sp_configure 'show advanced'
options',1
reconfigure
go
exec sp_configure 'affinity mask', 1111
go

レジストリエディタ(regedit.exe)を起動して、表のキーを追加これによって、すべてのCPUに対してソフトNUMAノードが割り当てられる

キー種類値の名前値データ
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server\90\NodeConfiguration\Node0DWORDCPUMask0x01
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server\90\NodeConfiguration\Node1DWORDCPUMask0x02
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server\90\NodeConfiguration\Node2DWORDCPUMask0x04
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server\90\NodeConfiguration\Node3DWORDCPUMask0x08