web-dev-qa-db-ja.com

IPアドレス(IPv4およびIPv6)範囲の検索/ルックアップを実行する場合の最良のインデックス戦略またはクエリSELECTは何ですか?

質問: 1つの大きなデータセットを別の大きなデータセットに対して検索するために使用できる、より優れたインデックス作成戦略またはクエリSELECTはありますか?または、ルックアップディメンションテーブルをメモリに配置することを検討する必要がありますか(すべて125 GB)。

サーバー構成:

  • サーバーはVMWare上で実行される仮想サーバーであるため、オペレーティングシステムを再インストールしなくても、追加のハードウェアをバックグラウンドで追加できます。
  • Microsoft SQL Server 2017(RTM)-14.0.1000.169(X64)2017年8月22日17:04:49 Copyright(C)2017 Microsoft Corporation Standard Edition(64-bit)on Windows Server 2016 Standard 10.0(Build 14393:)(Hypervisor)
  • 注:以前は2014 Enterpriseでした-なぜStandardに選ばれたのか尋ねました。
  • 2つのデータベースを実行しているインスタンスは1つだけです:私とDBA
  • 2つのファイルグループ、それぞれ1つのファイル:PRIMARY(システムテーブル:デフォルト以外)およびSECONDARY(非システムテーブル:デフォルト)。 SECONDARYは、CPUが追加されると、より多くのファイルを保持できるようにスケーラブルになることを目的としています。ファイルグループが最初に作成されたとき、サーバーには2つのCPUしかありませんでした
  • 8 GBメモリ
  • 500 GBディスクストレージ(ISCSI SAN)
  • 4 CPU(Intel Iと想定)

IIS Exchange Serverログテーブルスキーマ:

