web-dev-qa-db-ja.com

非常に大きなテーブルのインデックスを再構築しながら、小さなトランザクションログファイルを保持する方法

このインデックスの再構築を成功させるために必要なすべての支援が必要です。特に、トランザクションログ管理に関する専門家のアドバイス。

背景

ターゲットデータベースは、クラスター化列ストアインデックス(CCIX)で3つの大きなテーブルをホストするDWデータベースです。アナログと呼ばれる最大のテーブル。約370億行と約600 GBの高圧縮データを保持します。小さいテーブルを使用した最初の見積もりに基づいて、150 GBのCCIXテーブルは5 TBに拡張される可能性があるため、この再構築のために追加の29 TB=をプロビジョニングしました。

  • SQL Server Enterprise 2016
  • 回復モード=シンプル

なぜこれをしなければならないのですか?

ETLサービスの最初のバージョンでは、データは統合されず、CCIXにロードされる前にソートされません。その結果、多くの行セグメントが圧縮される前に完全に埋められないため、CCIXの使用が最適ではなくなります。最適なセグメントは1,0485,76行を圧縮し、時間でソートする必要があります。これにより、時間ベースのクエリは、SQLサーバーで最も多くの行グループを排除し、処理するセグメントを減らすことができます。利用できるドキュメントが増えれば増えるほど、CCIXについて学ぶようになります。

私の配置の抜粋はこちら https://drive.google.com/file/d/0BzGLNskaj70UQUtZYW9CZF9iUUk/view?usp=sharing

新しいETLは、データがCCIXにロードされる前に、時間内に統合およびソートされることを保証します。したがって、将来のロードはソートされます。ここでは、最初のロード中にロードされた既存のデータに関心があります。

ケースの詳細については、こちらをご覧ください。 https://drive.google.com/open?id=0BzGLNskaj70URmZURlVDWVNYd2M

これは2回目の試みであり、新しいスクリプトにパーティションベースのインデックスの再構築があるにもかかわらず、ログファイルがまだ蓄積されていることがわかります。これを抑える必要があります。

私の質問は次のとおりです:

  1. 2回目の試行(進行中)を既に実行しましたが、どのパーティションが既に処理およびソートされているかを確認することはできますか?再構築スクリプトにOFFLINE = ONがあります。

  2. パーティションベースのインデックスの再構築の進行中にトランザクションログのサイズをどのように管理できますか?これを確認し、可能であれば定期的にすべてのパーティションを切り捨てる必要があります。

  3. ログファイルの領域が不足しているため、インデックスの再構築は最初に失敗しました。新しいログファイルを追加し、別のディスクを使用しました。しかし、これがこの2回目の実行で再び起こらないようにするにはどうすればよいでしょうか。パーティションベースの再構築は、正常に再構築できることを保証するのに十分でしょうか?

あなたの助けに感謝。

パーティションごとにスクリプトを再構築(2回目の試行)–進行中

経過:17時間34分

--create a new clustered row index (CRI)
CREATE CLUSTERED INDEX [Analog_ColumnStoreIndex] 
ON [dbo].[Analog]
(
    [LogTime] ASC,
    [CTDIID] ASC,
    [WindFarmId] ASC,
    [StationId] ASC
) 
WITH (
    DROP_EXISTING = ON,
    PAD_INDEX = OFF, 
    STATISTICS_NORECOMPUTE = OFF, 
    SORT_IN_TEMPDB = OFF,
    ALLOW_ROW_LOCKS = ON, 
    ALLOW_PAGE_LOCKS = ON,
    ONLINE = OFF
) 
ON [AnalogMonthlyPScheme]([LogTime])
GO

enter image description here

5
rdagumampan

このパーティションは一度に1つずつ処理します。高レベルで:

  1. パーティション化されたクラスター化行ストアインデックスを含む新しい空のターゲットテーブルを作成する
  2. パーティション化されていないクラスター化列ストアインデックスを使用して作業テーブルを作成する
  3. SWITCHパーティション1(ソーステーブルから作業テーブルへ)
  4. DROP_EXISTINGを使用して、作業テーブルを行ストアクラスタ化インデックスに変換します
  5. CHECK制約を、パーティション1の値の範囲をカバーする作業テーブルに追加します。
  6. SWITCHターゲットテーブルのパーティション1に作業テーブル
  7. 空の作業テーブルをドロップします
  8. 次のパーティションについて、手順2から繰り返します
  9. すべてのパーティションが処理されたら、元のテーブルを削除し、新しい名前を変更します

