Làm sao để nhập 100,000 dòng vào Redis bằng NodeJS?
Đây chính thức là những dòng code NodeJS đầu tiên mà mình tự tay viết, không kể đến project trên công ty
Yêu cầu: làm sao để chạy một lượng lớn Redis command, bằng NodeJS, mà không bị lỗi (timeout, quá tải,v.v…)?
Trong ví dụ này mình sử dụng thư viện ioredis để kết nối tới server Redis. Server Redis sử dụng cluster.
Vấn đề: một cách ngây thơ, mình đã gọi hàm redis.set(key, value)
100,000 lần, kết quả là process node bị treo, ăn 100% CPU và sau đó bị lỗi tràn bộ nhớ – trong khi đó bên phía Redis cũng chỉ ghi lại được một lượng rất nhỏ record mà mình yêu cầu (~vài trăm).
Giải pháp: chia nhỏ số lượng yêu cầu ra, chỉ thực hiện mỗi lần một ít (100-1,000 lệnh/lần), tập lệnh này xong rồi mới gửi tập lệnh tiếp theo. Có vẻ lâu hơn, nhưng chắc ăn hơn. ioredis mặc định đã hỗ trợ bài toán này, chính là sử dụng cơ chế pipeline để gửi số lượng lớn lệnh cùng lúc đến server Redis, sau đó nhận kết quả ở hàm callback và thực thi tiếp tập lệnh tiếp theo.
Lưu ý: code dưới đây được viết kiểu self-invoking anonymous function để có thể chạy ngay trên giao diện command line (CLI).
(function(){ var Redis = require('ioredis'); const REDIS_NODES = [{ port: 7001, host: '192.168.0.18' }, { port: 7002, host: '192.168.0.18' }, { port: 7003, host: '192.168.0.18' }, { port: 7004, host: '192.168.0.18' }, { port: 7005, host: '192.168.0.18' }, { port: 7006, host: '192.168.0.18' }]; const REDIS_PASSWORD = ''; var redis = new Redis.Cluster(REDIS_NODES , { redisOptions: { password: REDIS_PASSWORD }, retryDelayOnFailover: 5000, retryDelayOnClusterDown: 5000, retryDelayOnTryAgain: 5000 }); redis.on('ready', function() { console.log('REDIS IS READY') switch(process.argv[2]) { case 'gen': return generateData(process.argv[3]); case 'del': return deleteData(process.argv[3]); } console.log('You did not call any command.') return process.exit(); }); redis.on('error', function(err){ console.log("REDIS CONNECTION ERROR ", err); return process.exit(); }); function execQueries(keyName, cmd, total, offset, limit) { var arr = [] for (var i = 0; i < limit; i++) { var q = [cmd, '{' + keyName + '}' + (offset+i).toString()] if (cmd === 'set') q.push('dummy data') arr.push(q) } console.log('Executing queries from', offset , 'to', offset+limit) return redis.pipeline(arr).exec().then(function(result) { if (total <= offset + limit) { console.log('Done.') return process.exit(); } return execQueries(keyName, cmd, total, offset + limit, limit); }); } var keyName = 'DUMMY'; var maxQuery = 1000; function generateData(count) { if (!count) count = 100 console.log('BEGIN GENERATING', count, 'RECORDS') execQueries(keyName, 'set', count, 0, maxQuery); } function deleteData(count) { if (!count) count = 100 console.log('BEGIN DELETING', count, 'RECORDS') execQueries(keyName, 'del', count, 0, maxQuery); } })();