2015-03-17 19 views
9

Tôi vừa bắt đầu chơi xung quanh với node.js với postgres, sử dụng nút-postgres. Một trong những điều tôi cố gắng làm là viết một js ngắn để điền vào cơ sở dữ liệu của tôi, sử dụng một tệp với khoảng 200.000 mục nhập.node-postgres với số lượng truy vấn lớn

Tôi nhận thấy rằng sau một thời gian (dưới 10 giây), tôi bắt đầu nhận được "Lỗi: Kết nối bị chấm dứt". Tôi không chắc liệu đây có phải là vấn đề với cách tôi sử dụng các nút postgres hay không, hoặc nếu đó là vì tôi đang gửi thư rác.

Dù sao, đây là một mã đơn giản cho thấy hành vi này:

var pg = require('pg'); 
var connectionString = "postgres://xxxx:[email protected]/xxxx"; 

pg.connect(connectionString, function(err,client,done){ 
    if(err) { 
    return console.error('could not connect to postgres', err); 
    } 

    client.query("DROP TABLE IF EXISTS testDB"); 
    client.query("CREATE TABLE IF NOT EXISTS testDB (id int, first int, second int)"); 
    done(); 

    for (i = 0; i < 1000000; i++){ 
    client.query("INSERT INTO testDB VALUES (" + i.toString() + "," + (1000000-i).toString() + "," + (-i).toString() + ")", function(err,result){ 
     if (err) { 
     return console.error('Error inserting query', err); 
     } 
     done(); 
    }); 
    } 
}); 

Nó thất bại sau khoảng 18,000-20,000 truy vấn. Đây có phải là cách sai để sử dụng client.query không? Tôi đã thử thay đổi số máy khách mặc định, nhưng dường như nó không giúp ích gì.

client.connect() dường như không giúp được gì, nhưng đó là vì tôi có quá nhiều khách hàng, vì vậy tôi chắc chắn nghĩ rằng việc gộp nhóm khách hàng là cách để đi.

Cảm ơn bạn đã trợ giúp!

Trả lời

13

CẬP NHẬT

Câu trả lời này đã được kể từ thay thế với bài viết này: Data Imports, đại diện cho cách tiếp cận up-to-date nhất.


Để sao chép kịch bản của bạn tôi đã sử dụng pg-promise thư viện, và tôi có thể xác nhận rằng cố gắng nó đầu-on sẽ không bao giờ làm việc, cho dù thư viện bạn sử dụng, nó là cách tiếp cận những vấn đề.

Dưới đây là một phương pháp biến đổi mà chúng tôi phân vùng chèn vào khối và sau đó thực hiện từng đoạn trong một giao dịch, mà là cân bằng tải (aka throttling):

function insertRecords(N) { 
    return db.tx(function (ctx) { 
     var queries = []; 
     for (var i = 1; i <= N; i++) { 
      queries.push(ctx.none('insert into test(name) values($1)', 'name-' + i)); 
     } 
     return promise.all(queries); 
    }); 
} 
function insertAll(idx) { 
    if (!idx) { 
     idx = 0; 
    } 
    return insertRecords(100000) 
     .then(function() { 
      if (idx >= 9) { 
       return promise.resolve('SUCCESS'); 
      } else { 
       return insertAll(++idx); 
      } 
     }, function (reason) { 
      return promise.reject(reason); 
     }); 
} 
insertAll() 
    .then(function (data) { 
     console.log(data); 
    }, function (reason) { 
     console.log(reason); 
    }) 
    .done(function() { 
     pgp.end(); 
    }); 

này được sản xuất 1000.000 hồ sơ trong khoảng 4 phút, làm chậm đáng kể sau 3 giao dịch đầu tiên. Tôi đã sử dụng Node JS 0.10.38 (64 bit), tiêu thụ khoảng 340MB bộ nhớ. Bằng cách này, chúng tôi đã chèn 100.000 bản ghi, 10 lần liên tiếp.