CREATE TABLE [FWY].[ExchangeServerLogTest](
    [RowKey] [int] IDENTITY(1,1) NOT NULL,
    [SourceFileName] [varchar](50) NOT NULL,
    [SourceServer] [varchar](9) NOT NULL,
    [SourceService] [varchar](6) NOT NULL,
    [EventOccuranceTs] [datetime] NOT NULL,
    [ServiceType] [varchar](50) NOT NULL,
    [UserNameType] [varchar](25) NOT NULL,
    [DomainId] [varchar](50) NULL,
    [DomainName] [varchar](255) NULL,
    [UserNameToLookup] [varchar](255) NOT NULL,
    [UserAgent] [varchar](255) NULL,
    [OutsideProtocolId] [varchar](10) NOT NULL,
    [OutsideIp] [varchar](39) NULL,
    [OutsideIpHex] [varbinary](16) NULL,
    [InsideProtocolId] [varchar](10) NOT NULL,
    [InsideIp] [varchar](39) NULL,
    [InsideIpHex] [varbinary](16) NULL,
    [DeviceId] [varchar](32) NULL,
    [DeviceType] [varchar](25) NULL,
    [DeviceModel] [varchar](75) NULL,
    [AsOfDt] [date] NULL,
    [OutsideProtocolKey] [int] NULL,
    [InsideProtocolKey] [int] NULL,
 CONSTRAINT [PK_ExchangeServerLogTest] PRIMARY KEY CLUSTERED 
(
    [RowKey] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [SECONDARY]
) ON [SECONDARY]

非クラスタ化インデックス:

CREATE NONCLUSTERED INDEX [NCIDX_ExchangeServerLogTest_InsideOutsideProtocolKeyIpHexInclRowKey] ON [FWY].[ExchangeServerLogTest]
(
    [InsideProtocolKey] ASC,
    [OutsideProtocolKey] ASC,
    [InsideIpHex] ASC,
    [OutsideIpHex] ASC
)
INCLUDE (   [RowKey]) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
GO

IP GeoLocationデータベンダーテーブルスキーマ

CREATE TABLE [DE].[IpGeoLocation](
    [CreateTs] [datetime] NOT NULL,
    [CreateBy] [varchar](50) NOT NULL,
    [CreateSequenceKey] [int] NULL,
    [UpdateTs] [datetime] NULL,
    [UpdateBy] [varchar](50) NULL,
    [UpdateSequenceKey] [int] NULL,
    [ActiveInd] [int] NOT NULL,
    [RowKey] [int] IDENTITY(1,1) NOT NULL,
    [VendorKey] [int] NULL,
    [VendorTypeKey] [int] NULL,
    [DimensionTypeKey] [int] NULL,
    [ProtocolKey] [int] NULL,
    [ProtocolId] [varchar](10) NOT NULL,
    [EffectiveStartDate] [date] NULL,
    [EffectiveEndDate] [date] NULL,
    [NetworkStartIp] [varchar](39) NOT NULL,
    [NetworkStartIpHex] [varbinary](16) NULL,
    [NetworkEndIp] [varchar](39) NOT NULL,
    [NetworkEndIpHex] [varbinary](16) NULL,
    [Country] [varchar](255) NOT NULL,
    [Region] [varchar](255) NOT NULL,
    [City] [varchar](255) NOT NULL,
    [ConnectionSpeed] [varchar](255) NOT NULL,
    [ConnectionType] [varchar](255) NOT NULL,
    [MetroCode] [int] NOT NULL,
    [Latitude] [numeric](6, 3) NULL,
    [Longitude] [numeric](6, 3) NULL,
    [PostalCode] [varchar](255) NOT NULL,
    [PostalExtension] [varchar](255) NOT NULL,
    [CountryCode] [int] NOT NULL,
    [RegionCode] [int] NOT NULL,
    [CityCode] [int] NOT NULL,
    [ContinentCode] [int] NOT NULL,
    [TwoLetterCountry] [varchar](2) NOT NULL,
    [InternalCode] [int] NOT NULL,
    [AreaCodes] [varchar](255) NOT NULL,
    [CountryConfidenceCode] [int] NOT NULL,
    [RegionConfidenceCode] [int] NOT NULL,
    [CityConfidenceCode] [int] NOT NULL,
    [PostalConfidenceCode] [int] NOT NULL,
    [GmtOffset] [varchar](255) NOT NULL,
    [InDistance] [varchar](255) NOT NULL,
    [TimeZoneName] [varchar](255) NOT NULL,
 CONSTRAINT [PK_IpGeoLocation] PRIMARY KEY CLUSTERED 
(
    [RowKey] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [SECONDARY]
) ON [SECONDARY]

非クラスタ化インデックス:

CREATE NONCLUSTERED INDEX [NCIDX_IpGeoLocation_ProtocolKeyNetworkStartEndIpHexIncRowKey] ON [DE].[IpGeoLocation]
(
    [ProtocolKey] ASC,
    [NetworkStartIpHex] ASC,
    [NetworkEndIpHex] ASC
)
INCLUDE (   [RowKey]) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
GO

IPアドレスは、.NETのSystem.Netクラスを使用して16進値に変換されます:Ipaddress.Parse(IpAddress)。GetAddressBytes()。 SSISを使用してデータファイルを読み込み、ProtocolIdおよびIPアドレスをバイト配列として返すスクリプトコンポーネントがあります。これは、DT_BYTEとしてSSISに入り、SQL ServerのVARBINARY(16)フィールドにマッピングされます(バイト配列は暗黙的に16進値に変換されます)。

ルックアップIPアドレスの範囲

2つのデータセットがあります。IIS Exchange Server IPログレコードとサードパーティベンダーから提供されたIP GeoLocationデータです。Geolocationは一連のIPアドレスをカバーしています。IPアドレスをログファイルを取得してそのGeoLocationを取得します。両方のデータセットがIPv4とIPv6に対応しており、IPアドレスは文字列形式で受信されます。データを読み込むときに、IPアドレスを16進値[VARBINARY(16)]に変換して、 IPアドレスGeoLocationを検索できます。

ここでの問題は、大量のレコードをロードしていることです。現在、ベンダーは2億近くのIPアドレスの地理位置情報(つまり、ディメンションルックアップテーブル)を提供しています。最初から、パフォーマンスの最適化がすべての段階で必要になることを知っていました(つまり、ハードウェア構成、テーブルのパーティション分割、およびインデックス作成戦略)。 1週間分のサンプルログデータをロードしました。これは約1億5000万のレコードです。

注:ログファイルは解析され、レコードの約90%が無視されます-レコードの10%のみをロードしているため、ここで行うことのできるパフォーマンスの向上はありません。

ExchangeLogsテーブルに次のインデックスを作成しました。

  1. RowIdと呼ばれる整数IDENTITY列のクラスター化インデックス
  2. ProtocolId(つまり、整数として表されるIPv4またはIPv6)の非クラスター化インデックス、IpHex; RowIdが含まれる場所

IPGeoLocationテーブルに次のインデックスを作成しました。

  1. RowIdと呼ばれる整数IDENTITY列のクラスター化インデックス
  2. ProtocolId(つまり、整数として表されるIPv4またはIPv6)、StartIpHex、およびEndIpHexの非クラスター化インデックス。 RowIdが含まれる場所

IPジオロケーションを検索するとき、次のように2つのデータセットを結合します。

SELECT COUNT(DISTINCT DE.RowKey)
FROM DE.IpGeoLocation DE
INNER JOIN FWY.ExchangeServerLogTest T
    ON T.InsideProtocolKey = DE.ProtocolKey
    AND T.InsideIpHex BETWEEN DE.NetworkStartIpHex AND DE.NetworkEndIpHex

推定クエリ実行プラン: 推定InsideIpクエリ実行プラン

実際のクエリ実行プラン:クエリが完了するまで待機しています

SELECT COUNT(DISTINCT DE.RowKey)
FROM DE.IpGeoLocation DE
INNER JOIN FWY.ExchangeServerLogTest T
    ON T.OutsideProtocolKey = DE.ProtocolKey
    AND T.OutsideIpHex BETWEEN DE.NetworkStartIpHex AND DE.NetworkEndIpHex

推定実行プラン: 推定OutsideIpクエリ実行プラン

実際のクエリ実行プラン:DOES NOT FINISH

注2: ProtocolIdを含める必要があります。そうでない場合、各IPルックアップに対して2つの結果があります。1つはIPv4用で、もう1つはIPv6用です。

これは、コストの95%がインデックスseekにあり、インデックスの別の2%scan-97%がインデックス作業に起因することを考えると、非常に効率的な実行計画のようです。

ログファイルの各行には、内部IPアドレスと外部IPアドレスの両方が含まれています。ロードされたサンプルデータの場合:

  1. 内部IPリストには3つのDISTINCT IPアドレスが含まれています。
  2. 外部IPリストには、約60,000個のDISTINCT IPアドレスが含まれています。

結果:

  1. 内部IPリストのSELECTが完了するまでに約9分かかります。
  2. 外部IPリストのSELECTは、16.25時間(一晩)実行した後にstoppedでした。

ログテーブルもIP GeoLocationテーブルもパーティション分割していません。これにより、2つの個別のLUNを介してデータをストリーミングすることでパフォーマンスが向上する可能性がありますが、私はまだIT Opsグループからハードウェア構成仕様を取得しようとしています(新しいサーバーをプロビジョニングしただけなので、その情報はまだありません)。

3
J Weezy
  • まず、2つの個別のインデックスを追加することをお勧めします。

    (InsideProtocolKey, InsideIpHex) INCLUDE (RowKey)
    
    (OutsideProtocolKey, OutsideIpHex) INCLUDE (RowKey)
    

    クエリを再試行してください。 4列のインデックスは、列が2番目と4番目の位置にあり、「内側」のクエリ(1番目と3番目)にはわずかに適しているため、「外側」クエリには適していません。さらに、これらの2つのインデックスのサイズは半分になります(20バイト対1行あたり40バイト)。

  • 第二に、マイナーな改善。 ProtocolKey列(およびそのバリエーションであるInside/Outside)には2つのオプションしかないため、(すべての)int(4バイト)からtinyint(1バイト)またはbit(1ビット)に変換して保存できます。行ごとに3バイト(または3 + 7/8)。

    これは大きな節約にはなりませんが、大きなテーブルの場合は役立ちます。それほど大きくない場合、200M行x 3バイト= 600MBの節約、すべてのインデックスに対して列が表示される場所。インデックスbit列のスペースの使用については完全にはわかりませんが、同じテーブルサイズの場合、保存はtinyint(600MB)以上の場合と同じです(最大775MB)。それでも、これについて再度言及しますが、列を使用するすべてのインデックスに対してです。

    インデックスが小さいほど、ディスク上のサイズが小さくなり、より重要ですメモリが少ない、そしてメモリにとどまるの可能性が高くなります(特に、RAMあなたが持っている。

  • 第3に、8 GBは非常に少量のように聞こえますRAM最近このサイズのテーブルがある場合は特にそうです。RAMは安いです(少なくとも合格するまで128GB Standard/Enterpriseのしきい値で、ライセンス料金が高くなります)。

4
ypercubeᵀᴹ