Symfoware

Symfowareについての考察blog

CodeIgniterで大量データを高速に登録する方法の検討

PHPのwebフレームワークCodeIgniter
http://www.codeigniter.com/

このフレームワークを使用しての大量データ投入を
高速に行う方法を考えてみます。

使用したCodeIgniterのバージョンは3.1.0
PHPのバージョンは5.6.24です。


サンプルデータ



郵便番号データ123,929件を登録してみます。
http://www.post.japanpost.jp/zipcode/dl/kogaki-zip.html

こちらの全国一括版を使用しました。
Pythonで文字コードをutf-8に変換し、郵便番号と住所だのcsvファイルに加工しておきます。


  1. # -*- coding:utf-8 *-
  2. def sunit(s):
  3.     
  4.     s = s.strip()
  5.     data = [s.strip('"') for s in line.split(u',')]
  6.     zip_code = data[2]
  7.     address = data[6] + data[7] + data[8]
  8.     
  9.     return '%s,%s\n' % (zip_code, address)
  10. kenall = codecs.open('KEN_ALL.CSV', 'r', 'ms932')
  11. out = codecs.open('all.csv', 'w', 'utf-8')
  12. with kenall, out:
  13.     for line in kenall:
  14.         out.write(sunit(line))




登録するデータベースにはMariaDB 10.0.26を使用します。
Debian 8.5にインストールしました。


郵便番号を格納するテーブルの定義は以下のとおりです。


  1. create table zip (
  2.     zip_code char(7) not null,
  3.     address varchar(100) not null
  4. );






csvファイルの読み込み



csvファイルを読み込むだけの処理はこんな感じになります。

・application/controllers/Test.php


  1. <?php
  2. class Test extends CI_Controller {
  3.     public function index() {
  4.         
  5.         $this->load->database();
  6.         
  7.         // 全件削除
  8.         $this->db->empty_table('zip');
  9.         
  10.         // csvファイル読み込み準備
  11.         $csv = new SplFileObject('/var/dev/php/all.csv', 'r');
  12.         $csv->setFlags(SplFileObject::READ_CSV);
  13.         
  14.         
  15.         // 時間計測開始
  16.         $start = microtime(true);
  17.         
  18.         foreach ($csv as $row) {
  19.             if ($row === [null]) {
  20.                 break;
  21.             }
  22.         }
  23.         
  24.         $stop = microtime(true);
  25.         echo sprintf("処理時間:%.3f 秒\n", ($stop - $start));
  26.         
  27.     }
  28. }




コマンドでプログラムを実行します。


$ php index.php test




時間を計測すると、0.3226秒でした。





単件インサート



1件づつ登録するサンプルです。


  1. <?php
  2. class Test extends CI_Controller {
  3.     public function index() {
  4.         
  5.         $this->load->database();
  6.         
  7.         // 全件削除
  8.         $this->db->empty_table('zip');
  9.         
  10.         // csvファイル読み込み準備
  11.         $csv = new SplFileObject('/var/dev/php/all.csv', 'r');
  12.         $csv->setFlags(SplFileObject::READ_CSV);
  13.         $csv->setFlags($csv->getFlags() | SplFileObject::SKIP_EMPTY);
  14.         $csv->setFlags($csv->getFlags() | SplFileObject::DROP_NEW_LINE);
  15.         
  16.         
  17.         // 時間計測開始
  18.         $start = microtime(true);
  19.         
  20.         foreach ($csv as $row) {
  21.             $this->db->insert('zip', [
  22.                 'zip_code' => $row[0],
  23.                 'address' => $row[1],
  24.             ]);
  25.         }
  26.         
  27.         $stop = microtime(true);
  28.         echo sprintf("処理時間:%.3f 秒\n", ($stop - $start));
  29.         
  30.     }
  31. }




結果は4074秒となりました。
なんだか怪しい測定結果ですが、かなり待たされたのは間違いないです。





insert_batch



insert_batchを使用して100件毎に一括登録してみます。


  1. <?php
  2. class Test extends CI_Controller {
  3.     public function index() {
  4.         
  5.         $this->load->database();
  6.         
  7.         // 全件削除
  8.         $this->db->empty_table('zip');
  9.         
  10.         // csvファイル読み込み準備
  11.         $csv = new SplFileObject('/var/dev/php/all.csv', 'r');
  12.         $csv->setFlags(SplFileObject::READ_CSV);
  13.         
  14.         
  15.         // 時間計測開始
  16.         $start = microtime(true);
  17.         
  18.         $rows = [];
  19.         
  20.         foreach ($csv as $row) {
  21.             if ($row === [null]) {
  22.                 break;
  23.             }
  24.             
  25.             $rows[] = [
  26.                 'zip_code' => $row[0],
  27.                 'address' => $row[1],
  28.             ];
  29.             
  30.             if (count($rows) >= 100) {
  31.                 $this->db->insert_batch('zip', $rows);
  32.                 $rows = [];
  33.             }
  34.         }
  35.         
  36.         if (!empty($rows)) {
  37.             $this->db->insert_batch('zip', $rows);
  38.         }
  39.         
  40.         $stop = microtime(true);
  41.         echo sprintf("処理時間:%.3f 秒\n", ($stop - $start));
  42.         
  43.     }
  44. }