Nếu chúng ta làm như vậy, chỉ lần này chèn 10.000 bản ghi trong 100 giao dịch, cùng 1.000.000 bản ghi được thêm vào chỉ trong 1m25, không chậm lại, với Node JS tiêu thụ khoảng 100MB bộ nhớ, cho chúng ta biết rằng phân vùng dữ liệu như Đây là một ý tưởng rất tốt.

Nó không quan trọng mà thư viện bạn sử dụng, phương pháp này nên được như vậy:

  1. Partition/ga chèn của bạn thành nhiều giao dịch;
  2. Giữ danh sách các lần chèn trong một giao dịch duy nhất với khoảng 10.000 hồ sơ;
  3. Thực hiện tất cả các giao dịch của bạn trong một chuỗi đồng bộ.
  4. Kết nối bản phát hành trở lại hồ bơi sau mỗi COMMIT của giao dịch.

Nếu bạn vi phạm bất kỳ quy tắc nào trong số đó, bạn sẽ được đảm bảo gặp sự cố. Ví dụ, nếu bạn phá vỡ quy tắc 3, quá trình Node JS của bạn có khả năng chạy hết bộ nhớ thật nhanh và đưa ra một lỗi. Quy tắc 4 trong ví dụ của tôi được cung cấp bởi thư viện.

Và nếu bạn làm theo mẫu này, bạn không cần phải gặp rắc rối với cài đặt nhóm kết nối.

UPDATE 1

phiên bản sau của pg-promise hỗ trợ các tình huống như vậy hoàn hảo, như hình dưới đây:

function factory(index) { 
    if (index < 1000000) { 
     return this.query('insert into test(name) values($1)', 'name-' + index); 
    } 
} 

db.tx(function() { 
    return this.batch([ 
     this.none('drop table if exists test'), 
     this.none('create table test(id serial, name text)'), 
     this.sequence(factory), // key method 
     this.one('select count(*) from test') 
    ]); 
}) 
    .then(function (data) { 
     console.log("COUNT:", data[3].count); 
    }) 
    .catch(function (error) { 
     console.log("ERROR:", error); 
    }); 

và nếu bạn không muốn để bao gồm bất cứ điều gì thêm, như tạo bảng, sau đó nó trông thậm chí đơn giản hơn:

function factory(index) { 
    if (index < 1000000) { 
     return this.query('insert into test(name) values($1)', 'name-' + index); 
    } 
} 

db.tx(function() { 
    return this.sequence(factory); 
}) 
    .then(function (data) { 
     // success; 
    }) 
    .catch(function (error) { 
     // error; 
    }); 

Xem Synchronous Transactions để biết chi tiết.

Sử dụng thư viện hứa hẹn, ví dụ: phải mất 1m43s trên máy sản xuất của tôi để chèn 1.000.000 bản ghi (không có dấu vết ngăn xếp dài được bật).

Bạn sẽ chỉ có các yêu cầu trả lại phương thức factory theo index, cho đến khi bạn không còn gì, đơn giản như vậy.

Và phần tốt nhất, điều này không chỉ nhanh, mà còn tạo ra rất ít tải trên quy trình NodeJS của bạn. Quá trình kiểm tra bộ nhớ vẫn dưới 60MB trong toàn bộ thử nghiệm, chỉ tốn 7-8% thời gian CPU.

CẬP NHẬT 2

Bắt đầu với phiên bản 1.7.2, pg-promise hỗ trợ giao dịch siêu lớn một cách dễ dàng. Xem chương Synchronous Transactions.

Ví dụ: tôi có thể chèn 10.000.000 bản ghi trong một giao dịch duy nhất chỉ trong 15 phút trên máy tính ở nhà, với Windows 8.1 64 bit.

Để kiểm tra, tôi đặt PC ở chế độ sản xuất và sử dụng Bluebird làm thư viện lời hứa. Trong quá trình thử nghiệm, mức tiêu thụ bộ nhớ không vượt quá 75MB cho toàn bộ quá trình NodeJS 0.12.5 (64-bit), trong khi i7-4770 CPU của tôi cho thấy tải 15% phù hợp.

Chèn bản ghi 100 triệu theo cùng một cách sẽ đòi hỏi sự kiên nhẫn hơn, nhưng không cần nhiều tài nguyên máy tính hơn.

