MattBoothDev
MattBoothDev

Reputation: 1324

Symfony 4, Doctrine and AWS RDS read replica usage

I'm about to embark on using RDS with a master read/write and slave read-only setup.

I've read about the Doctrine MasterSlaveConnection type.

However, there will be some endpoints I create that I would like to use the read-only replica (most of my GET endpoints) and some will need to write data (PUT, PATCH, DELETE endpoints).

I'm also using API Platform.

What is the best way of achieving this in Symfony 4 and Doctrine 2?

Upvotes: 3

Views: 6647

Answers (3)

zed
zed

Reputation: 3267

You actually don't need to setup multiple entity managers, nor is it preferable as handling one entity with multiple entity managers is hard.

Using Doctrine 2.2, you can setup slaves/replicas directly from configuration without needing an extra entity manager:

See the config reference here: https://www.doctrine-project.org/projects/doctrine-bundle/en/2.2/configuration.html#configuration-overview

Example:

doctrine:
    dbal:
        default_connection: default
        connections:
            default:
                dbname: '%env(DATABASE_DBNAME)%'
                user: '%env(DATABASE_USER)%'
                password: '%env(DATABASE_PASSWORD)%'
                host: '%env(DATABASE_HOST)%'
                driver: 'pdo_mysql'
                server_version: '5.7'
                charset: utf8mb4
                default_table_options:
                    charset: utf8mb4
                    collate: utf8mb4_unicode_ci
                slaves:
                    ro_replica:
                        dbname: '%env(REPLICA_DBNAME)%'
                        user: '%env(REPLICA_USER)%'
                        password: '%env(REPLICA_PASSWORD)%'
                        host: '%env(REPLICA_HOST)%'
                        charset: utf8mb4

Upvotes: 6

Strabek
Strabek

Reputation: 2511

Thank you @Chase for the solution. You have made my day. Although it works for me in 'dev' environment I had a problem when switched to 'prod'. I was getting an error that an Entity can not be found. The solution came from this post - thanks @xabbuh. Basically I had to add default_entity_manager: name_of_default_em to doctrine.yml. Here is the copy of the code:

# config/packages/prod/doctrine.yaml
doctrine:
    orm:
        default_entity_manager: BOE <- add this line to let know prod about default em
        auto_generate_proxy_classes: false
        metadata_cache_driver:
            type: service
            id: doctrine.system_cache_provider
        query_cache_driver:
            type: service
            id: doctrine.system_cache_provider
        result_cache_driver:
            type: service
            id: doctrine.result_cache_provider

# ...

Upvotes: 1

Chase
Chase

Reputation: 9362

What I have done in the past is to just use different connections.

Something like:

doctrine:
  dbal:
    default_connection: default
    connections:
      default:
        # This is your Master
        url: '%env(DATABASE_URL)%'
        driver: 'pdo_mysql'
        server_version: '5.7'
        charset: utf8mb4
      slave:
        # This would be the slave
        url: '%env(DATABASE_SLAVE_URL)%'
        driver: 'pdo_mysql'
        server_version: '5.7'
        charset: utf8mb4

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

https://symfony.com/doc/current/doctrine/multiple_entity_managers.html

Then in your controllers or business logic you can either choose to use the default entity manager:

// Controller
$this->getDoctrine()->getEntityManager();

Or you can get the slave connection:

// Controller
$this->getDoctrine()->getEntityManager('slave');

If you need this to work just on all requests without having to create special actions for everything then your best bet is to decorate the Collection and Item DataProviders for doctrine.

https://symfony.com/doc/current/service_container/service_decoration.html

https://github.com/api-platform/core/blob/master/src/Bridge/Doctrine/Orm/CollectionDataProvider.php

https://github.com/api-platform/core/blob/master/src/Bridge/Doctrine/Orm/ItemDataProvider.php

So basically you need to change what manager is chosen based on the $opperationName something like:

if($opperationName === 'GET'){
    $manager = $this->managerRegistry->getManager('slave');
} else {
    $manager = $this->managerRegistry->getManager();
}

Upvotes: 6

Related Questions