結果は40秒程度。
格段に性能が向上しました。


合わせて、一括登録する件数を1,000件、10,000件と増やしてみます。
insert_batchはデフォルトで100件毎に分割してinsertするので、
一括登録する件数を引数で明示してやります。
若干処理を変更しました。


  1. <?php
  2. class Test extends CI_Controller {
  3.     public function index() {
  4.         
  5.         $this->load->database();
  6.         
  7.         // 全件削除
  8.         $this->db->empty_table('zip');
  9.         
  10.         // csvファイル読み込み準備
  11.         $csv = new SplFileObject('/var/dev/php/all.csv', 'r');
  12.         $csv->setFlags(SplFileObject::READ_CSV);
  13.         
  14.         
  15.         // 時間計測開始
  16.         $start = microtime(true);
  17.         
  18.         $rows = [];
  19.         
  20.         foreach ($csv as $row) {
  21.             if ($row === [null]) {
  22.                 break;
  23.             }
  24.             
  25.             $rows[] = [
  26.                 'zip_code' => $row[0],
  27.                 'address' => $row[1],
  28.             ];
  29.         }
  30.         
  31.         // 1,000件区切りでインサート
  32.         $this->db->insert_batch('zip', $rows, null, 1000);
  33.         
  34.         $stop = microtime(true);
  35.         echo sprintf("処理時間:%.3f 秒\n", ($stop - $start));
  36.         
  37.     }
  38. }




1,000件毎インサートで6.7秒
10,000件毎インサートで2.8秒程度でした。
格段に性能が向上しています。





トランザクション



トランザクションをかけて、自動コミットを抑止してみます。


  1. <?php
  2. class Test extends CI_Controller {
  3.     public function index() {
  4.         
  5.         $this->load->database();
  6.         
  7.         // 全件削除
  8.         $this->db->empty_table('zip');
  9.         
  10.         // csvファイル読み込み準備
  11.         $csv = new SplFileObject('/var/dev/php/all.csv', 'r');
  12.         $csv->setFlags(SplFileObject::READ_CSV);
  13.         
  14.         
  15.         // 時間計測開始
  16.         $start = microtime(true);
  17.         
  18.         $rows = [];
  19.         
  20.         foreach ($csv as $row) {
  21.             if ($row === [null]) {
  22.                 break;
  23.             }
  24.             
  25.             $rows[] = [
  26.                 'zip_code' => $row[0],
  27.                 'address' => $row[1],
  28.             ];
  29.         }
  30.         
  31.         // トランザクションをかけて一括更新
  32.         $this->db->trans_start();
  33.         $this->db->insert_batch('zip', $rows, null, 100);
  34.         $this->db->trans_complete();
  35.         
  36.         $stop = microtime(true);
  37.         echo sprintf("処理時間:%.3f 秒\n", ($stop - $start));
  38.         
  39.     }
  40. }




100件毎が4.9秒
1,000件毎が2.4秒
10,000件毎が2.2秒
という結果になりました。





まとめ



結果をまとめると以下のようになります。

5回試行して平均を求めています。(単件登録はあまりに時間がかかるので測定は1回でやめました)
単位はすべて秒です。

内容1回目2回目3回目4回目5回目平均
ループのみ0.3260.3230.3210.3240.3190.323
単件insert4074.385----4074.385
insert_batch(100)41.09640.55241.67940.43940.33640.82
insert_batch(1,000)6.5716.5716.7566.6497.0346.716
insert_batch(10,000)2.6982.9252.8312.6982.7062.772
insert_batch(100)+tran4.7945.0064.8375.0495.0294.943
insert_batch(1,000)+tran2.1432.3632.5342.4342.6032.415
insert_batch(10,000)+tran2.1182.2282.3062.2222.2192.219


・可能な限り1回のbatch_size(登録件数)を増やす。
・ロールバックする気がなくてもトランザクションをかける。

この2つでかなりの高速化が可能です。
関連記事

テーマ:プログラミング - ジャンル:コンピュータ

  1. 2016/08/05(金) 01:12:39|
  2. PHP
  3. | トラックバック:0
  4. | コメント:0
  5. | 編集
<<CodeIgniter 大量データの更新を高速に行う方法 | ホーム | PHP pharに同梱したプログラムにautoloadを適用する>>

コメント

コメントの投稿


管理者にだけ表示を許可する

トラックバック

トラックバック URL
http://symfoware.blog68.fc2.com/tb.php/1907-205f2f0a
この記事にトラックバックする(FC2ブログユーザー)