およそ1000万行(または7GB)からなる非常に大きなInnoDBテーブルを一括インポートするのに苦労しています(これは、これまでに扱った中で最大のテーブルです)。
Innoのインポート速度を改善する方法を調べましたが、今のところ、私のセットアップは次のようになっています。
/etc/mysql/my.cnf/
[...]
innodb_buffer_pool_size = 7446915072 # ~90% of memory
innodb_read_io_threads = 64
innodb_write_io_threads = 64
innodb_io_capacity = 5000
innodb_thread_concurrency=0
innodb_doublewrite = 0
innodb_log_file_size = 1G
log-bin = ""
innodb_autoinc_lock_mode = 2
innodb_flush_method = O_DIRECT
innodb_flush_log_at_trx_commit=2
innodb_buffer_pool_instances=8
import is done via bash script, here is the mysql code:
SET GLOBAL sync_binlog = 1;
SET sql_log_bin = 0;
SET FOREIGN_KEY_CHECKS = 0;
SET UNIQUE_CHECKS = 0;
SET AUTOCOMMIT = 0;
SET SESSION tx_isolation='READ-UNCOMMITTED';
LOAD DATA LOCAL INFILE '$filepath' INTO TABLE monster
COMMIT;
データはCSV
ファイルで提供されます。
現在、200万、300万、…の各行を含む小さな「テストダンプ」で設定をテストし、time import_script.sh
パフォーマンスを比較します。
欠点は、全体の実行時間しか得られないため、完全なインポートが完了して結果が得られるまで待つ必要があることです。
これまでの私の結果:
「クックブック」の解決策はないようで、自分で設定の最適な組み合わせを見つける必要があります。
セットアップで何を変更するかについての提案に加えて、インポートプロセスのベンチマークを改善したり、何が起こっているのか、ボトルネックがどこにあるのかについてより多くの洞察を得たりする方法について、さらに詳しい情報をいただければ幸いです。
変更している設定のドキュメントを読み込もうとしましたが、副作用を認識していないため、不適切な値を選択するとパフォーマンスが低下する可能性があります。
とりあえず、インポート中にMyISAM
を使用し、後でテーブルエンジンを変更するようチャットから提案を試みたいと思います。
これを試したいのですが、今のところDROP TABLE
クエリも完了するまでに数時間かかります。 (私の設定が最適ではない別の指標のようです)。
追加情報:
現在使用しているマシンには、8 GBのRAMおよび5400RPMのソリッドステートハイブリッドハードドライブがあります。
問題のテーブルから古いデータを削除することも目的としていますが、
テスト automatic data cleanup feature
開発中および
b)サーバーがクラッシュした場合に、2つ目のサーバーを代替として使用します(最新のデータが必要です。最後のインポートには24時間以上かかりました)
mysql> SHOW CREATE TABLE monster\G
*************************** 1. row ***************************
Table: monster
Create Table: CREATE TABLE `monster` (
`monster_id` int(11) NOT NULL AUTO_INCREMENT,
`ext_monster_id` int(11) NOT NULL DEFAULT '0',
`some_id` int(11) NOT NULL DEFAULT '0',
`email` varchar(250) NOT NULL,
`name` varchar(100) NOT NULL,
`address` varchar(100) NOT NULL,
`postcode` varchar(20) NOT NULL,
`city` varchar(100) NOT NULL,
`country` int(11) NOT NULL DEFAULT '0',
`address_hash` varchar(250) NOT NULL,
`lon` float(10,6) NOT NULL,
`lat` float(10,6) NOT NULL,
`ip_address` varchar(40) NOT NULL,
`cookie` int(11) NOT NULL DEFAULT '0',
`party_id` int(11) NOT NULL,
`status` int(11) NOT NULL DEFAULT '2',
`creation_date` datetime NOT NULL,
`someflag` tinyint(1) NOT NULL DEFAULT '0',
`someflag2` tinyint(4) NOT NULL,
`upload_id` int(11) NOT NULL DEFAULT '0',
`news1` tinyint(4) NOT NULL DEFAULT '0',
`news2` tinyint(4) NOT NULL,
`someother_id` int(11) NOT NULL DEFAULT '0',
`note` varchar(2500) NOT NULL,
`referer` text NOT NULL,
`subscription` int(11) DEFAULT '0',
`hash` varchar(32) DEFAULT NULL,
`thumbs1` int(11) NOT NULL DEFAULT '0',
`thumbs2` int(11) NOT NULL DEFAULT '0',
`thumbs3` int(11) NOT NULL DEFAULT '0',
`neighbours` tinyint(4) NOT NULL DEFAULT '0',
`relevance` int(11) NOT NULL,
PRIMARY KEY (`monster_id`),
KEY `party_id` (`party_id`),
KEY `creation_date` (`creation_date`),
KEY `email` (`email`(4)),
KEY `hash` (`hash`(8)),
KEY `address_hash` (`address_hash`(8)),
KEY `thumbs3` (`thumbs3`),
KEY `ext_monster_id` (`ext_monster_id`),
KEY `status` (`status`),
KEY `note` (`note`(4)),
KEY `postcode` (`postcode`),
KEY `some_id` (`some_id`),
KEY `cookie` (`cookie`),
KEY `party_id_2` (`party_id`,`status`)
) ENGINE=InnoDB AUTO_INCREMENT=13763891 DEFAULT CHARSET=utf8
まず、何百万もの行をInnoDBテーブルに送り込むときにInnoDBに対して何をしているのかを知る必要があります。 InnoDBアーキテクチャを見てみましょう。
左上隅に、InnoDBバッファープールの図があります。挿入バッファー専用のセクションがあることに注意してください。それは何をしますか?セカンダリインデックスへの変更をバッファプールからシステムテーブルスペース(別名ibdata1)内の挿入バッファに移行することが目的です。デフォルトでは、 innodb_change_buffer_max_size は25に設定されています。これは、バッファープールの最大25%をセカンダリインデックスの処理に使用できることを意味します。
あなたの場合、InnoDBバッファープール用に6.935 GBがあります。セカンダリインデックスの処理には最大1.734 GBが使用されます。
今、あなたのテーブルを見てください。 13個のセカンダリインデックスがあります。処理する各行は、セカンダリインデックスエントリを生成し、それを行の主キーと結合し、それらをペアとして、バッファープールの挿入バッファーからibdata1の挿入バッファーに送信する必要があります。これは、各行で13回発生します。これに1,000万を掛けると、ボトルネックが発生しそうな気がします。
1つのトランザクションで1,000万行をインポートすると、すべてが1つのロールバックセグメントに積み上げられ、ibdata1のUNDOスペースがいっぱいになることを忘れないでください。
このかなり大きなテーブルをインポートするための最初の提案は
重複するインデックスを削除します。あなたの場合、あなたは持っています
KEY `party_id` (`party_id`),
KEY `party_id_2` (`party_id`,`status`)
両方のインデックスはparty_id
で始まります。セカンダリインデックスの処理を少なくとも7.6%増やすことで、13のうち1つのインデックスを取り除くことができます。最終的に実行する必要があります
ALTER TABLE monster DROP INDEX party_id;
使用しないインデックスを削除します。アプリケーションコードを調べ、クエリがすべてのインデックスを使用しているかどうかを確認します。 pt-index-usage を調べて、使用されていないインデックスを提案することができます。
デフォルトは8Mなので、 innodb_log_buffer_size を64Mに増やす必要があります。より大きなログバッファーは、InnoDB書き込みI/Oパフォーマンスを向上させる可能性があります。
最初の2つの提案を配置して、次の操作を行います。
party_id
インデックスを除くすべての一意でないインデックスを作成しますおそらく以下が役立つかもしれません
CREATE TABLE monster_new LIKE monster;
ALTER TABLE monster_new
DROP INDEX `party_id`,
DROP INDEX `creation_date`,
DROP INDEX `email`,
DROP INDEX `hash`,
DROP INDEX `address_hash`,
DROP INDEX `thumbs3`,
DROP INDEX `ext_monster_id`,
DROP INDEX `status`,
DROP INDEX `note`,
DROP INDEX `postcode`,
DROP INDEX `some_id`,
DROP INDEX `cookie`,
DROP INDEX `party_id_2`;
ALTER TABLE monster RENAME monster_old;
ALTER TABLE monster_new RENAME monster;
データをmonster
にインポートします。次に、これを実行します
ALTER TABLE monster
ADD INDEX `creation_date`,
ADD INDEX `email` (`email`(4)),
ADD INDEX `hash` (`hash`(8)),
ADD INDEX `address_hash` (`address_hash`(8)),
ADD INDEX `thumbs3` (`thumbs3`),
ADD INDEX `ext_monster_id` (`ext_monster_id`),
ADD INDEX `status` (`status`),
ADD INDEX `note` (`note`(4)),
ADD INDEX `postcode` (`postcode`),
ADD INDEX `some_id` (`some_id`),
ADD INDEX `cookie` (`cookie`),
ADD INDEX `party_id_2` (`party_id`,`status`);
monster_csv
というテーブルを、インデックスのないMyISAMテーブルとして作成し、これを行うことができます。
CREATE TABLE monster_csv ENGINE=MyISAM AS SELECT * FROM monster WHERE 1=2;
ALTER TABLE monster RENAME monster_old;
CREATE TABLE monster LIKE monster_old;
ALTER TABLE monster DROP INDEX `party_id`;
データをmonster_csv
にインポートします。次に、mysqldumpを使用して別のインポートを作成します。
mysqldump -t -uroot -p mydb monster_csv | sed 's/monster_csv/monster/g' > data.sql
Mysqldumpファイルdata.sql
は、一度に10,000〜20,000行をインポートするINSERTコマンドを拡張します。
さて、mysqldumpをロードしてください
mysql -uroot -p mydb < data.sql
最後に、MyISAMテーブルを削除します
DROP TABLE monster_csv;
コメントを書きたかったのですが(これは決定的な答えではないため)、長くなりすぎました:
私はあなたにいくつかの幅広いアドバイスを与えるつもりです、そしてあなたが望むなら、私たちはそれぞれについて詳細に行くことができます:
これらの一部は、安全でないか、非インポート(通常の操作)では推奨されないことに注意してください。
これまでのところ、優れたヒントのほとんどは提供されていますが、最良のヒントについては多くの説明がありません。詳細をお知らせします。
まず、インデックスの作成を遅らせることは良いことであり、他の応答に十分な詳細があります。私はそれに戻りません。
大きいInnoDBログファイルは非常に役立ちます(MySQL 5.6を使用している場合、MySQL 5.5では増やすことができないため)。 7 GBのデータを挿入しています。合計ログサイズは少なくとも8 GBにすることをお勧めします(innodb_log_files_in_group
デフォルト(2)でバンプinnodb_log_file_size
4 GBで)。この8 GBは正確ではありません。少なくともREDOログのインポートサイズであり、おそらくそのサイズの2倍または4倍である必要があります。 InnoDBログサイズの背後にある理由により、ログがほぼいっぱいになると、InnoDBは積極的にそのバッファープールをディスクにフラッシュし始めて、ログがいっぱいになるのを回避します(ログがいっぱいになると、InnoDBはいくつかのバッファプールのページがディスクに書き込まれます)。
大きいInnoDBログファイルが役立ちますが、主キーの順序で挿入する必要もあります(挿入する前にファイルを並べ替えます)。主キーの順序で挿入すると、InnoDBは1つのページを埋め、次に別のページを埋めていきます。主キーの順序で挿入しない場合、次の挿入はページがいっぱいになり、「ページ分割」が発生する可能性があります。このページ分割はInnoDBにとってコストがかかり、インポートが遅くなります。
RAMで十分なバッファプールが既にあり、テーブルがそれに収まらない場合、RAMを追加購入する以外にできることはほとんどありません。しかし、テーブルが収まるバッファプールがバッファプールの75%より大きい場合は、innodb_max_dirty_pages_pct
インポート中は85または95(デフォルト値は75)。この構成パラメーターは、ダーティページのパーセンテージがこの制限に達すると、バッファープールの積極的なフラッシュを開始するようにInnoDBに指示します。このパラメーターを増やすことで(そして、データサイズが運が良ければ)、インポート中にアグレッシブなIOを回避し、これらのIOを後で延期することができます。
多分(これは推測ですが)多くの小さなトランザクションでデータをインポートすると役立つでしょう。 REDOログがどのように構築されるかは正確にはわかりませんが、RAM(およびディスクが多すぎる場合、RAMが必要になるとディスク)にバッファされる場合、トランザクションが進行していると、不要なIOが発生する可能性があります。これを試すことができます。ファイルを並べ替えたら、それを多数のチャンクに分割し(16 MBと他のサイズで試してください)、1つずつインポートします。これにより、インポートの進行状況を制御します。インポート中にデータが他のリーダーに部分的に表示されないようにするには、別のテーブル名を使用してインポートし、後でインデックスを作成してから、テーブルの名前を変更します。
ハイブリッドSSD/5400RPMディスクについては、それらについて、およびこれを最適化する方法についてはわかりません。 5400RPMはデータベースに対して低速に見えますが、SSDがそれを回避している可能性があります。おそらく、REDOログへの順次書き込みでディスクのSSD部分を埋めている可能性があり、SSDがパフォーマンスを低下させています。私は知らない。
試してはいけない(または注意が必要な)悪いヒントは次のとおりです。マルチスレッドを使用しないでください。InnoDBでページ分割を回避するために最適化するのは非常に困難です。マルチスレッドを使用する場合は、異なるテーブル(または同じテーブルの異なるパーティション)に挿入します。
マルチスレッドを検討している場合は、マルチソケット(NUMA)コンピュータを使用している可能性があります。この場合、 MySQLスワップの狂気の問題 を避けてください。
MySQL 5.5を使用している場合は、MySQL 5.6にアップグレードします。これには、REDOログサイズを増やすオプションがあり、バッファプールのフラッシュアルゴリズムが向上しています。
インポートで頑張ってください。