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

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

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.

Transcript of SaaS con Symfony2 un caso *molto* concreto di applicazione multitenant

Page 1: SaaS con Symfony2 un caso *molto* concreto di applicazione multitenant

SaaS con Symfony2un caso *molto* concreto di applicazione multitenant

Page 2: SaaS con Symfony2 un caso *molto* concreto di applicazione multitenant

@ftassi Francesco Tassi

@matteomoretti85 Matteo Moretti

Page 3: SaaS con Symfony2 un caso *molto* concreto di applicazione multitenant
Page 4: SaaS con Symfony2 un caso *molto* concreto di applicazione multitenant

Nuvola e i suoi 50GB

Page 5: SaaS con Symfony2 un caso *molto* concreto di applicazione multitenant

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

• Difficoltà di evoluzione (alter dello schema)

• Impossibile replicare il sistema (debug)

Page 6: SaaS con Symfony2 un caso *molto* concreto di applicazione multitenant

Applicazioni multi tenant

Page 7: SaaS con Symfony2 un caso *molto* concreto di applicazione multitenant

– 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

Page 8: SaaS con Symfony2 un caso *molto* concreto di applicazione multitenant

Sharding

Page 9: SaaS con Symfony2 un caso *molto* concreto di applicazione multitenant

– 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

Page 10: SaaS con Symfony2 un caso *molto* concreto di applicazione multitenant

Shardinguser_id username

1 idiopathic

2 bouffant

3 skedaddle

4 tweezers

5 igloo

6 foibles

7 oocephalus

Page 11: SaaS con Symfony2 un caso *molto* concreto di applicazione multitenant

Shardinguser_id username

1 idiopathic

2 bouffant

3 skedaddle

4 tweezers

5 igloo

6 foibles

7 oocephalus

Page 12: SaaS con Symfony2 un caso *molto* concreto di applicazione multitenant

Shardinguser_id username

1 idiopathic2 bouffant3 skedaddle

user_id username

4 tweezers

5 igloo

6 foibles

7 oocephalus

Shard 1

Shard 2

Page 13: SaaS con Symfony2 un caso *molto* concreto di applicazione multitenant

Vantaggi• Suddivide anche il carico di scrittura

• Indici più piccoli

• distribuzione dei dati migliore

Page 14: SaaS con Symfony2 un caso *molto* concreto di applicazione multitenant

Svantaggi• Difficile o impossibile effettuare query su shard

differenti

• Consistenza dei dati

• Complessità extra

Page 15: SaaS con Symfony2 un caso *molto* concreto di applicazione multitenant

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

ards

Supporto nativo

Page 16: SaaS con Symfony2 un caso *molto* concreto di applicazione multitenant

Sharding con Doctrine

Page 17: SaaS con Symfony2 un caso *molto* concreto di applicazione multitenant

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

Page 18: SaaS con Symfony2 un caso *molto* concreto di applicazione multitenant

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

Page 19: SaaS con Symfony2 un caso *molto* concreto di applicazione multitenant

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.

Page 20: SaaS con Symfony2 un caso *molto* concreto di applicazione multitenant
Page 21: SaaS con Symfony2 un caso *molto* concreto di applicazione multitenant

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

Page 22: SaaS con Symfony2 un caso *molto* concreto di applicazione multitenant

Il PianoStrategia di frazionamento

Strategia di selezione del DB

Switch della connessione

Tool di gestione per N databases

Page 23: SaaS con Symfony2 un caso *molto* concreto di applicazione multitenant

Il PianoStrategia di frazionamento

Strategia di selezione del DB

Switch della connessione

Tool di gestione per N databases

Page 24: SaaS con Symfony2 un caso *molto* concreto di applicazione multitenant

Il PianoStrategia di frazionamento

Strategia di selezione del DB

Switch della connessione

Tool di gestione per N databases

Page 25: SaaS con Symfony2 un caso *molto* concreto di applicazione multitenant

Il PianoStrategia di frazionamento

Strategia di selezione del DB

Switch della connessione

Tool di gestione per N databases

