3500万行以上のテーブルに対応する効果的なmysqlテーブル/インデックスデザイン、200以上の対応する列(ダブル)、任意の組み合わせをクエリ可能


17

次の状況でのテーブル/インデックスの設計に関するアドバイスを探しています。

複合主キー(assetid(int)、date(date))を含む大きなテーブル(株価履歴データ、InnoDB、3500万行および成長)があります。価格情報に加えて、各レコードに対応する必要がある200のdouble値があります。

CREATE TABLE `mytable` (
`assetid` int(11) NOT NULL,
`date` date NOT NULL,
`close` double NOT NULL,
`f1` double DEFAULT NULL,   
`f2` double DEFAULT NULL,
`f3` double DEFAULT NULL,   
`f4` double DEFAULT NULL,
 ... skip a few 
`f200` double DEFAULT NULL, 
PRIMARY KEY (`assetid`, `date`)) ENGINE=`InnoDB` DEFAULT CHARACTER SET latin1 COLLATE
    latin1_swedish_ci ROW_FORMAT=COMPACT CHECKSUM=0 DELAY_KEY_WRITE=0 
    PARTITION BY RANGE COLUMNS(`date`) PARTITIONS 51;

更新と取得を容易にするために、最初に200個のダブル列をこのテーブルに直接保存しました。 )、および200の二重列は読み取り専用でした。データベースサイズは約45ギグでした

ただし、次のように、これらの200列(f1、f2、... f200という名前)の任意の組み合わせでこのテーブルを照会できる必要があるという要件があります。

select from mytable 
where assetid in (1,2,3,4,5,6,7,....)
and date > '2010-1-1' and date < '2013-4-5'
and f1 > -0.23 and f1 < 0.9
and f117 > 0.012 and f117 < .877
etc,etc

過去にこれほど大量のデータを扱う必要はなかったので、最初の本能は、これらの200列のそれぞれにインデックスが必要であるか、大規模なテーブルスキャンなどを行うことでした。私は、主キー、値、およびインデックス値を持つ200列ごとにテーブルが必要でした。それで私はそれで行きました。

CREATE TABLE `f1` (
`assetid` int(11) NOT NULL DEFAULT '0',
`date` date NOT NULL DEFAULT '0000-00-00',
`value` double NOT NULL DEFAULT '0',
PRIMARY KEY (`assetid`, `date`),
INDEX `val` (`value`)
) ENGINE=`InnoDB` DEFAULT CHARACTER SET latin1 COLLATE latin1_swedish_ci ROW_FORMAT=COMPACT CHECKSUM=0 DELAY_KEY_WRITE=0;

200のテーブルすべてを埋めてインデックスを作成しました。定期的にassetidと日付範囲を照会され、200列すべてが選択されるため、メイン列には200列すべてがそのまま残されています。親テーブルにこれらの列を(インデックスなしで)読み取り目的で残し、さらに(フィルタリングを結合するために)独自のテーブルにインデックスを付けておくと、最もパフォーマンスが向上すると考えました。クエリの新しい形式について説明しました

select count(p.assetid) as total 
from mytable p 
inner join f1 f1 on f1.assetid = p.assetid and f1.date = p.date
inner join f2 f2 on f2.assetid = p.assetid and f2.date = p.date 
where p.assetid in(1,2,3,4,5,6,7)
and p.date >= '2011-01-01' and p.date < '2013-03-14' 
and(f1.value >= 0.96 and f1.value <= 0.97 and f2.value >= 0.96 and f2.value <= 0.97) 

確かに、私の希望する結果が達成されたので、このクエリではスキャンされた行がはるかに小さいことが説明でわかります。しかし、私はいくつかの望ましくない副作用で終わった。

1)私のデータベースは45ギガバイトから110ギガバイトになりました。データベースをRAMに保持できなくなりました。(ただし、途中で256GigのRAMがあります)

2)新しいデータの夜間挿入は、1回ではなく200回行う必要があります

3)新しい200個のテーブルのメンテナンス/デフラグは、1つのテーブルよりも200倍長くかかります。一晩で完了することはできません。

4)f1などのテーブルに対するクエリは、必ずしもパフォーマンスが高いとは限りません。例えば:

 select min(value) from f1 
 where assetid in (1,2,3,4,5,6,7) 
 and date >= '2013-3-18' and date < '2013-3-19'

上記のクエリは、explainが1000行未満で検索されることを示していますが、完了するまでに30秒以上かかる場合があります。これは、インデックスが大きすぎてメモリに収まらないためだと思います。

それは多くの悪いニュースだったので、さらに調べてパーティション分割を見つけました。メインテーブルにパーティションを実装し、3か月ごとにパーティション分割しました。月刊は私には理にかなっているように見えましたが、120パーティション以上を取得するとパフォーマンスが低下することを読みました。四半期ごとにパーティション分割することで、今後20年ほどはその下に置かれます。各パーティションは2ギガ未満です。私はパーティションの説明を実行しましたが、すべてが適切にプルーニングされているようです。

私はこの記事でかなりの時間を費やしました

