SlideShare a Scribd company logo
SaaS con Symfony2 
un caso *molto* concreto di applicazione multitenant
@ftassi 
Francesco Tassi 
@matteomoretti85 
Matteo Moretti
SaaS con Symfony2
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
Applicazioni multi 
tenant 
“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)”. 
– wikipedia
Sharding
Sharding 
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. 
– wikipedia
Sharding 
user_id username 
1 idiopathic 
2 bouffant 
3 skedaddle 
4 tweezers 
5 igloo 
6 foibles 
7 oocephalus
Sharding 
user_id username 
1 idiopathic 
2 bouffant 
3 skedaddle 
4 tweezers 
5 igloo 
6 foibles 
7 oocephalus
Sharding 
user_id username 
1 idiopathic 
2 bouffant 
3 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
Supporto nativo 
http://en.wikipedia.org/wiki/ 
Shard_(database_architecture)#Support_for_sh 
ards
Sharding con Doctrine
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. 
- http://doctrine-dbal.readthedocs.org/en/latest/reference/sharding.html
Sharding con Doctrine 
At the moment there are no functionalities yet to 
dynamically pick a shard based on ID, query or 
database row yet 
- 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.
SaaS con Symfony2
https://www.flickr.com/photos/reallyboring/3234624436 
Il Piano
Il Piano 
Strategia di frazionamento 
Strategia di selezione del DB 
Switch della connessione 
Tool di gestione per N databases
Il Piano 
Strategia di frazionamento 
Strategia di selezione del DB 
Switch della connessione 
Tool di gestione per N databases
Il Piano 
Strategia di frazionamento 
Strategia di selezione del DB 
Switch della connessione 
Tool di gestione per N databases
Il Piano 
Strategia di frazionamento 
Strategia di selezione del DB 
Switch della connessione 
Tool di gestione per N databases
Il Piano 
Strategia di frazionamento 
Strategia di selezione del DB 
Switch della connessione 
Tool di gestione per N databases
SaaS con Symfony2
Strategia di 
frazionamento
KEEP CALM 
AND 
SPLIT YOUR DATA
Strategia di frazionamento 
user_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 
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
Una connessione “default” 
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
500 Shards 
shards: 
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
UUID 
user_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-cd4a3d449c46 
4 1 tweezers 
cd5759ae-7a7e 
-42d1- 
b4cf-0cd0701b 
5 2 igloo 
64976e7a-54d2 
-4230-a8ef-d624dc320cee 
6 1 foibles 
202528c0-7028 
-4a6f-9c0b-e97c6544693c 
7 1 oocephalus 
30bd250c-0a9c 
-4cf2- 
a54c-020804d1
Sincronizzazione 
(onFlush, prePersist) 
$this 
->eventDispatcher 
->dispatch( 
MultiDbSyncEntityEvent::SYNC_UTENTE, 
new MultiDbSyncEntityEvent($utente) 
);
Sincronizzazione 
public function onSyncUtente(MultiDbSyncEntityEvent 
$event) 
{ 
$user = $event->getEntity(); 
$shard = $this->getShardToSync($user); 
/** @var $connection DoctrineDBALConnection */ 
$connection = $this->doctrine->getConnection($this- 
>getConnectionNameFromShard($shard)); 
$this->syncUser($user, $connection); 
}
500 Connessioni 
nuvolamc12345678: 
driver: '%database_driver%' 
host: '%database_host%' 
port: '%database_port%' 
dbname: nuvolamc12345678 
user: '%database_user%' 
password: '%database_password%' 
charset: UTF8 
nuvolamcps015006: 
driver: '%database_driver%' 
host: '%database_host%' 
port: '%database_port%' 
dbname: nuvolamcps015006 
user: '%database_user%' 
password: '%database_password%' 
charset: UTF8
Switch della 
connessione
Switch della connessione 
private function selectDbForIstituto( 
Istituto $istituto, 
SessionInterface $session 
) 
{ 
$shardManager = $this->get('shard_manager'); 
$shardManager->selectShard($istituto); 
! 
$session->set('shard', $istituto->getShardId()); 
}
Switch della connessione 
public function onKernelRequest(GetResponseEvent $event) 
{ 
if (!$event->isMasterRequest()) { 
return; 
} 
! 
$this->shardManager->selectShard( 
$this->session->get(‘shard') 
); 
}
https://www.flickr.com/photos/jdhancock/8671399450/ 
Gestire 500 DB 
Help needed
Configurare gli shards 
protected 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 shards 
protected 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 shard 
public function onConsoleCommand(ConsoleCommandEvent $event) 
{ 
$shardManager = new SafeShardManager($connection); 
$istituto = $input->getParameterOption(['--istituto', '-i']); 
! 
if ('global' === $istituto) { 
$shardManager->selectGlobal(); 
} else { 
$shardManager->selectShard($istituto); 
} 
! 
}
Ciclare gli shards 
class 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 FTW 
class 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 FTW 
protected 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~mig 
rate0 -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

More Related Content

SaaS con Symfony2

  • 1. SaaS con Symfony2 un caso *molto* concreto di applicazione multitenant
  • 2. @ftassi Francesco Tassi @matteomoretti85 Matteo Moretti
  • 4. Nuvola e i suoi 50GB
  • 5. Nuvola e i suoi 50GB • Difficoltà di manutenzione (backup/ripristino) • Difficoltà di evoluzione (alter dello schema) • Impossibile replicare il sistema (debug)
  • 7. Applicazioni multi tenant “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)”. – wikipedia
  • 9. Sharding 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. – wikipedia
  • 10. Sharding user_id username 1 idiopathic 2 bouffant 3 skedaddle 4 tweezers 5 igloo 6 foibles 7 oocephalus
  • 11. Sharding user_id username 1 idiopathic 2 bouffant 3 skedaddle 4 tweezers 5 igloo 6 foibles 7 oocephalus
  • 12. Sharding user_id username 1 idiopathic 2 bouffant 3 skedaddle user_id username 4 tweezers 5 igloo 6 foibles 7 oocephalus Shard 1 Shard 2
  • 13. Vantaggi • Suddivide anche il carico di scrittura • Indici più piccoli • distribuzione dei dati migliore
  • 14. Svantaggi • Difficile o impossibile effettuare query su shard differenti • Consistenza dei dati • Complessità extra
  • 15. Supporto nativo http://en.wikipedia.org/wiki/ Shard_(database_architecture)#Support_for_sh ards
  • 17. 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. - http://doctrine-dbal.readthedocs.org/en/latest/reference/sharding.html
  • 18. Sharding con Doctrine At the moment there are no functionalities yet to dynamically pick a shard based on ID, query or database row yet - http://doctrine-dbal.readthedocs.org/en/latest/reference/sharding.html
  • 19. 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.
  • 22. Il Piano Strategia di frazionamento Strategia di selezione del DB Switch della connessione Tool di gestione per N databases
  • 23. Il Piano Strategia di frazionamento Strategia di selezione del DB Switch della connessione Tool di gestione per N databases
  • 24. Il Piano Strategia di frazionamento Strategia di selezione del DB Switch della connessione Tool di gestione per N databases
  • 25. Il Piano Strategia di frazionamento Strategia di selezione del DB Switch della connessione Tool di gestione per N databases
  • 26. Il Piano Strategia di frazionamento Strategia di selezione del DB Switch della connessione Tool di gestione per N databases
  • 29. KEEP CALM AND SPLIT YOUR DATA
  • 30. Strategia di frazionamento user_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
  • 31. Strategia di frazionamento user_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
  • 32. 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
  • 37. Chiedilo all’utente • Login tramite database unico (default) ! • Selezione manuale dell’istituto • Switch della connessione • Sincronizzazione dei dati duplicati
  • 38. Una connessione “default” 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
  • 39. 500 Shards shards: 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
  • 40. UUID user_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-cd4a3d449c46 4 1 tweezers cd5759ae-7a7e -42d1- b4cf-0cd0701b 5 2 igloo 64976e7a-54d2 -4230-a8ef-d624dc320cee 6 1 foibles 202528c0-7028 -4a6f-9c0b-e97c6544693c 7 1 oocephalus 30bd250c-0a9c -4cf2- a54c-020804d1
  • 41. Sincronizzazione (onFlush, prePersist) $this ->eventDispatcher ->dispatch( MultiDbSyncEntityEvent::SYNC_UTENTE, new MultiDbSyncEntityEvent($utente) );
  • 42. Sincronizzazione public function onSyncUtente(MultiDbSyncEntityEvent $event) { $user = $event->getEntity(); $shard = $this->getShardToSync($user); /** @var $connection DoctrineDBALConnection */ $connection = $this->doctrine->getConnection($this- >getConnectionNameFromShard($shard)); $this->syncUser($user, $connection); }
  • 43. 500 Connessioni nuvolamc12345678: driver: '%database_driver%' host: '%database_host%' port: '%database_port%' dbname: nuvolamc12345678 user: '%database_user%' password: '%database_password%' charset: UTF8 nuvolamcps015006: driver: '%database_driver%' host: '%database_host%' port: '%database_port%' dbname: nuvolamcps015006 user: '%database_user%' password: '%database_password%' charset: UTF8
  • 45. Switch della connessione private function selectDbForIstituto( Istituto $istituto, SessionInterface $session ) { $shardManager = $this->get('shard_manager'); $shardManager->selectShard($istituto); ! $session->set('shard', $istituto->getShardId()); }
  • 46. Switch della connessione public function onKernelRequest(GetResponseEvent $event) { if (!$event->isMasterRequest()) { return; } ! $this->shardManager->selectShard( $this->session->get(‘shard') ); }
  • 48. Configurare gli shards protected 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'); }
  • 49. Configurare gli shards protected 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 }
  • 50. Ad ognuno il suo shard public function onConsoleCommand(ConsoleCommandEvent $event) { $shardManager = new SafeShardManager($connection); $istituto = $input->getParameterOption(['--istituto', '-i']); ! if ('global' === $istituto) { $shardManager->selectGlobal(); } else { $shardManager->selectShard($istituto); } ! }
  • 51. Ciclare gli shards class 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)' ); } }
  • 52. Ciclare gli shards app/console nu:sha:li | while read shard; do app/console doctrine:mig:mig - i $shard -n;done;
  • 53. Parallelizzare FTW class 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(); } }
  • 54. Parallelizzare FTW protected 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; }
  • 55. Parallelizzare FTW app/console gearman:job:execute NuvolaMultiDbBundleWorkerShardWorker~mig rate0 -n —env=prod
  • 57. Fa al caso tuo?