PostgreSQL
 sql >> Database >  >> RDS >> PostgreSQL

Gestisci il pool di connessioni nell'app Web multi-tenant con Spring, Hibernate e C3P0

Puoi scegliere tra 3 diverse strategie che avranno un impatto sul polling delle connessioni. In ogni caso devi fornire un'implementazione di MultiTenantConnectionProvider . La strategia che scegli avrà ovviamente un impatto sulla tua implementazione.

Osservazione generale su MultiTenantConnectionProvider.getAnyConnection()

getAnyConnection() è richiesto da Hibernate per raccogliere i metadati e configurare SessionFactory. Di solito in un'architettura multi-tenant si dispone di un database (o schema) speciale/master non utilizzato da nessun tenant. È una specie di database modello (o schema). Va bene se questo metodo restituisce una connessione a questo database (o schema).

Strategia 1:ogni tenant ha il proprio database. (e quindi è il proprio pool di connessioni)

In questo caso, ogni tenant ha il proprio pool di connessioni gestito da C3PO e puoi fornire un'implementazione di MultiTenantConnectionProvider basato su AbstractMultiTenantConnectionProvider

Ogni tenant ha il proprio C3P0ConnectionProvider , quindi tutto quello che devi fare in selectConnectionProvider(tenantIdentifier) è restituire quello corretto. Puoi mantenere una mappa per memorizzarli nella cache e puoi inizializzare lazy un C3POConnectionProvider con qualcosa come :

private ConnectionProvider lazyInit(String tenantIdentifier){
    C3P0ConnectionProvider connectionProvider = new C3P0ConnectionProvider();
    connectionProvider.configure(getC3POProperties(tenantIdentifier));
    return connectionProvider;
}

private Map getC3POProperties(String tenantIdentifier){
    // here you have to get the default hibernate and c3po config properties 
    // from a file or from Spring application context (there are good chances
    // that those default  properties point to the special/master database) 
    // and alter them so that the datasource point to the tenant database
    // i.e. : change the property hibernate.connection.url 
    // (and any other tenant specific property in your architecture like :
    //     hibernate.connection.username=tenantIdentifier
    //     hibernate.connection.password=...
    //     ...) 
}

Strategia 2:ogni tenant ha il proprio schema e il proprio pool di connessioni in un unico database

Questo caso è molto simile alla prima strategia relativa a ConnectionProvider implementazione poiché puoi anche utilizzare AbstractMultiTenantConnectionProvider come classe base per implementare il tuo MultiTenantConnectionProvider

L'implementazione è molto simile all'implementazione suggerita per la Strategia 1 tranne per il fatto che è necessario modificare lo schema invece del database nella configurazione c3po

Strategia 3:ogni tenant ha il proprio schema in un unico database ma utilizza un pool di connessioni condiviso

Questo caso è leggermente diverso poiché ogni tenant utilizzerà lo stesso provider di connessione (e quindi il pool di connessioni sarà condiviso). Nel caso:il provider di connessione deve impostare lo schema da utilizzare prima di qualsiasi utilizzo della connessione. cioè devi implementare MultiTenantConnectionProvider.getConnection(String tenantIdentifier) (ovvero l'implementazione predefinita fornita da AbstractMultiTenantConnectionProvider non funzionerà).

Con postgresql puoi farlo con :

 SET search_path to <schema_name_for_tenant>;

o usando l'alias

 SET schema <schema_name_for_tenant>;

Quindi ecco cosa è il tuo getConnection(tenant_identifier); sarà simile a:

@Override
public Connection getConnection(String tenantIdentifier) throws SQLException {
    final Connection connection = getAnyConnection();
    try {
        connection.createStatement().execute( "SET search_path TO " + tenanantIdentifier );
    }
    catch ( SQLException e ) {
        throw new HibernateException(
                "Could not alter JDBC connection to specified schema [" +
                        tenantIdentifier + "]",
                e
        );
    }
    return connection;
}

Un riferimento utile è qui (documento ufficiale)

Altro collegamento utile C3POConnectionProvider.java

Puoi combinare la strategia 1 e la strategia 2 nella tua implementazione. Hai solo bisogno di un modo per trovare le proprietà di connessione/URL di connessione corretti per il tenant corrente.

MODIFICA

Penso che la scelta tra la strategia 2 o 3 dipenda dal traffico e dal numero di tenant sulla tua app. Con pool di connessioni separati:la quantità di connessioni disponibili per un tenant sarà molto inferiore e così:se per qualche motivo legittimo un tenant necessita improvvisamente di molte connessioni, le prestazioni viste da questo particolare tenant diminuiranno drasticamente (mentre l'altro tenant non sarà colpiti).

Con la strategia 3, invece, se per qualche motivo legittimo un inquilino ha bisogno all'improvviso di molte connessioni:la performance vista da ogni inquilino diminuirà.

In generale, penso che la strategia 2 sia più flessibile e sicura:ogni tenant non può consumare più di una determinata quantità di connessione (e questa quantità può essere configurata per tenant, se necessario)