web-dev-qa-db-ja.com

InnoDBインポートのパフォーマンス

およそ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パフォーマンスを比較します。

欠点は、全体の実行時間しか得られないため、完全なインポートが完了して結果が得られるまで待つ必要があることです。

これまでの私の結果:

  • 10 000行:<1秒
  • 100 000行:10秒
  • 30万行:40秒
  • 200万行:18分
  • 300万行:26分
  • 400万行:(2時間後にキャンセル)

「クックブック」の解決策はないようで、自分で設定の最適な組み合わせを見つける必要があります。
セットアップで何を変更するかについての提案に加えて、インポートプロセスのベンチマークを改善したり、何が起こっているのか、ボトルネックがどこにあるのかについてより多くの洞察を得たりする方法について、さらに詳しい情報をいただければ幸いです。
変更している設定のドキュメントを読み込もうとしましたが、副作用を認識していないため、不適切な値を選択するとパフォーマンスが低下する可能性があります。

とりあえず、インポート中に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
10
nuala

まず、何百万もの行をInnoDBテーブルに送り込むときにInnoDBに対して何をしているのかを知る必要があります。 InnoDBアーキテクチャを見てみましょう。

InnoDB Architecture

左上隅に、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スペースがいっぱいになることを忘れないでください。

提案

提案#1

このかなり大きなテーブルをインポートするための最初の提案は

  • 一意でないすべてのインデックスを削除する
  • データをインポートする
  • 一意でないすべてのインデックスを作成する

提案#2

重複するインデックスを削除します。あなたの場合、あなたは持っています

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;

提案#3

使用しないインデックスを削除します。アプリケーションコードを調べ、クエリがすべてのインデックスを使用しているかどうかを確認します。 pt-index-usage を調べて、使用されていないインデックスを提案することができます。

提案#4

デフォルトは8Mなので、 innodb_log_buffer_size を64Mに増やす必要があります。より大きなログバッファーは、InnoDB書き込みI/Oパフォーマンスを向上させる可能性があります。

エピローグ

最初の2つの提案を配置して、次の操作を行います。

  • 13の一意でないインデックスを削除する
  • データをインポートする
  • 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;
13
RolandoMySQLDBA

コメントを書きたかったのですが(これは決定的な答えではないため)、長くなりすぎました:

私はあなたにいくつかの幅広いアドバイスを与えるつもりです、そしてあなたが望むなら、私たちはそれぞれについて詳細に行くことができます:

  • 耐久性を低下させます(すでに一部を実行しています)。最新バージョンでは、さらに多くのことができます。インポートでは破損は問題にならないため、二重書き込みバッファを無効にすることもできます。
  • バッファリングを増やす:トランザクションログのサイズを増やし、使用可能なバッファプールのサイズを増やします。トランザクションログファイルの使用状況とチェックポイントを監視します。インポートの巨大なログを恐れないでください。
  • 巨大なトランザクションを避けてください。ロールバックは不要なデータでいっぱいになります。 これはおそらくあなたの最大の問題です。
  • SQLがボトルネックになり、SQLオーバーヘッド(handlersocket、memcached)を回避したり、同時に複数のスレッドと並行してロードしたりします。並行性は、多すぎず、少なすぎず、スイートスポットに到達する必要があります。
  • 主キーの順序でデータをロードする断片化は問題になる可能性があります
  • IOがボトルネックであり、CPUとメモリによって速度が低下しない場合は、InnoDB圧縮をテストします
  • 後で(場合によってはより速く)セカンダリキーを作成してみてください。インデックス付きのデータをロードしないでくださいDISABLE KEYSはInnoDBに影響しません。そうでない場合は、挿入バッファーを監視します(おそらく、バッファープールの半分を追い越します)。
  • チェックサムアルゴリズムを変更または無効にします。おそらく問題ではありませんが、ハイエンドフラッシュカードではボトルネックになります。
  • 最後の手段:サーバーを監視して現在のボトルネックを見つけ、軽減を試みます(InnoDBはそれについて非常に柔軟です)。

これらの一部は、安全でないか、非インポート(通常の操作)では推奨されないことに注意してください。

8
jynus

これまでのところ、優れたヒントのほとんどは提供されていますが、最良のヒントについては多くの説明がありません。詳細をお知らせします。

まず、インデックスの作成を遅らせることは良いことであり、他の応答に十分な詳細があります。私はそれに戻りません。

大きい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ログサイズを増やすオプションがあり、バッファプールのフラッシュアルゴリズムが向上しています。

インポートで頑張ってください。

3
jfg956