All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.torodb.backend.AbstractDbBackendService Maven / Gradle / Ivy

There is a newer version: 0.50.3
Show newest version
/*
 * ToroDB
 * Copyright © 2014 8Kdata Technology (www.8kdata.com)
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see .
 */

package com.torodb.backend;

import com.google.common.base.Preconditions;
import com.torodb.backend.ErrorHandler.Context;
import com.torodb.core.annotations.TorodbIdleService;
import com.torodb.core.services.IdleTorodbService;
import com.vladmihalcea.flexypool.FlexyPoolDataSource;
import com.vladmihalcea.flexypool.adaptor.HikariCPPoolAdapter;
import com.vladmihalcea.flexypool.config.Configuration;
import com.vladmihalcea.flexypool.strategy.RetryConnectionAcquiringStrategy;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import org.apache.logging.log4j.Logger;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ThreadFactory;

import javax.annotation.Nonnull;
import javax.sql.DataSource;

public abstract class AbstractDbBackendService
    extends IdleTorodbService implements DbBackendService {

  private static final Logger LOGGER = BackendLoggerFactory.get(AbstractDbBackendService.class);

  public static final int SYSTEM_DATABASE_CONNECTIONS = 1;
  public static final int MIN_READ_CONNECTIONS_DATABASE = 1;
  public static final int MIN_SESSION_CONNECTIONS_DATABASE = 2;
  public static final int MIN_CONNECTIONS_DATABASE = SYSTEM_DATABASE_CONNECTIONS
      + MIN_READ_CONNECTIONS_DATABASE
      + MIN_SESSION_CONNECTIONS_DATABASE;
  public static final int MAX_RETRY_ATTEMPS = 5;
  
  private final ConfigurationT configuration;
  private final ErrorHandler errorHandler;

  private HikariDataSource embeddableWriteDataSource;
  private HikariDataSource embeddableSystemDataSource;
  private HikariDataSource embeddableReadOnlyDataSource;
  private FlexyPoolDataSource writeDataSource;
  private FlexyPoolDataSource systemDataSource;
  private FlexyPoolDataSource readOnlyDataSource;
  /**
   * Relation between schema names and their data import mode.
   *
   * 

If a entry has the value true, then that schema is on import mode. Indexes will not be * created while data import mode is enabled. When this mode is enabled importing data will be * faster. */ private final ConcurrentHashMap schemaImportMode = new ConcurrentHashMap<>(); /** * Configure the backend. * *

The contract specifies that any subclass must call initialize() method * after properly constructing the object. * * @param threadFactory the thread factory that will be used to create the startup and shutdown * threads */ public AbstractDbBackendService(@TorodbIdleService ThreadFactory threadFactory, ConfigurationT configuration, ErrorHandler errorHandler) { super(threadFactory); this.configuration = configuration; this.errorHandler = errorHandler; int connectionPoolSize = configuration.getConnectionPoolSize(); int reservedReadPoolSize = configuration.getReservedReadPoolSize(); Preconditions.checkState( connectionPoolSize >= MIN_CONNECTIONS_DATABASE, "At least " + MIN_CONNECTIONS_DATABASE + " total connections with the backend SQL database are required" ); Preconditions.checkState( reservedReadPoolSize >= MIN_READ_CONNECTIONS_DATABASE, "At least " + MIN_READ_CONNECTIONS_DATABASE + " read connection(s) is(are) required" ); Preconditions.checkState( connectionPoolSize - reservedReadPoolSize >= MIN_SESSION_CONNECTIONS_DATABASE, "Reserved read connections must be lower than total connections minus " + MIN_SESSION_CONNECTIONS_DATABASE ); } @Override protected void startUp() throws Exception { int reservedReadPoolSize = configuration.getReservedReadPoolSize(); embeddableWriteDataSource = createPooledDataSource( configuration, "session", configuration.getConnectionPoolSize() - reservedReadPoolSize - SYSTEM_DATABASE_CONNECTIONS, getCommonTransactionIsolation(), false ); embeddableSystemDataSource = createPooledDataSource( configuration, "system", SYSTEM_DATABASE_CONNECTIONS, getSystemTransactionIsolation(), false); embeddableReadOnlyDataSource = createPooledDataSource( configuration, "cursors", reservedReadPoolSize, getGlobalCursorTransactionIsolation(), true); writeDataSource = wrapObservableDataSource(embeddableWriteDataSource); systemDataSource = wrapObservableDataSource(embeddableSystemDataSource); readOnlyDataSource = wrapObservableDataSource(embeddableReadOnlyDataSource); writeDataSource.start(); systemDataSource.start(); readOnlyDataSource.start(); } @Override @SuppressFBWarnings(value = "UWF_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR", justification = "Object lifecyle is managed as a Service. Datasources are initialized in setup method") protected void shutDown() throws Exception { writeDataSource.stop(); systemDataSource.stop(); readOnlyDataSource.stop(); embeddableWriteDataSource.close(); embeddableSystemDataSource.close(); embeddableReadOnlyDataSource.close(); } @Nonnull protected abstract TransactionIsolationLevel getCommonTransactionIsolation(); @Nonnull protected abstract TransactionIsolationLevel getSystemTransactionIsolation(); @Nonnull protected abstract TransactionIsolationLevel getGlobalCursorTransactionIsolation(); private HikariDataSource createPooledDataSource( ConfigurationT configuration, String poolName, int poolSize, TransactionIsolationLevel transactionIsolationLevel, boolean readOnly ) { HikariConfig hikariConfig = new HikariConfig(); // Delegate database-specific setting of connection parameters and any specific configuration hikariConfig.setDataSource(getConfiguredDataSource(configuration, poolName)); // Apply ToroDB-specific datasource configuration hikariConfig.setConnectionTimeout(configuration.getConnectionPoolTimeout()); hikariConfig.setPoolName(poolName); hikariConfig.setMaximumPoolSize(poolSize); hikariConfig.setTransactionIsolation(transactionIsolationLevel.name()); hikariConfig.setReadOnly(readOnly); LOGGER.info("Created pool {} with size {} and level {}", poolName, poolSize, transactionIsolationLevel.name()); return new HikariDataSource(hikariConfig); } private FlexyPoolDataSource wrapObservableDataSource( HikariDataSource dataSource ) { Configuration hikariConfiguration = createPooledObservableDataSourceConfiguration(dataSource); return new FlexyPoolDataSource<>(hikariConfiguration, new RetryConnectionAcquiringStrategy.Factory(MAX_RETRY_ATTEMPS)); } private Configuration createPooledObservableDataSourceConfiguration( HikariDataSource poolingDataSource ) { return new Configuration.Builder<>( poolingDataSource.getPoolName(), poolingDataSource, HikariCPPoolAdapter.FACTORY).build(); } protected abstract DataSource getConfiguredDataSource(ConfigurationT configuration, String poolName); @Override public void disableDataInsertMode(String schemaName) { schemaImportMode.put(schemaName, Boolean.FALSE); } @Override public void enableDataInsertMode(String schemaName) { schemaImportMode.put(schemaName, Boolean.TRUE); } @Override public boolean isOnDataInsertMode(String schemaName) { Boolean importMode = schemaImportMode.get(schemaName); if (importMode == null) { return false; } return importMode; } @Override public DataSource getSessionDataSource() { checkState(); return writeDataSource; } @Override public DataSource getSystemDataSource() { checkState(); return systemDataSource; } @Override public DataSource getGlobalCursorDatasource() { checkState(); return readOnlyDataSource; } protected void checkState() { if (!isRunning()) { throw new IllegalStateException("The " + serviceName() + " is not running"); } } @Override public boolean includeForeignKeys() { return configuration.includeForeignKeys(); } protected void postConsume(Connection connection, boolean readOnly) throws SQLException { connection.setReadOnly(readOnly); if (!connection.isValid(500)) { throw new RuntimeException("DB connection is not valid"); } connection.setAutoCommit(false); } private Connection consumeConnection(DataSource ds, boolean readOnly) { checkState(); try { Connection c = ds.getConnection(); postConsume(c, readOnly); return c; } catch (SQLException ex) { throw errorHandler.handleException(Context.GET_CONNECTION, ex); } } @Override public Connection createSystemConnection() { checkState(); return consumeConnection(systemDataSource, false); } @Override public Connection createReadOnlyConnection() { checkState(); return consumeConnection(readOnlyDataSource, true); } @Override public Connection createWriteConnection() { checkState(); return consumeConnection(writeDataSource, false); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy