Соединение SQLAlchemy зависает при перезагрузке AWS MySQL RDS с отработкой отказа

У нас есть сервер Python, который использует SQLAlchemy для чтения/записи данных из экземпляра AWS MySQL MultiAZ RDS.

Мы сталкиваемся с поведением, которого мы хотели бы избежать, когда всякий раз, когда мы запускаем аварийную перезагрузку, соединение, которое уже было открыто, а затем выдает оператор, зависает на неопределенный срок. Хотя согласно документации AWS этого следует ожидать, мы ожидаем, что коннектор Python MySQL сможет справиться с этой ситуацией.

Ближайший случай, который мы нашли в Интернете, — это тема групп Google. в котором говорится о проблеме и предлагается решение, касающееся Postgres RDS.

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

from datetime import datetime
from time import time, sleep
from sqlalchemy import Column, Integer, String, create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.orm.scoping import scoped_session
from sqlalchemy.ext.declarative import declarative_base

import logging

current_milli_time = lambda: int(round(time() * 1000))
Base = declarative_base()

logging.basicConfig(format='%(asctime)s %(filename)s %(lineno)s %(process)d %(levelname)s: %(message)s', level="INFO")

class Message(Base):
    __tablename__ = 'message'
    id = Column(Integer, primary_key=True)
    body = Column(String(450), nullable=False)

engine = create_engine('mysql://<username>:<password>@<db_host>/<db_name>',echo=False, pool_recycle=1800,)
session_maker = scoped_session(sessionmaker(bind=engine, autocommit=False, autoflush=False))
session = session_maker()

while True:
    try:
        ids = ''
        start = current_milli_time()
        for msg in session.query(Message).order_by(Message.id.desc()).limit(5):
            ids += str(msg.id) + ', '
            logging.info('({!s}) (took {!s} ms) fetched ids: {!s}'.format(datetime.now().time().isoformat(), current_milli_time() - start, ids))

        start = current_milli_time()
        m = Message()
        m.body = 'some text'
        session.add(m)
        session.commit()
        logging.info('({!s}) (took {!s} ms) inserted new message'.format(datetime.now().time().isoformat(), current_milli_time() - start))

    except Exception, e:
        logging.exception(e)
        session.rollback()
    finally:
        session_maker.remove()

    sleep(0.25)

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

Наш вопрос: кто-нибудь сталкивался с этой проблемой или есть возможные направления, которые стоит проверить?


person wilfo    schedule 04.04.2016    source источник
comment
Это определенно не должно быть бесшовным для клиентов. Из документов: При принудительном аварийном переключении базы данных Например, Amazon RDS автоматически переключается на резервную реплику в другой зоне доступности и обновляет запись DNS для экземпляра БД, чтобы она указывала на резервный экземпляр БД. В результате вам нужно будет очистить и восстановить все существующие подключения к вашему экземпляру БД.   -  person univerio    schedule 04.04.2016
comment
Вы совершенно правы, мне интересно, есть ли у коннектора Python DB способ определения этой ситуации.   -  person wilfo    schedule 04.04.2016
comment
Как вы устанавливаете таймауты соединения? Если проблема связана с существующими подключениями, похоже, что удаленный хост не отправляет пакет FIN при завершении работы, что вызывает беспокойство. Обычно, когда вы корректно завершаете работу хоста, приложения корректно закрывают соединения с открытыми сокетами. Соединение не должно зависать, если какой-либо маршрутизатор в середине не отбрасывает пакеты TCP RST.   -  person univerio    schedule 05.04.2016
comment
Прежде всего, я отредактировал вопрос, чтобы отразить ваш комментарий о документации AWS. О тайм-аутах, которые, похоже, не действуют, если отработка отказа происходит после открытия соединения. Я пытался найти способы воздействия на тайм-ауты сокетов специально через SQLAlchemy, но вышел коротким   -  person wilfo    schedule 05.04.2016
comment
Можете ли вы показать конкретный код того, как вы устанавливаете тайм-аут?   -  person univerio    schedule 05.04.2016
comment
обязательно: engine = create_engine('mysql://<username>:<password>@<db_host>/<db_name>', echo=False, pool_recycle=1800, connect_args = {'connect_timeout': <x_seconds>}) или engine = create_engine('mysql://<username>:<password>@<db_host>/<db_name>?connect_timeout=<x_seconds>', echo=False, pool_recycle=1800)   -  person wilfo    schedule 05.04.2016
comment
Имейте в виду, что существует множество соединителей MySQL для SQLAlchemy, вы никогда не знаете, какой из них действительно работает для условий переключения тайм-аута docs.sqlalchemy.org/en/latest/dialects/   -  person mootmoot    schedule 05.04.2016
comment
Спасибо, что указали на это, пока я оставляю это, так как усилия кажутся большими для возвращения. Спасибо!   -  person wilfo    schedule 07.04.2016


Ответы (1)


ИМХО, использование времени ожидания соединителя SQL для обработки переключателя похоже на черную магию. Каждый разъем всегда действует по-разному и его трудно диагностировать.

Если вы снова прочитаете комментарий @univerio, AWS переназначит новый IP-адрес для ТОГО ЖЕ имени конечной точки RDS. Во время переключения имя вашей конечной точки RDS и старые IP-адреса все еще находятся в кэше DNS вашего экземпляра сервера. Итак, это проблемы с кэшированием DNS, и поэтому AWS просит вас «очистить…».

Если вы не перезапустите SQLAlchemy, чтобы снова прочитать DNS, сеанс не сможет узнать, что что-то происходит, и динамически переключать его. И что еще хуже, проблема может возникнуть в коннекторе, используемом SQLAlchemy.

ИМХО, не стоит заморачиваться с переключением внутри кода. Я просто подпишусь на сервис AWS, такой как лямбда, который может реагировать на события переключения, запускать сервер приложений для перезапуска соединения, которое должно отражать новый IP-адрес.

person mootmoot    schedule 05.04.2016