http://ftp.nchu.edu.tw/MySQL/tech-resources/articles/testing-partitions-large-db.html

私のテーブルは現在、主キーがまだ残っている状態でパーティション分割されています。この記事では、主キーによってパーティションテーブルの速度が低下する可能性がありますが、それを処理できるマシンがある場合は、パーティションテーブルの主キーが高速になります。途中に大きなマシン(256 G RAM)があることを知って、キーをオンのままにしました。

私が見るように、ここに私のオプションがあります

オプション1

1)余分な200個のテーブルを削除し、クエリにテーブルスキャンを実行させて、f1、f2などの値を見つけます。一意でないインデックスは、適切にパーティション分割されたテーブルのパフォーマンスを実際に損なう可能性があります。ユーザーがクエリを実行する前にExplainを実行し、スキャンされた行数が定義したしきい値を超えている場合は拒否します。巨大なデータベースの手間を省きます。とにかく、それはすべてすぐにメモリ内にあります。

サブ質問:

適切なパーティション構成を選択したように聞こえますか?

オプション2

同じ3か月のスキームを使用して、200のテーブルすべてをパーティション分割します。より小さな行スキャンを楽しんで、ユーザーがより大きなクエリを実行できるようにします。少なくともパーティション化されているので、メンテナンスのために一度に1パーティションずつ管理できます。とにかく、それはすべてすぐにメモリ内にあります。それらを毎晩更新する効率的な方法を開発します。

サブ質問:

これらのf1、f2、f3、f4 ...テーブルの主キーインデックスを避けることができる理由がわかりますか?クエリ時に常にアセットIDと日付があることを知っていますか?私には直観に反するようですが、このサイズのデータ​​セットには慣れていません。それはデータベースを私が推測する束を縮小するだろう

オプション3

マスター表のf1、f2、f3列をドロップして、そのスペースを再利用します。200の機能を読む必要がある場合は200の参加を行います。

オプション4

これまで考えてきた以上に、皆さんはこれを構築するためのより良い方法を持っています。

*注:間もなくこれらのdouble値のうち50〜100個を各アイテムに追加するので、それが来ることを知って設計する必要があります。

すべての助けをありがとう

アップデート#1-2013年3月24日

私は下にあるコメントで提案されたアイデアを採用し、次のセットアップで新しいテーブルを1つ作成しました:

create table 'features'{
  assetid int,
  date    date,
  feature varchar(4),
  value   double
}

3か月間隔でテーブルを分割しました。

以前の200個のテーブルを吹き飛ばして、データベースを45ギガまで下げ、この新しいテーブルをいっぱいにし始めました。1日半後、それは完了し、私のデータベースはぽっちゃりの 220ギグになりました!

マスターテーブルからこれらの200の値を削除する可能性があります。1つの結合から値を取得できますが、実際には25ギガ程度しか返されません

assetid、date、feature、value on indexの主キーを作成するように依頼しましたが、9時間たっても、くぼみがなく、フリーズしたように見えたので、その部分を削除しました。

いくつかのパーティションを再構築しましたが、多くのスペースを取り戻すことはできませんでした。

そのため、そのソリューションはおそらく理想的ではないように見えます。行が列よりも大幅に多くのスペースを占めるのか、それがこのソリューションがこれほど多くのスペースを占めるのはなぜでしょうか?

私はこの記事に出会いました:

http://www.chrismoos.com/2010/01/31/mysql-partitioning-tables-with-millions-of-rows

アイデアを与えてくれました。それは言います:

最初は、日付によるRANGEパーティション化について考えました。クエリで日付を使用している間、クエリの日付範囲が非常に広いのは非常に一般的であり、すべてのパーティションに簡単に適用できることを意味します。

現在、日付による範囲のパーティション分割も行っていますが、大きな日付範囲による検索も許可するため、パーティション分割の効果が低下します。検索するときは常に日付範囲がありますが、資産IDのリストも常にあります。おそらく私の解決策は、アセットIDと日付でパーティション分割することであり、通常検索されたアセットIDの範囲を特定します(標準リスト、S&P 500、Russell 2000などがあります)。この方法では、データセット全体をほとんど見ることはありません。

繰り返しになりますが、私はとにかくassetidとdateに主キーが設定されているので、おそらくそれはあまり役に立たないでしょう。

これ以上の考え/コメントをいただければ幸いです。


2
200個のテーブルが必要な理由がわかりません。持つ単一のテーブルには、(value_name varchar(20), value double)店舗のすべてのことができるようになり(value_namef1f2、...)
a_horse_with_no_name

ありがとう。私がそれらを個別に置いた理由は、テーブルのインデックスを50に制限することでした。私はそれらをそれぞれ40の値の5つのテーブルに入れることを考えていましたが、私はそれぞれ1日に17000かそこらのレコードを挿入しており、40のインデックスを持つテーブルで挿入パフォーマンスがどのようになるかわかりませんでした。assetid、dateの各組み合わせは、独自のf1、f2 ...値を取得することに注意してください。(assetid、date、value_name、value)を持ち、主キーassetid、dateを持つ単一のテーブルを提案していますか?そのテーブルには35 mil * 200 = 70億行がありますが、おそらくうまくパーティション分割されますか?
dyeryn