Page 26: SaaS con Symfony2 un caso *molto* concreto di applicazione multitenant

Il PianoStrategia di frazionamento

Strategia di selezione del DB

Switch della connessione

Tool di gestione per N databases

Page 27: SaaS con Symfony2 un caso *molto* concreto di applicazione multitenant
Page 28: SaaS con Symfony2 un caso *molto* concreto di applicazione multitenant

Strategia di frazionamento

Page 29: SaaS con Symfony2 un caso *molto* concreto di applicazione multitenant

KEEP CALMAND

SPLIT YOUR DATA

Page 30: SaaS con Symfony2 un caso *molto* concreto di applicazione multitenant

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

Page 31: SaaS con Symfony2 un caso *molto* concreto di applicazione multitenant

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

Page 32: SaaS con Symfony2 un caso *molto* concreto di applicazione multitenant

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

Page 33: SaaS con Symfony2 un caso *molto* concreto di applicazione multitenant

Strategia di selezione del DB

Page 34: SaaS con Symfony2 un caso *molto* concreto di applicazione multitenant

Sottodominio?

Page 35: SaaS con Symfony2 un caso *molto* concreto di applicazione multitenant

Sottodominio

Page 36: SaaS con Symfony2 un caso *molto* concreto di applicazione multitenant

Chiedilo all’utente

Page 37: SaaS con Symfony2 un caso *molto* concreto di applicazione multitenant

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

• Selezione manuale dell’istituto

• Switch della connessione

• Sincronizzazione dei dati duplicati

Page 38: SaaS con Symfony2 un caso *molto* concreto di applicazione multitenant

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”

Page 39: SaaS con Symfony2 un caso *molto* concreto di applicazione multitenant

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

Page 40: SaaS con Symfony2 un caso *molto* concreto di applicazione multitenant

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

Page 41: SaaS con Symfony2 un caso *molto* concreto di applicazione multitenant

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

(onFlush, prePersist)

Page 42: SaaS con Symfony2 un caso *molto* concreto di applicazione multitenant

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);}

Page 43: SaaS con Symfony2 un caso *molto* concreto di applicazione multitenant

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

Page 44: SaaS con Symfony2 un caso *molto* concreto di applicazione multitenant

Switch della connessione

Page 45: SaaS con Symfony2 un caso *molto* concreto di applicazione multitenant

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

Page 46: SaaS con Symfony2 un caso *molto* concreto di applicazione multitenant

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

Page 47: SaaS con Symfony2 un caso *molto* concreto di applicazione multitenant

Gestire 500 DBHelp needed

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

Page 48: SaaS con Symfony2 un caso *molto* concreto di applicazione multitenant

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');}

Page 49: SaaS con Symfony2 un caso *molto* concreto di applicazione multitenant

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}

Page 50: SaaS con Symfony2 un caso *molto* concreto di applicazione multitenant

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); }!}

Page 51: SaaS con Symfony2 un caso *molto* concreto di applicazione multitenant

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)' ); } }

Page 52: SaaS con Symfony2 un caso *molto* concreto di applicazione multitenant

Ciclare gli shards

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

Page 53: SaaS con Symfony2 un caso *molto* concreto di applicazione multitenant

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(); }}

Page 54: SaaS con Symfony2 un caso *molto* concreto di applicazione multitenant

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;}

Page 55: SaaS con Symfony2 un caso *molto* concreto di applicazione multitenant

Parallelizzare FTW

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

Page 56: SaaS con Symfony2 un caso *molto* concreto di applicazione multitenant

Conclusioni

Page 57: SaaS con Symfony2 un caso *molto* concreto di applicazione multitenant

Fa al caso tuo?

Page 58: SaaS con Symfony2 un caso *molto* concreto di applicazione multitenant

Si, lo rifarei

Page 59: SaaS con Symfony2 un caso *molto* concreto di applicazione multitenant

Domande?

Page 60: SaaS con Symfony2 un caso *molto* concreto di applicazione multitenant

https://joind.in/12212

Page 61: SaaS con Symfony2 un caso *molto* concreto di applicazione multitenant

Thanks