これにより、最小限のログに記録された操作を最大限に活用し、ワークスペースを最小限に抑え、必要に応じてパーティション間でトランザクションログをクリアできます。

デモ

パーティショニング

CREATE PARTITION FUNCTION [AnalogMonthlyP] (datetime2(0))
AS RANGE RIGHT FOR VALUES(
N'2014-09-01T00:00:00.000', N'2014-10-01T00:00:00.000', N'2014-11-01T00:00:00.000',
N'2014-12-01T00:00:00.000', N'2015-01-01T00:00:00.000', N'2015-02-01T00:00:00.000',
N'2015-03-01T00:00:00.000', N'2015-04-01T00:00:00.000', N'2015-05-01T00:00:00.000',
N'2015-06-01T00:00:00.000', N'2015-07-01T00:00:00.000', N'2015-08-01T00:00:00.000',
N'2015-09-01T00:00:00.000', N'2015-10-01T00:00:00.000', N'2015-11-01T00:00:00.000',
N'2015-12-01T00:00:00.000', N'2016-01-01T00:00:00.000', N'2016-02-01T00:00:00.000',
N'2016-03-01T00:00:00.000', N'2016-04-01T00:00:00.000', N'2016-05-01T00:00:00.000',
N'2016-06-01T00:00:00.000', N'2016-07-01T00:00:00.000', N'2016-08-01T00:00:00.000',
N'2016-09-01T00:00:00.000', N'2016-10-01T00:00:00.000', N'2016-11-01T00:00:00.000',
N'2016-12-01T00:00:00.000', N'2017-01-01T00:00:00.000', N'2017-02-01T00:00:00.000',
N'2017-03-01T00:00:00.000', N'2017-04-01T00:00:00.000', N'2017-05-01T00:00:00.000',
N'2017-06-01T00:00:00.000', N'2017-07-01T00:00:00.000', N'2017-08-01T00:00:00.000',
N'2017-09-01T00:00:00.000', N'2017-10-01T00:00:00.000', N'2017-11-01T00:00:00.000',
N'2017-12-01T00:00:00.000', N'2018-01-01T00:00:00.000', N'2018-02-01T00:00:00.000',
N'2018-03-01T00:00:00.000', N'2018-04-01T00:00:00.000', N'2018-05-01T00:00:00.000',
N'2018-06-01T00:00:00.000', N'2018-07-01T00:00:00.000', N'2018-08-01T00:00:00.000',
N'2018-09-01T00:00:00.000', N'2018-10-01T00:00:00.000', N'2018-11-01T00:00:00.000',
N'2018-12-01T00:00:00.000');

CREATE PARTITION SCHEME [AnalogMonthlyPScheme]
AS PARTITION [AnalogMonthlyP]
ALL TO ([PRIMARY]);

数表の設定(必要な場合)

WITH Ten(N) AS 
(
    SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL 
    SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL 
    SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
)   
SELECT TOP (10000000) 
    n = IDENTITY(int, 1, 1)
INTO   dbo.Numbers
FROM   Ten T10,
       Ten T100,
       Ten T1000,
       Ten T10000,
       Ten T100000,
       Ten T1000000,
       Ten T10000000;

ALTER TABLE dbo.Numbers
ADD CONSTRAINT PK_dbo_Numbers_n
PRIMARY KEY CLUSTERED (n)
WITH (SORT_IN_TEMPDB = ON, MAXDOP = 1, FILLFACTOR = 100);

ソーステーブルとデータ

-- Source table currently partitioned clustered columnstore
CREATE TABLE [dbo].[Analog]
(
    LogTime datetime2(0) NOT NULL,
    CTDID integer NOT NULL,
    WindFarmId integer NOT NULL,
    StationId integer NOT NULL,

    INDEX [Analog_ColumnStoreIndex]
        CLUSTERED COLUMNSTORE
)
ON [AnalogMonthlyPScheme](LogTime);

-- Some test data
INSERT dbo.Analog WITH (TABLOCKX)
    (LogTime, CTDID, WindFarmId, StationId)
