Lambda SQL Утечка соединения RDS с сервером - PullRequest
2 голосов
/ 07 августа 2020

Проблема

Я использую mssql v6.2.0 в Lambda, которая часто вызывается (постоянно ~ 25 одновременных вызовов при стандартной нагрузке).

Кажется, у меня проблемы с пулом соединений или чем-то в этом роде, потому что у меня по-прежнему тонны открытых соединений с БД, которые перегружают мою базу данных (SQL Сервер на RDS), в результате чего лямбды просто теряют время ожидания результатов запроса.

Я прочитал документы, различные похожие вопросы, проблемы с Github и т. д. c. но ничего не помогло с этой конкретной проблемой. разделяется между вызовами в одном контейнере. Это заставляет меня думать, что я должен видеть всего несколько соединений для каждого контейнера, в котором работает моя Lambda, но я не знаю, сколько их, поэтому это трудно проверить. Суть в том, что пул должен удерживать меня от тонны и тонны открытых соединений, поэтому что-то работает неправильно.

Есть несколько разных способов использования mssql, и я пробовал несколько из них. Примечательно, что я пытался указать максимальный размер пула как с большими, так и с маленькими значениями, но получил те же результаты. AWS рекомендует вам проверить, существует ли уже пул, прежде чем пытаться создать новый. Я пробовал это безрезультатно. Это было что-то вроде pool = pool || await createPool() Я знаю, что прокси-сервер RDS существует для помощи в подобных ситуациях, но, похоже, он не предлагается (в настоящее время) для SQL экземпляров сервера. У меня есть возможность немного замедлить мои данные, но это немного влияет на производительность продукта в целом, поэтому я не хочу делать это только для того, чтобы избежать решения проблемы с подключениями к БД. Если флажок не установлен, я увидел до 700 подключений к БД одновременно, что заставило меня подумать, что есть какая-то утечка, и это, возможно, не просто разумный результат большого использования. I не нашел способа сократить TTL для подключений на стороне сервера SQL, как рекомендовано этим слайдом re: Invent. Возможно, это часть ответа? enter image description here

Code

'use strict';

/* Dependencies */
const sql = require('mssql');
const fs = require('fs').promises;
const path = require('path');
const AWS = require('aws-sdk');
const GeoJSON = require('geojson');

AWS.config.update({ region: 'us-east-1' });
var iotdata = new AWS.IotData({ endpoint: process.env['IotEndpoint'] });

/* Export */

exports.handler = async function (event) {

    let myVal= event.Records[0].Sns.Message;

    // Gather prerequisites in parallel
    let [
        query1,
        query2,
        pool
    ] = await Promise.all([
        fs.readFile(path.join(__dirname, 'query1.sql'), 'utf8'),
        fs.readFile(path.join(__dirname, 'query2.sql'), 'utf8'),
        sql.connect(process.env['connectionString'])
    ]);

    // Query DB for updated data
    let results = await pool.request()
        .input('MyCol', sql.TYPES.VarChar, myVal)
        .query(query1);

    // Prepare IoT Core message
    let params = {
        topic: `${process.env['MyTopic']}/${results.recordset[0].TopicName}`,
        payload: convertToGeoJsonString(results.recordset),
        qos: 0
    };

    // Publish results to MQTT topic
    try {
        await iotdata.publish(params).promise();
        console.log(`Successfully published update for ${myVal}`);

        //Query 2
        await pool.request()
            .input('MyCol1', sql.TYPES.Float, results.recordset[0]['Foo'])
            .input('MyCol2', sql.TYPES.Float, results.recordset[0]['Bar'])
            .input('MyCol3', sql.TYPES.VarChar, results.recordset[0]['Baz'])
            .query(query2);
        
    } catch (err) {
        console.log(err);
    }
};

/**
 * Convert query results to GeoJSON for API response
 * @param {Array|Object} data - The query results
 */
function convertToGeoJsonString(data) {
    let result = GeoJSON.parse(data, { Point: ['Latitude', 'Longitude']});
    return JSON.stringify(result);
}

