Задать вопрос
@dinya17

Как работать с подключениями к БД в долгоживущих приложениях с EntityManager в RoadRunner?

Есть приложение на Symfony 7.3.1, запустил его под RoadRunner и вот встал вопрос - как теперь сделать так чтобы подключение к БД постоянно проверялось и происходило переподключение при разрыве связи?
Мои поиски привели меня вот к этому
https://github.com/pixelfederation/doctrine-resett...
Но когда я начал его использовать, то ощущение, что оно не работает (я запускал дебагер и смотрел что используется).
В общем вопрос - как работать с подключениями к БД в долгоживущих приложениях.
Как сбрасывать EM чтобы не жрало память?
  • Вопрос задан
  • 82 просмотра
Подписаться 2 Средний Комментировать
Пригласить эксперта
Ответы на вопрос 1
Тут нам надо просто подойти логически к вопросу. Понимаем, что RoadRunner держит приложение Symfony в памяти. Значит, в памяти будет находиться и сервис-контейнер со всеми инициализированными сервисами. Поэтому дефолтное подключение для Doctrine не подойдёт, потому что соединение упадёт, и оно будет выдавать ошибку.

Т.е. нам надо написать свою обёртку вокруг доктриновского класса, предоставляющего соединение с базой данных. И в этой обёртке мы и сделаем логику псевдо-пула соединений. А именно, перед каждым запросом будем пинговать соединение, и если оно мертво, то пробовать реконнектиться и продолжать запросы далее в случае успешного реконнекта. Это самое простое, но не самое эффективное решение.

1. Надеюсь, у вас уже установлен бандл для работы с Roadrunner

composer require spiral/roadrunner-bundle

2. Создаём вот эту вот обёртку над соединением

<?php
// src/DBAL/ConnectionWrapper.php

namespace App\DBAL;

use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Driver;
use Doctrine\DBAL\Configuration;
use Doctrine\Common\EventManager;

class ConnectionWrapper extends Connection
{
    /**
     * @var int
     */
    private $reconnectAttempts = 3;

    public function __construct(array $params, Driver $driver, ?Configuration $config = null, ?EventManager $eventManager = null)
    {
        parent::__construct($params, $driver, $config, $eventManager);
    }

    public function prepare($sql)
    {
        $this->ping();
        return parent::prepare($sql);
    }

    public function executeQuery(string $sql, array $params = [], $types = [], ?QueryCacheProfile $qcp = null)
    {
        $this->ping();
        return parent::executeQuery($sql, $params, $types, $qcp);
    }

    public function executeUpdate(string $sql, array $params = [], array $types = [])
    {
        $this->ping();
        return parent::executeUpdate($sql, $params, $types);
    }

    public function ping(): bool
    {
        if ($this->isConnected() && $this->getWrappedConnection()->ping()) {
            return true;
        }

        $this->close();

        for ($i = 0; $i < $this->reconnectAttempts; $i++) {
            try {
                $this->connect();
                if ($this->getWrappedConnection()->ping()) {
                    return true;
                }
            } catch (\Exception $e) {
                // ещё разок
            }
        }

        throw new \RuntimeException("Unable to reconnect to the database.");
    }
}


3. Конфигурируем Доктрину, чтобы использовала нашу обёртку.

# config/packages/doctrine.yaml
doctrine:
    dbal:
        # обычные настройки
        url: '%env(resolve:DATABASE_URL)%'
        driver: 'pdo_mysql'
        server_version: '8.0'
        charset: utf8mb4
        default_table_options:
            charset: utf8mb4
            collate: utf8mb4_unicode_ci

        # наша обёртка
        wrapper_class: App\DBAL\ConnectionWrapper


4. Что ещё

  • Обязательно очищайте EntityManager вручную после каждого запроса по API, или каждого большого действия, или что там у вас ещё. Иначе доктриновская Identity Map распухнет, и память будет течь как мороженное в пустыне.
  • Не забывайте аккуратно обращаться с транзакциями и всегда их коммитить или откатывать.
  • Продумайте, как лучше всего рестартовать сервис, если реконнект всё-таки не сработает.


5. Лучше, конечно, сделать всё через воркеры RoadRunner.

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

composer require spiral/roadrunner-symfony
composer require spiral/roadrunner-doctrine


Но идея та же, нужно написать обёртку для Доктрины, плюс использовать воркер RoadRunner. Там будет уже более адекватный connection pool и т.д.
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы