Лямбда-функция «Ошибка: подключение ETIMEDOUT» при получении слишком большого количества запросов для запроса MySQL

#mysql #node.js #aws-lambda #load-testing #serverless

#mysql #node.js #aws-lambda #нагрузочное тестирование #без сервера

Вопрос:

Я пытаюсь выполнить тестирование моей AWS lambda-функции, которая выполняет запрос к RDS MySQL (t2.medium). Однако, если я запрашиваю API большое количество раз, хотя я могу успешно получить запрос с правильными данными, иногда это приводит к «Ошибке: connect ETIMEDOUT».

Что-то не так в моем коде или настройках?

Я прочитал несколько предложений по настройке некоторых параметров:

MySQL:

wait_timeout 1

max_connections 16000

interactive_timeout 6000

max_allowed_packet 1073741824

Lamdba:

Тайм-аут 60 сек. поместите лямбда-функцию в тот же VPC, что и ваш RDS

Добавлена политика выполнения VPC AWSLambdaVPCAccessExecutionRole

назначьте группу безопасности для лямбда-функции

В системе безопасности, прикрепленной к экземпляру RDS, добавлено входящее правило для mysql

Подтвердите, что лямбда-функция имеет доступ к той же базе данных VPC RDS

Журнал ошибок Lambda

2019-03-28T18:51:47.353Z ab4fbbaf-1ea2-458b-a5b5-781cdfdd80df { Ошибка: подключить время ожидания

при подключении._handleConnectTimeout

(/var/task/node_modules/mysql/lib/Connection.js:411:13)

в Object.onceWrapper (events.js:313:30)

в emitNone (events.js:106:13)

в Socket.emit (events.js:208:7)

в сокете._onTimeout (net.js:420:8)

при ontimeout (timers.js:482:11)

при tryOnTimeout (timers.js:317:5)

в Timer.listOnTimeout (timers.js:277:5)


в Protocol._enqueue (/var/task/node_modules/mysql/lib/protocol/Protocol.js:144:48)

при Protocol.handshake (/var/task/node_modules/mysql/lib/protocol/Protocol.js:51:23)

при подключении.connect (/var/task/node_modules/mysql/lib/Connection.js:118:18)

при подключении._implyConnect (/var/task/node_modules/mysql/lib/Connection.js:453:10)

при подключении.запрос (/var/task/node_modules/mysql/lib/Connection.js:198:8)

в Promise (/var/task/db.js:62:9)

at new Promise ()

at Object.retrieve (/var/task/db.js:55:10)

at exports.getAlerts (/var/task/index.js:59:24)

errorno: ‘ETIMEDOUT’,

code: ‘ETIMEDOUT’,

syscall: ‘connect’,

fatal: true }

RDS Error Log

2019-03-28T18:18:49.378320Z 9500 [Note] Aborted connection 9500 to db:
‘db’ user: ‘user’ host: ‘123.123.123.123’ (Got timeout reading
communication packets)

2019-03-28T18:18:49.392514Z 9498 [Note] Aborted connection 9498 to db:
‘db’ user: ‘user’ host: ‘123.123.123.123’ (Got timeout reading
communication packets)

2019-03-28T18:18:49.470617Z 9499 [Note] Aborted connection 9499 to db:
‘db’ user: ‘user’ host: ‘123.123.123.123’ (Got timeout reading
communication packets)

2019-03-28T18:18:49.636775Z 9501 [Note] Aborted connection 9501 to db:
‘db’ user: ‘user’ host: ‘123.123.123.123’ (Got timeout reading
communication packets)

2019-03-28T18:18:49.694669Z 9502 [Note] Aborted connection 9502 to db:
‘db’ user: ‘user’ host: ‘123.123.123.123’ (Got timeout reading
communication packets)

2019-03-28T18:18:49.803457Z 9503 [Note] Aborted connection 9503 to db:
‘db’ user: ‘user’ host: ‘123.123.123.123’ (Got timeout reading
communication packets)

2019-03-28T18:18:49.824250Z 9504 [Note] Aborted connection 9504 to db:
‘db’ user: ‘user’ host: ‘123.123.123.123’ (Got timeout reading
communication packets)

My db.js query on lambda

 const mysql = require('mysql')
let retrieve = (sql, objectArr, entityName) => {
      return new Promise((resolve, reject) => {
        let con = mysql.createConnection(dbParams)
        con.query(sql, objectArr, (err2, results) => {
          con.end()
          if (err2) {
            console.log(err2)
            return reject(new apiError.DatabaseError('An error occurred in retrieve'))
          }
          console.log('Data retrieve successfully')
          return resolve(results)
        })
      })
    }
  

My test.js script

 const request = require('request')
let errorCount = 0
let success = 0

for (let i = 0; i < 4000; i  ) {
  console.log('Send')
  request.get('https://myapi/users', {
    headers: {
      'client_id': 'app',
      'Content-Type': 'application/json'
    }
  }, (error, response, body) => {
    if (error) {
      console.log('some error')
      errorCount  
    } else {
      let jsonBody = JSON.parse(body)
      if (jsonBody.code === 0) {
        success  
      } else {
        errorCount  
      }
    }

    console.log('Success: ', success)
    console.log('Error: ', errorCount)
  })
}
  

Edit:
I also tried to change the i < 1 in test script, then it always gives me «Error: connect ETIMEDOUT. But with i < 900, it works successfully sometime

index.js

 const db = require('./db.js')
exports.getUsers = async (event, context) => {
  context.callbackWaitsForEmptyEventLoop = false
  try {
    let sql = 'SELECT * FROM User'
    let abc = await db.retrieve(sql, [], 'user')

    let response = {
      statusCode: 200,
      abc: abc,
      code: 0
    }
    return response
  } catch (err) {
    console.log(err)
    errorHandler(err)
  }
}
  