Trong thời gian chờ đợi, thử nghiệm trước đó cho chèn 1 m giảm từ 1m43 xuống còn 1m31.

CẬP NHẬT 3

Các yếu tố sau có thể làm cho một sự khác biệt rất lớn: Performance Boost.

CẬP NHẬT 4

câu hỏi liên quan, với một ví dụ thực hiện tốt hơn: Massive inserts with pg-promise.

CẬP NHẬT 5

Một ví dụ tốt hơn và mới hơn có thể được tìm thấy ở đây: nodeJS inserting Data into PostgreSQL error

+2

Cảm ơn. Tôi đồng ý rằng đạo đức của câu chuyện là "đừng làm theo cách đó". FWIW Tôi đã kết thúc bằng cách sử dụng postgres COPY FROM. –

+0

am i phải giả định rằng 'this.sequence (factory), this.one ('select count (*) from test')' sẽ không cung cấp cho tôi số đếm chính xác vì tôi không chắc chắn chuỗi được viết hoàn toàn khi bắt đầu truy vấn đếm? – stephanlindauer

+0

@stephanlindauer khi trình tự giải quyết, nó giải quyết với một đối tượng '{total, duration}', do đó, tổng số là số lượng các phần tử được xử lý. –

2

Tôi đoán rằng bạn đang đạt đến kích thước hồ bơi tối đa. Kể từ khi client.query là không đồng bộ, prolly tất cả các kết nối có sẵn được sử dụng trước khi chúng được trả lại.

kích thước mặc định Pool là 10. Kiểm tra ở đây: https://github.com/brianc/node-postgres/blob/master/lib/defaults.js#L27

Bạn có thể tăng kích thước hồ bơi mặc định bằng cách thiết lập pg.defaults.poolSize:

pg.defaults.poolSize = 20; 

Cập nhật: Thực hiện truy vấn khác sau khi giải phóng kết nối.

var pg = require('pg'); 
var connectionString = "postgres://xxxx:[email protected]/xxxx"; 
var MAX_POOL_SIZE = 25; 

pg.defaults.poolSize = MAX_POOL_SIZE; 
pg.connect(connectionString, function(err,client,done){ 
    if(err) { 
    return console.error('could not connect to postgres', err); 
    } 

    var release = function() { 
    done(); 
    i++; 
    if(i < 1000000) 
     insertQ(); 
    }; 

    var insertQ = function() { 
    client.query("INSERT INTO testDB VALUES (" + i.toString() + "," + (1000000-i).toString() + "," + (-i).toString() + ")",  function(err,result){ 
     if (err) { 
     return console.error('Error inserting query', err); 
     } 
     release(); 
    }); 
    }; 

    client.query("DROP TABLE IF EXISTS testDB"); 
    client.query("CREATE TABLE IF NOT EXISTS testDB (id int, first int, second int)"); 
    done(); 

    for (i = 0; i < MAX_POOL_SIZE; i++){ 
    insertQ(); 
    } 
}); 

Ý tưởng cơ bản là vì bạn đang enqueuing một số lượng lớn các truy vấn với kích thước hồ bơi tương đối nhỏ, bạn đang đạt đến kích thước hồ bơi tối đa. Ở đây chúng tôi thực hiện truy vấn mới chỉ sau khi kết nối hiện có được giải phóng.

+0

Cảm ơn. Tôi đã thay đổi nó thành 25, nhưng nó không giúp mã ban đầu của tôi. Chưa thử nó với cái này. Tôi gọi nó là 'số máy khách mặc định' ... oops ... hoặc là cái gì khác? –

+0

@DanielSutantyo: cách thay đổi 'số máy khách mặc định'? –

+0

Vì vậy, tôi đã thử cả pg.defaults.poolsize = 25; (thậm chí có thể nhiều hơn) và pg.defaults.poolIdleTimeout = 60000; Người đầu tiên chắc chắn tăng số lượng mục nhập mà tôi có thể đưa vào, nhưng không nhiều. –

Các vấn đề liên quan