この方法を試した私の経験で投稿を更新
-dyeryn

開発中の最終的なソリューションがあります。完了したら更新します。基本的に、ここで提案する特定のパーティション化と論理シャーディングを備えた単一テーブルソリューションです。
dyeryn

別のストレージエンジンが役立つ場合がありますか?InnoDbの代わりにInfiniDBを試すことができますか?カラムナーデータ、アクセスパターンは大きなバッチ更新のように見え、範囲ベースの読み取り、最小限のテーブルメンテナンス。
乱雑な

回答:


1

偶然にも、柔軟性のためにキーと値のペア構造を設計したクライアントサポートの1つを検討しています。現在のテーブルは1.5B行を超えており、ETLは遅すぎます。私の場合、他にもたくさんありますが、そのデザインについて考えてみてください。200列すべてに値が存在する1つの行があり、その行はKey-Valueペア設計で200行に変換されます。特定のAssetIDと日付に応じて、実際に200個のf1からf200の値がすべて存在する行数に応じて、この設計でスペースの利点が得られますか?30%のodカラムでさえNULL値を持っていると言うと、それはスペース節約になります。キーと値のペアの設計では、値IDがNULLの場合、その行はテーブルにある必要はありません。しかし、既存の列構造設計では、NULLでもスペースが必要です。(100%確信はありませんが、テーブルに30列以上のNULLがある場合、NULLは4バイトを使用します)。このデザインを見て、すべての35M行に200列すべてに値があると仮定すると、現在のデータベースはすぐに200 * 35M = 700M行になります。しかし、列を行に転置しているだけなので、単一のテーブルのすべての列で持っていたものよりも、表スペースはそれほど高くありません。この転置操作では、実際には値がNULLの行はありません。したがって、実際にこのテーブルに対してクエリを実行し、そこにヌルがいくつあるかを確認し、実際に実装する前にターゲットテーブルのサイズを見積もることができます。しかし、列を行に転置しているだけなので、単一のテーブルのすべての列で持っていたものよりも、表スペースはそれほど高くありません。この転置操作では、実際には値がNULLの行はありません。したがって、実際にこのテーブルに対してクエリを実行し、そこにヌルがいくつあるかを確認し、実際に実装する前にターゲットテーブルのサイズを見積もることができます。しかし、列を行に転置しているだけなので、単一のテーブルのすべての列で持っていたものよりも、表スペースはそれほど高くありません。この転置操作では、実際には値がNULLの行はありません。したがって、実際にこのテーブルに対してクエリを実行し、そこにヌルがいくつあるかを確認し、実際に実装する前にターゲットテーブルのサイズを見積もることができます。

2番目の利点は、読み取りパフォーマンスです。先ほど述べたように、データをクエリする新しい方法は、where句のf1〜f200列の任意の組み合わせです。キーと値のペアのデザインでは、f1からf200が1つの列に存在し、「FildName」と言い、2番目の列に値があり、「FieldValue」と言います。両方の列にCLUSTEREDインデックスを作成できます。クエリはこれらの選択のUNIONになります。

WHERE(FiledName = 'f1'およびFieldValue BETWEEN 5 AND 6)

連合

(FiledName = 'f2'およびFieldValue BETWEEN 8 AND 10)

等.....

実際のprodサーバーのパフォーマンスの数値をいくつか示します。セキュリティTICKERごとに75の価格列があります。


1

多くの行を挿入する必要があり、分析クエリのパフォーマンスも非常に優れている必要があるこの種のデータを扱う場合(ここではそうであると仮定しています)、円柱RDBMSが適していることがわかります。 。Infobright CEとInfiniDB CE(両方とも円柱ストレージエンジンがMySQLにプラグインされている)、およびVertica CE(MySQLの代わりにPostgreSQLに似ている)をご覧ください...これらのCommunity Editionはすべて無料です(Verticaはそうではありませんが)オープンソースの場合、3ノードおよび1Tbのデータに無料で拡張できます)。Columnar RDBMSは通常、行ベースよりも10〜100倍優れた「大きなクエリ」応答時間と、5〜50倍優れたロード時間を提供します。それらを正しく使用するか、悪臭を放つ必要があります(単一行の操作を行わないでください...一括操作ですべての操作を行う)が、正しく使用すると、本当に揺れます。;-)

HTH、デイブ・シスク


1
3ノードのVerticaインストールでは、ほぼ10億行のクリックストリームタイプのデータ(ストックティッカーデータとは異なります)があります。1日分のデータを約15秒で読み込むことができ、クエリ応答時間は500ミリ秒の範囲。あなたの場合、これは一見の価値があるようです。
デイブシスク

同じことを保証できます。私の最後の会社では、ほぼ同じ行数の8ノードのVerticaクラスターと、1〜3秒(平均)で返されたセット全体の単純な集約クエリがありました。以前のGreenplumクラスターの約1/4のコストでもありました。
bma
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.