SELECT
    DATEADD(SECOND, N.n, '20140801'),
    CHECKSUM(NEWID()),
    CHECKSUM(NEWID()),
    CHECKSUM(NEWID())
FROM dbo.Numbers AS N -- ten million rows
OPTION (MAXDOP 1);

対象テーブルと作業テーブル

-- The final table we want - partitioned rowstore clustered index
CREATE TABLE [dbo].[AnalogNew]
(
    LogTime datetime2(0) NOT NULL,
    CTDID integer NOT NULL,
    WindFarmId integer NOT NULL,
    StationId integer NOT NULL,

    INDEX CCIX_Analog
        CLUSTERED (LogTime, CTDID, WindFarmId, StationId)
)
ON [AnalogMonthlyPScheme](LogTime);

-- Working table
CREATE TABLE dbo.AnalogSwitch
(
    LogTime datetime2(0) NOT NULL,
    CTDID integer NOT NULL,
    WindFarmId integer NOT NULL,
    StationId integer NOT NULL,

    INDEX CCI CLUSTERED COLUMNSTORE
);

パーティション1

-- Process the first partition
ALTER TABLE dbo.Analog 
SWITCH PARTITION 1 
TO dbo.AnalogSwitch;

-- Optional, may help parallel plan
CREATE STATISTICS s ON dbo.AnalogSwitch (LogTime);

-- Convert to rowstore clustered index
CREATE CLUSTERED INDEX CCI
ON dbo.AnalogSwitch (LogTime, CTDID, WindFarmId, StationId)
WITH (DROP_EXISTING = ON);

-- Specify data range
ALTER TABLE dbo.AnalogSwitch
    ADD CONSTRAINT CK 
    CHECK (LogTime >= '20140801' AND LogTime < '20140901');

-- Move to target table
ALTER TABLE dbo.AnalogSwitch 
SWITCH TO dbo.AnalogNew 
PARTITION 1;

-- Recreate switch table
DROP TABLE dbo.AnalogSwitch;

CREATE TABLE dbo.AnalogSwitch
(
    LogTime datetime2(0) NOT NULL,
    CTDID integer NOT NULL,
    WindFarmId integer NOT NULL,
    StationId integer NOT NULL,

    INDEX CCI CLUSTERED COLUMNSTORE
);

パーティション2

-- Process the second partition
ALTER TABLE dbo.Analog 
SWITCH PARTITION 2
TO dbo.AnalogSwitch;

-- Optional, may help parallel plan
CREATE STATISTICS s ON dbo.AnalogSwitch (LogTime);

-- Convert to rowstore clustered index
CREATE CLUSTERED INDEX CCI
ON dbo.AnalogSwitch (LogTime, CTDID, WindFarmId, StationId)
WITH (DROP_EXISTING = ON);

-- Specify data range
ALTER TABLE dbo.AnalogSwitch
    ADD CONSTRAINT CK 
    CHECK (LogTime >= '20140901' AND LogTime < '20141001');

-- Move to target table
ALTER TABLE dbo.AnalogSwitch 
SWITCH TO dbo.AnalogNew 
PARTITION 2;

-- Recreate switch table
DROP TABLE dbo.AnalogSwitch;

CREATE TABLE dbo.AnalogSwitch
(
    LogTime datetime2(0) NOT NULL,
    CTDID integer NOT NULL,
    WindFarmId integer NOT NULL,
    StationId integer NOT NULL,

    INDEX CCI CLUSTERED COLUMNSTORE
);

...等々。これはスクリプトを書くのはかなり簡単です。

1
Paul White 9

私があなたに与えることができる最も重要なアドバイスは、最初に小さいテーブルに対してテストすることです。 370億行のテーブルを変換するだけではありません。小さなテーブルに対してメソッドを調整し、それを使用して大きなテーブルの変換に関する見積もりを取得できます。また、ミスからの回復ははるかに低コストです(24時間のロールバック操作は不要)。また、小さいテーブルを使用してクエリのパフォーマンスをテストし、行っている変換が努力に値することを確認することもできます。

2番目に重要なアドバイスは、誰かにストレージ構成をチェックしてもらうことです。最近大量のストレージを追加した場合は、スループットを最大化するための適切な方法で行われていない可能性があります。

