SaaS con Symfony2

62
SaaS con Symfony2 un caso *molto* concreto di applicazione multitenant

Transcript of SaaS con Symfony2

SaaS con Symfony2un caso *molto* concreto di applicazione multitenant

@ftassi Francesco Tassi

@matteomoretti85 Matteo Moretti

Nuvola e i suoi 50GB

Nuvola e i suoi 50GB• Difficoltà di manutenzione (backup/ripristino)

• Difficoltà di evoluzione (alter dello schema)

• Impossibile replicare il sistema (debug)

Applicazioni multi tenant

– wikipedia

“Multi-tenant si riferisce ad una architettura software in cui una singola istanza del suddetto

software gira su un server ed è utilizzata da più di una client organization (tenant)”.

Applicazioni multi tenant

Sharding

– wikipedia

A database shard is a horizontal partition of data in a database. Each individual partition is referred

to as a shard or database shard. Each shard is held on a separate database server instance, to

spread load.

Sharding

Shardinguser_id username

1 idiopathic

2 bouffant

3 skedaddle

4 tweezers

5 igloo

6 foibles

7 oocephalus

Shardinguser_id username

1 idiopathic

2 bouffant

3 skedaddle

4 tweezers

5 igloo

6 foibles

7 oocephalus

Shardinguser_id username

1 idiopathic2 bouffant3 skedaddle

user_id username

4 tweezers

5 igloo

6 foibles

7 oocephalus

Shard 1

Shard 2

Vantaggi• Suddivide anche il carico di scrittura

• Indici più piccoli

• distribuzione dei dati migliore

Svantaggi• Difficile o impossibile effettuare query su shard

differenti

• Consistenza dei dati

• Complessità extra

http://en.wikipedia.org/wiki/Shard_(database_architecture)#Support_for_sh

ards

Supporto nativo

Sharding con Doctrine

Starting with 2.3 Doctrine DBAL contains some functionality to simplify the development of

horizontally sharded applications. !

In this first release it contains a ShardManager interface. This interface allows to programatically

select a shard to send queries to.

Sharding con Doctrine

- http://doctrine-dbal.readthedocs.org/en/latest/reference/sharding.html

At the moment there are no functionalities yet to dynamically pick a shard based on ID, query or

database row yet

Sharding con Doctrine

- http://doctrine-dbal.readthedocs.org/en/latest/reference/sharding.html

ShardManager Interface$shardManager = new PoolingShardManager($conn);!$currentCustomerId = 1234;$shardManager->selectShard($currentCustomerId);// all queries after this call hit the shard// where customer with id 1234 is on.!$shardManager->selectGlobal();// the global database is selected.

Il Pianohttps://www.flickr.com/photos/reallyboring/3234624436

Il PianoStrategia di frazionamento

Strategia di selezione del DB

Switch della connessione

Tool di gestione per N databases

Il PianoStrategia di frazionamento

Strategia di selezione del DB

Switch della connessione

Tool di gestione per N databases

Il PianoStrategia di frazionamento

Strategia di selezione del DB

Switch della connessione

Tool di gestione per N databases

Il PianoStrategia di frazionamento

Strategia di selezione del DB

Switch della connessione

Tool di gestione per N databases

Il PianoStrategia di frazionamento

Strategia di selezione del DB

Switch della connessione

Tool di gestione per N databases

Strategia di frazionamento

KEEP CALMAND

SPLIT YOUR DATA

Strategia di frazionamentouser_id istituto_id! username

1 1 idiopathic

2 2 bouffant

3 1 skedaddle

4 1 tweezers

5 2 igloo

6 1 foibles

7 1 oocephalus

Strategia di frazionamentouser_id istituto_id! username

1 1 idiopathic

2 2 bouffant

3 1 skedaddle

4 1 tweezers

5 2 igloo

6 1 foibles

7 1 oocephalus

Strategia di frazionamento

user_id istituto_id! username

1 1 idiopathic

3 1 skedaddle

4 1 tweezers

6 1 foibles

7 1 oocephalus

user_id istituto_id! username

2 2 bouffant

5 2 igloo

Shard 1

Shard 2

Strategia di selezione del DB

Sottodominio?

Sottodominio

Chiedilo all’utente

Chiedilo all’utente• Login tramite database unico (default) !

• Selezione manuale dell’istituto

• Switch della connessione

• Sincronizzazione dei dati duplicati

doctrine: dbal: default_connection: default connections: default: driver: "%database_driver%" host: "%database_host%" port: "%database_port%" dbname: "%database_name%" user: "%database_user%" password: "%database_password%" charset: UTF8

Una connessione “default”

500 Shardsshards: mc12345678: id: 1 host: '%database_host%' user: '%database_user%' password: '%database_password%' dbname: nuvolamc12345678 charset: UTF8 mcps015006: id: 2 host: '%database_host%' user: '%database_user%' password: '%database_password%' dbname: mcps015006 charset: UTF8

UUIDuser_id istituto_id! username uuid

1 1 idiopathic e5f0b536-c4cd-47c4-

a810-2 2 bouffant ea5d2eb4-851c

-462d-a25e-1756bece

