Производительность сети Node.js сильно страдает при нагрузочном тестировании на AWS

У меня есть следующий код Node.js, написанный как очень простой HTTP-сервер. Цель состоит в том, чтобы глотать большое количество запросов, содержащих данные base64, и записывать эти данные в S3 как изображение. Аспект S3-записи работает фантастически и не имеет проблем. Тем не менее, первоначальный запрос, похоже, требует чрезмерно длительного времени под нагрузкой.

server.js

http.createServer(function(req, res){
 if (url.parse(req.url).pathname == '/processimage' && req.method.toLowerCase() == 'post') {
 var startTime = new Date();
 var rawBody = '';
 req.on('data', function(chunk) { 
 rawBody += chunk;
 });

 req.on('end', function() {
 console.log('REQUEST FINISHED: ' + (new Date() - startTime) + ' ms');
 // Process image, upload to S3
 res.writeHead(200);
 res.end('data');
 }
 return;
 } else {
 // Other requests
 }
}).listen(1347);

Я также занимаюсь секцией обработки изображений, но он отлично работает и не имеет отношения к этому вопросу.

Чтобы проверить это, я написал сценарий, который POSTs тестирует данные, содержащие base64 размером приблизительно 500 тыс. Символов (оригинальные изображения 2-3 МБ). При тестировании локально все работает нормально. Мой выход:

REQUEST FINISHED: 9ms
REQUEST FINISHED: 23ms
REQUEST FINISHED: 18ms

и т.п.

Однако после развертывания кода в AWS в экземпляре x-large я вижу следующее:

REQUEST FINISHED: 499ms
REQUEST FINISHED: 2493ms
REQUEST FINISHED: 1784ms
REQUEST FINISHED: 3440ms
REQUEST FINISHED: 994ms
REQUEST FINISHED: 36043ms

По сути, при стресс-тестировании это примерно 1 из каждых 30 запросов, кажется, занимает 10+ секунд (даже 30+ секунды в некоторых случаях), чтобы пройти через конвейер запросов. Как вы можете видеть в моем коде, перед данными вычисляется нулевая обработка, поэтому это означает, что где-то между "req.on(" data ") и" req.on("end"), происходит массовая задержка.

Мой вопрос: есть ли какая-то обработка, происходящая между req.on('data') и req.on('end'), которая заставит этот POST занять так много времени? Возможно ли, что хост-машина по некоторым причинам задыхается от этих запросов (Ubuntu 12.04, x-large instance, 14-гигабайтная память, 4-кратные процессоры)?

1 ответ

Скорее всего, это займет некоторое время, чтобы увидеть, что происходит. У вас есть несколько запросов, каждый из которых имеет большие данные, которые будут обрабатываться несколькими кусками. Я бы сказал, что первое, что нужно сделать, - это регистрировать точно, когда обрабатывается каждый кусок от каждого запроса, и тогда вы можете понять, что происходит в порядке вещей и посмотреть, куда это ведет. Вот идея первого прохода при регистрации:

// helper function
logDelta(id, start, msg) {
 var delta = new Date() - start;
 console.log(id + ": (" + delta + ") - " + msg);
}

var reqCntr = 0;
http.createServer(function(req, res){
 if (url.parse(req.url).pathname == '/processimage' && req.method.toLowerCase() == 'post') {
 // place an id on the request for logging purposes
 req.trackerID = reqCntr++;
 console.log(req.trackerID + ": Begin Request");

 var startTime = new Date();
 var rawBody = '';
 var chunkCntr = 0;
 req.on('data', function(chunk) { 
 logDelta(req.trackerID, startTime, "chunk(" + chunkCntr + "), length = " + chunk.length);
 ++chunkCntr;
 rawBody += chunk;
 });

 req.on('end', function() {
 logDelta(req.trackerID, startTime, "Request Finished");
 // Process image, upload to S3
 res.writeHead(200);
 res.end('data');
 }
 return;
 } else {
 // Other requests
 }
}).listen(1347);

Затем вам, возможно, придется выполнить некоторую обработку данных журнала, чтобы иметь возможность следить за временем каждого события в каждом отдельном запросе, особенно в длинных. Это, скорее всего, даст вам подсказку о том, где искать дальше.

FYI, в стеке есть куча разных мест, где вы можете столкнуться с узким местом. Если, например, вы запускаете данные на сервере быстрее, чем его можно обрабатывать (либо на уровне ОС, либо на уровне узла), тогда буферы TCP будут заполняться в какой-то момент, а входящие пакеты будут удалены или сокет будет помещен в какой-то контроль потока.

Если вы работаете на общем сервере, у вас может не быть доступа ко всему TCP-буфере.

Здесь схема, которая собирала бы всю историю для каждого соединения, а затем выводила бы ее в журнал сразу (облегчая рассечение одного соединения, но скрывая последовательность событий между различными соединениями).

var reqCntr = 0;
http.createServer(function(req, res){
 var log = [];
 var id;
 var startTime = new Date();

 // helper function
 logDelta(msg) {
 var delta = new Date() - startTime;
 log.push(id + ": (" + delta + ") - " + msg);
 }

 if (url.parse(req.url).pathname == '/processimage' && req.method.toLowerCase() == 'post') {
 // place an id on the request for logging purposes
 id = reqCntr++;
 log.push(id + ": Begin Request");

 var rawBody = '';
 var chunkCntr = 0;
 req.on('data', function(chunk) { 
 logDelta("chunk(" + chunkCntr + "), length = " + chunk.length);
 ++chunkCntr;
 rawBody += chunk;
 });

 req.on('end', function() {
 // dump connection history to console.log()
 logDelta("Request Finished");
 console.log(log.join("/n"));
 // Process image, upload to S3
 res.writeHead(200);
 res.end('data');
 }
 return;
 } else {
 // Other requests
 }
}).listen(1347);

licensed under cc by-sa 3.0 with attribution.