0

I'm using Symfony 4 to interface with an existing Master/Slave MySQL setup and am executing queries against the server using raw sql. Raw SQL is the only option at the moment.

I'm using show full processlist; on the DB server to monitor which DB is used, and I am only seeing connections to the master server. It doesn't appear that any of the slaves are ever used.

For reference, I have two dbal connections setup, the default is NOT master/slave, and uses orm mapping. The second is the master/slave which I'm having issues with, and this is the server I'm executing raw sql queries against.

Below is my doctrine.yml:

doctrine:
    dbal:
        default_connection: default
        connections:
            default:
                driver:   pdo_mysql
                host:     "%env(DATABASE_HOST)%"
                dbname:   "db1"
                user:     "%env(DATABASE_USER)%"
                password: "%env(DATABASE_PASS)%"
                charset:  UTF8
            ds:
                driver:   pdo_mysql
                host:     "%env(DS_DATABASE_HOST)%"
                dbname:   "db2"
                user:     "%env(DS_DATABASE_USER)%"
                password: "%env(DS_DATABASE_PASS)%"
                slaves:
                    slave1:
                        host:     "%env(DS_DATABASE_SLAVE1_HOST)%"
                        user:     "%env(DS_DATABASE_USER)%"
                        password: "%env(DS_DATABASE_PASS)%"
                        dbname:   "db2"
                    slave2:
                        host:     "%env(DS_DATABASE_SLAVE2_HOST)%"
                        user:     "%env(DS_DATABASE_USER)%"
                        password: "%env(DS_DATABASE_PASS)%"
                        dbname:   "db2"

orm:
    default_entity_manager: default
    entity_managers:
        default:
            connection: default
            mappings:
                Main:
                    is_bundle: false
                    type: annotation
                    dir: '%kernel.project_dir%/src/Entity/Main'
                    prefix: 'App\Entity\Main'
                    alias: Main
        ds:
            connection: ds

I have configured my entity managers in my services.yml as follows:

# Entity managers
App\Service\Database\MainEntityManager:
    arguments:
        $wrapped: '@doctrine.orm.default_entity_manager'
App\Service\Database\DSEntityManager:
    arguments:
        $wrapped: '@doctrine.orm.ds_entity_manager'

The entity manager (in this case DSEntityManager) is injected into the constructor of a class, then the query is executed as such:

    $result = $this->em->getConnection()->prepare($sql);
    $result->execute($args);

Please let me know if I'm missing any helpful configuration.

Thanks a lot for the help.

Dbl0McJim
  • 196
  • 1
  • 13
  • 1
    No need for a ds entity manager, you can inject the connection object directly and use it. In fact there is a [wrapper_class attribute](https://symfony.com/doc/master/bundles/DoctrineBundle/configuration.html#doctrine-dbal-configuration) so you can typehint against it. Never used the master/slave stuff so it's doubtful that injecting the connection directly will actually help. – Cerad Jun 20 '18 at 18:43

1 Answers1

-1

Thanks @Cerad for the tip, that got me in the correct direction. Since I was no longer trying to use an entity manager for raw queries that were not mapped to entities, I could work with the connection directly.

I Created a wrapper class which extended MasterSlaveConnection. That worked as long as I was using executeQuery(). Per the docs, that must be used to query the slaves. However, my query required the use of prepare() and query() which both force the master connection.

So inside my new wrapper class I created two new methods, prepareSlave() and querySlave() which do the same as the original; however, they do $this->connect('slave'); instead of $this->connect('master');

Now all my read queries hit slave and everything else hits master.

So here are the following updates I've made to the configuration above to achieve this:

doctrine.yml

        ds:
            driver:   pdo_mysql
            host:     "%env(DS_DATABASE_HOST)%"
            dbname:   "db2"
            user:     "%env(DS_DATABASE_USER)%"
            password: "%env(DS_DATABASE_PASS)%"
            wrapper_class: "%env(DS_DATABASE_PASS)%"
            slaves: App\Service\Database\DSWrapper
                slave1: ...

services.yml

# DBAL connections
App\Service\Database\DSWrapper: '@doctrine.dbal.ds_connection'

My new wrapper class

class DSWrapper extends MasterSlaveConnection
{
public function prepareSlave($statement)
{
    $this->connect('slave');

    try {
        $stmt = new Statement($statement, $this);
    } catch (\Exception $ex) {
        throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $statement);
    }

    $stmt->setFetchMode($this->defaultFetchMode);

    return $stmt;
}

public function querySlave()
{
    $this->connect('slave');

    $args = func_get_args();

    $logger = $this->getConfiguration()->getSQLLogger();
    if ($logger) {
        $logger->startQuery($args[0]);
    }

    $statement = $this->_conn->query(...$args);

    if ($logger) {
        $logger->stopQuery();
    }

    return $statement;
}
}

So now if I need to execute a query which would normally require the use of prepare() and query(), I instead use prepareSlave() and querySlave().

Dbl0McJim
  • 196
  • 1
  • 13