3 1 skedaddle a5889369-61d8-4b3c-b93f-

cd4a3d449c464 1 tweezers cd5759ae-7a7e

-42d1-b4cf-0cd0701b

5 2 igloo 64976e7a-54d2-4230-a8ef-

d624dc320cee6 1 foibles 202528c0-7028

-4a6f-9c0b-e97c6544693c

7 1 oocephalus 30bd250c-0a9c-4cf2-

a54c-020804d1

Sincronizzazione$this ->eventDispatcher ->dispatch( MultiDbSyncEntityEvent::SYNC_UTENTE, new MultiDbSyncEntityEvent($utente) );

(onFlush, prePersist)

Sincronizzazionepublic function onSyncUtente(MultiDbSyncEntityEvent $event){ $user = $event->getEntity(); $shard = $this->getShardToSync($user); /** @var $connection \Doctrine\DBAL\Connection */ $connection = $this->doctrine->getConnection($this->getConnectionNameFromShard($shard)); $this->syncUser($user, $connection);}

500 Connessioninuvolamc12345678: driver: '%database_driver%' host: '%database_host%' port: '%database_port%' dbname: nuvolamc12345678 user: '%database_user%' password: '%database_password%' charset: UTF8nuvolamcps015006: driver: '%database_driver%' host: '%database_host%' port: '%database_port%' dbname: nuvolamcps015006 user: '%database_user%' password: '%database_password%' charset: UTF8

Switch della connessione

Switch della connessioneprivate function selectDbForIstituto( Istituto $istituto, SessionInterface $session){ $shardManager = $this->get('shard_manager'); $shardManager->selectShard($istituto);! $session->set('shard', $istituto->getShardId());}

Switch della connessionepublic function onKernelRequest(GetResponseEvent $event){ if (!$event->isMasterRequest()) { return; }! $this->shardManager->selectShard( $this->session->get(‘shard') );}

Gestire 500 DBHelp needed

https://www.flickr.com/photos/jdhancock/8671399450/

Configurare gli shardsprotected function configure(){ $this->setName('nuvola:shard:add-config') ->setDescription('Aggiunge la configurazione necessaria ad uno shard') ->addOption('host', null, InputOption::VALUE_OPTIONAL, 'L\'host della connessione al db') ->addOption('codiceMeccanografico', null, InputOption::VALUE_OPTIONAL, 'Codice meccanografico per lo shard');}

Configurare gli shardsprotected function configure(){ $this->setName('nuvola:shard:create-config') ->setDescription('Crea il file di configurazione per gli shards') ->addOption( 'append', null, InputOption::VALUE_NONE, 'Se impostato a false cancella la configurazione attuale, altrimenit la aggiunge. Default a true' ) //CUT}

Ad ognuno il suo shardpublic function onConsoleCommand(ConsoleCommandEvent $event){ $shardManager = new SafeShardManager($connection); $istituto = $input->getParameterOption(['--istituto', '-i']);! if ('global' === $istituto) { $shardManager->selectGlobal(); } else { $shardManager->selectShard($istituto); }!}

Ciclare gli shardsclass ListShardsCommand extends AbstractShardCommand{ protected function configure() { $this->setName('nuvola:shard:list-shards') ->setDescription('Restituisce l\'elenco degli shard configurati') ->addOption( 'letteraInizioIntervallo', null, InputOption::VALUE_OPTIONAL, 'Lettera di inizio intervallo per lo shard da esportare (estremo compreso)' ) ->addOption( 'letteraFineIntervallo', null, InputOption::VALUE_OPTIONAL, 'Lettera di fine intervallo per lo shard da esportare (estremo compreso)' ); } }

Ciclare gli shards

app/console nu:sha:li | while read shard; do app/console doctrine:mig:mig -i $shard -n;done;

Parallelizzare FTWclass MigrateCommand extends AbstractParallelCommand{ protected function execute(InputInterface $input, OutputInterface $output) { /** @var GearmanClient $gearman */ $gearman = $this->getContainer()->get('gearman'); //[CUT] foreach ($shards as $shard) { $job = 'NuvolaMultiDbBundleWorkerShardWorker~migrate' . $shard['queue']; $gearman->addTask($job, $shard['shard']); }! $gearman->runTasks(); }}

Parallelizzare FTWprotected function doMigrate(\GearmanJob $job){ $shard = $job->workload(); $command = sprintf( 'app/console doctrine:migrations:migrate -n -i %s --env=%s', $shard, $this->env );! $process = $this->runProcess($job, $command);! if (!$process->isSuccessful()) { $this->sendErrorsToJob($job, $process, $command, 'Errore migrando ' . $shard); return; }! $success = [sprintf('Migrazione per %s completata', $shard)]; $job->sendComplete(serialize($success));! return;}

Parallelizzare FTW

app/console gearman:job:execute NuvolaMultiDbBundleWorkerShardWorker~migrate0 -n —env=prod

Conclusioni

Fa al caso tuo?

Si, lo rifarei

Domande?

https://joind.in/12212

Hiring!

http://www.ideato.it/offerta-cercasi-sviluppatore-php/

Thanks