正直なところ、トランザクションログのアクティビティがこれほど多く発生している理由ははっきりしません。シンプルな復旧モデルを使用しているため、CCIを行ストアに変換することは、最小限のログ記録操作でなければなりません。

SQL Serverが次のステートメントを実装する方法について知っていることを説明します。

CREATE CLUSTERED INDEX [Analog_ColumnStoreIndex] 
ON [dbo].[Analog]
(
    [LogTime] ASC,
    [CTDIID] ASC,
    [WindFarmId] ASC,
    [StationId] ASC
) 
WITH (
    DROP_EXISTING = ON,
    PAD_INDEX = OFF, 
    STATISTICS_NORECOMPUTE = OFF, 
    SORT_IN_TEMPDB = OFF,
    ALLOW_ROW_LOCKS = ON, 
    ALLOW_PAGE_LOCKS = ON,
    ONLINE = OFF
) 
ON [AnalogMonthlyPScheme]([LogTime]);

行を書き込む前に、SQL Serverはテーブルのデータ全体をLogTimeCTDIIDWindFarmId、およびStationIdでソートする必要があります。ソートのサイズは巨大になります。圧縮されていないクラスター化された行ストアテーブルとほぼ同じサイズになると思います。おそらくtempdbからディスクに溢れます。おそらく、操作のその部分は最小限に記録されていませんか?他になぜログ活動がそんなに多く見られるのか、私には本当にわからない。 LogTimeでの並べ替えは変換には十分であり、並べ替えが安くなる可能性がありますが、並べ替えに必要なワークスペースが減るとは思わないことに注意してください。

並べ替えとともに、データは新しいテーブルに書き込まれます。新しいテーブルが完了すると、古いCCIテーブルは削除されます。最大のテーブルの場合、これは20 TBになると見積もります。 20 TB=のデータを書き込むには時間がかかる場合があります。

巨大な並べ替えや巨大なトランザクションを避けてデータを変換したい場合、それは可能ですが、面倒です。これに入る前に、CCIがすでにパーティション化されている場合、正しいことは パーティションスイッチング を使用して行グループをより適切に編成することであることを指摘する必要があります。あなたの質問に基づいて、テーブルはまだ分割されていないようですが、私は確認したいと思います。また、以下のテストも行っていません。

パーティション化された行ストアヒープ または パーティション化されたビュー を作成します。これらのパーティション構成は、将来のCCIパーティション構成と一致する必要があります。 TABLOCKヒントを使用して、CCIからTOP N行をパーティションヒープに削除します。これにより、挿入に関する最小限のログが得られます。 CCIに対する削除の最小限のログは取得されません。ただし、CCIに対する削除はビットマップの値を変更するだけなので、標準の削除よりもはるかに少ないログで済みます。

上記の挿入には、ヒープのソートが必要であり、パーティション化されたビューのソートもおそらく必要です。ただし、Nの値を制御するので、メモリ+ tempdb内に収まるようにソートのサイズを制限できます。

ループを実行し続けると、CCIからデータを読み取るためにクエリに時間がかかることに注意してください。これは、削除された行をスキャンする必要があるためです。定期的にCCIに対してREORGANIZEを発行して 削除された行をクリーンアップ する必要があります。

ループが終了すると、すべてのデータが正しいパーティションにありますが、並べ替えられていません。この時点で、パーティションまたはテーブルごとに、スイッチをヒープにパーティション化し、テーブルに行ストアのクラスター化インデックスを構築し、DROP_EXISTING = ONおよびMAXDOP 1を使用してCCIを構築し、最後にスイッチを新しいCCIテーブルにパーティション化できます。 。すべてのパーティション切り替えが完了したら、LogTimeによる行グループの除去が優れた、パーティション化されたCCIが得られます。

パーティション化されたCCIへの新しいデータのロードに関する2つの注意事項。完全に順序付けされた完全な行グループが必要な場合は、並列挿入を使用できません。これは、ソースデータの並列読み取りによってスレッドの行が不均等に分散され、各スレッドが異なる行グループに読み込まれるためです。また、パーティションを慎重に計画して、パーティションをデータで分割する必要がないようにします。パーティションを直接分割することはできません CCIを削除せずに 明らかにしたくない。

2
Joe Obbish