Вопрос

Пожалуйста, помогите мне понять, почему у меня появляются неконтролируемые соединения и как это исправить. Для бонусных баллов: какова идеальная стратегия для обработки высокого уровня параллелизма БД на Lambda?

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

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

Сопутствующие материалы

Ответы [ 2 ]

1 голос
/ 10 августа 2020

Ответ

Я наконец нашел ответ после 4 дней усилий. Все, что мне нужно было сделать, это увеличить масштаб БД. Код действительно хорош как есть.

Я перешел с db.t2.micro на db.t3.small (или с 1 виртуального ЦП, 1 ГБ ОЗУ до 2 виртуальных ЦП и 2 ГБ ОЗУ) по цене net примерно 15 долларов в месяц. .

Теория

В моем случае БД, вероятно, не смогла справиться с обработкой (которая включает в себя несколько географических c вычислений) для всех моих вызовов одновременно. Я видел CPU go up, но я предположил, что это результат высоких открытых соединений. Когда запросы замедляются, количество одновременных вызовов накапливается, поскольку Lambdas начинают ждать результатов, в конечном итоге заставляя их истекать время ожидания и не закрывать свои соединения должным образом.

Сравнения:

db.t2. micro:

  • 200 + соединений с БД (постоянно увеличивается, если оставить его включенным)
  • 50 + одновременные вызовы
  • 5000 + длительность лямбда-выражения при замедлении , ~ 300 мсек без нагрузки

db.t3.small:

  • 25-35 соединений с БД (постоянно)
  • ~ 5 одновременных вызовов
  • ~ 33 мс Длительность лямбда <- в десять раз быстрее! </li>

Панель управления CloudWatch

Панель управления CloudWatch

Резюме

Я думаю, что эта проблема сбивала меня с толку, потому что она не пахла проблемой емкости. Почти каждый раз, когда я имел дело с большим количеством подключений к БД в прошлом, это была ошибка кода. Попробовав там варианты, я подумал, что это «какая-то магическая проблема бессерверности», которую мне нужно понять. В конце концов, это было так же просто, как изменить уровни БД. Мой вывод состоит в том, что проблемы с емкостью БД могут проявляться не только в высокой загрузке ЦП и памяти, но и в других случаях, и что высокие соединения могут быть результатом чего-то помимо ошибки кода.

0 голосов
/ 07 августа 2020

Таким образом, пул соединений вообще бесполезен для Lambda, что вы можете сделать, это повторно использовать соединения.

Проблема в том, что каждое выполнение Lambda открывает пул, он просто наводняет БД, как вы, вы хотите 1 соединение для каждого лямбда-контейнера, вы можете использовать такой класс db (это грубо, но, если у вас есть вопросы, знайте)

    export default class MySQL {

    constructor() {

        this.connection = null
    }

    async getConnection() {

        if (this.connection === null || this.connection.state === 'disconnected') {

            return this.createConnection()
        }

        return this.connection


    }

    async createConnection() {

        this.connection = await mysql.createConnection({
            host: process.env.dbHost,
            user: process.env.dbUser,
            password: process.env.dbPassword,
            database: process.env.database,
        })


        return this.connection
    }

    async query(sql, params) {

        await this.getConnection()

        let err
        let rows
        [err, rows] = await to(this.connection.query(sql, params))

        if (err) {

            console.log(err)
            return false
        }

        return rows
    }

}

function to(promise) {
    return promise.then((data) => {
        return [null, data]
    }).catch(err => [err])
}

Что вам нужно понять, это A выполнение лямбда - это небольшая виртуальная машина, которая выполняет задачу, а затем останавливается, она сидит там некоторое время, и если кому-то еще она нужна, она повторно используется вместе с контейнером и соединением для одной задачи там никогда не используйте несколько соединений с одной лямбдой.

Надеюсь, это поможет, дайте мне знать, если вам понадобится дополнительная информация! О, и добро пожаловать в stackoverflow, это хорошо сформулированный вопрос.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...