View
275
Download
8
Category
Preview:
DESCRIPTION
Sogni di sviluppare il tuo SaaS, di poterlo gestire, curare, evolvere. Speri di attrarre nuovi utenti con funzionalità innovative, di offrire un servizio veloce e puntuale. Finalmente ti puoi concentrare sulla qualità del tuo prodotto. Poi una mattina ti svegli, hai un database da 50GB, modificare una colonna richiede 8 ore e ti ritrovi in trappola. Gioie e dolori delle applicazioni multi-tenant. In questo talk analizzeremo perché e come abbiamo suddiviso il database di un SaaS da circa 1 milione di utenti. > Vedremo come aggiungere un parametro di selezione del db a tutti i comandi della console, come eseguire comandi in parallelo per ridurre i tempi di manutenzione, come aggiungere info di debug utilizzando gli eventi del framework, come gestire il caricamento delle fixtures, quali idee si sono rivelate vincenti e quali no.
Citation preview
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?
Thanks
Recommended