db.js with Pool

 const mysql = require('mysql')
const constants = require('./constants.js')

let dbParams = {
  host: constants.SQL_CONNECTION_HOST,
  user: constants.SQL_CONNECTION_USER,
  password: constants.SQL_CONNECTION_PASSWORD,
  database: constants.SQL_CONNECTION_DATABASE,
  multipleStatements: true,
  maxConnections: 4
}

const pool = mysql.createPool(dbParams)
let retrieve = (sql, objectArr, entityName) => {
  return new Promise((resolve, reject) => {
    pool.getConnection((err1, con) => {
      if (err1) {
        console.log(err1)
        return reject(new apiError.DatabaseError('An error occurred in retrieve pool'))
      }
      console.log('Pool connect successfully')
      con.query(sql, objectArr, (err2, results) => {
        con.end()
        if (err2) {
          console.log(err2)
          return reject(new apiError.DatabaseError('An error occurred in retrieve'))
        }
        console.log('Data retrieve successfully')
        return resolve(results)
      })
    })
  })
}
  

Error Log after used pool

2019-03-28T23:35:24.144Z 91b0fc78-e4d1-4fd9-bdf7-923715b165c0 { Error:
Handshake inactivity timeout

at Handshake.
(/var/task/node_modules/mysql/lib/protocol/Protocol.js:163:17)

at emitNone (events.js:106:13)

at Handshake.emit (events.js:208:7)

при рукопожатии._onTimeout (/var/task/node_modules/mysql/lib/protocol/sequences/Последовательность.js:124:8)

при Timer._onTimeout (/var/task/node_modules/mysql/lib/protocol/Timer.js:32:23)

при ontimeout (timers.js:482:11)

при tryOnTimeout (timers.js:317:5)

в Timer.listOnTimeout (timers.js:277:5)


в Protocol._enqueue (/var/task/node_modules/mysql/lib/protocol/Protocol.js:144:48)

при Protocol.handshake (/var/task/node_modules/mysql/lib/protocol/Protocol.js:51:23)

в PoolConnection.connect (/var/task/node_modules/mysql/lib/Connection.js:118:18)

в Pool.getConnection (/var/task/node_modules/mysql/lib/Pool.js:48:16)

в Promise (/var/task/db.js:72:10)

при новом обещании ()

в Object.retrieve (/var/task/db.js:67:10)

в exports.getAlerts (/var/task/index.js:59:24)

код: ‘PROTOCOL_SEQUENCE_TIMEOUT’,

фатальный: true,

время ожидания: 10000 }

Установите с помощью пула сейчас и протестируйте его с помощью цикла запроса:

i < 100 результат => Успех: 866 и ошибка: 134 запроса.

i < 10 результат => Успех: 8 и ошибка: 2 запроса.

Ошибка с таймаутом бездействия квитирования

db.js с con.createConnection снаружи

 const mysql = require('mysql')
// initialize dbParams
let con = mysql.createConnection(dbParams)

let retrieve = (sql, objectArr, entityName) => {
  return new Promise((resolve, reject) => {
    con.query(sql, objectArr, (err2, results) => {
      //con.end() commet out connection end here 
      if (err2) {
        console.log(err2)
        return reject(new apiError.DatabaseError('An error occurred in retrieve'))
      }
      console.log('Data retrieve successfully')
      return resolve(results)
    })
  })
}
  

Комментарии:

1. Сколько раз это «большое количество раз» и за какой промежуток времени вы это делали? (например, 1000 раз за 60 секунд)

2. SELECT * в большинстве случаев ЭТО НИКОГДА не бывает хорошим выбором, особенно без каких-либо ограничений… Возможно, если вы запрашиваете меньше данных, потребуется меньше времени для их извлечения и тайм-аута после большего количества итераций. Хотя это хороший тест, позволяющий нагрузить всю вашу систему

3. @Zyigh В настоящее время у меня есть только 3 строки данных в таблице User. Я установлю ограничение после

4. @Michael — sqlbot Я установил с пулом сейчас и протестировал его с циклом запросов i < 100. И в результате: 866 запросов и ошибка: 134. Ошибка с таймаутом бездействия квитирования

5. Итак … 1000 попыток за какой период времени? Прочитайте о MySQL thread_cache_size — хотя, вероятно, это не вся проблема, сервер MySQL не может принять неопределенное количество подключений за короткое время из-за накладных расходов на создание потока операционной системы. Кэш потоков позволяет серверу сохранять их для повторного использования.

Ответ №1:

В вопросе не объясняется, как retrieve функция вызывается внутри вашей лямбда-функции.

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

Я предлагаю создать соединение во время инициализации лямбда-кода (он же ‘coldstart’), переместив строку кода let con = mysql.createConnection(dbParams) за пределы любой функции (так, чтобы имело место только одно соединение).

Другим хорошим вариантом является использование пула соединений.

Комментарии:

1. Я пробовал с пулом, это также приводит к тому же поведению с «Ошибка: тайм-аут неактивности квитирования». Я отредактировал с index.js и db.js с пулом в post

2. Поэтому, пожалуйста, попробуйте инициализировать соединение один раз, как предложено

3. Я обновил с помощью let con = mysql.createConnection вне функции, но она также выдает «Ошибка: connect ETIMEDOUT». Я предоставил скрипт для редактирования

4. Спасибо. Я обнаружил проблему, я назначил дополнительную подсеть VPC и зону доступности, в которую сервер не входит. Подсеть. Теперь я изменил свою лямбда-функцию только для подсети базы данных, и она работает без проблем с 1000 подключениями