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

io.datarouter.client.mysql.MysqlClientManager Maven / Gradle / Ivy

There is a newer version: 0.0.125
Show newest version
/*
 * Copyright © 2009 HotPads ([email protected])
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.datarouter.client.mysql;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

import javax.inject.Inject;
import javax.inject.Singleton;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.mysql.cj.jdbc.Driver;

import io.datarouter.client.mysql.connection.MysqlConnectionPoolHolder;
import io.datarouter.client.mysql.ddl.execute.DatabaseCreator;
import io.datarouter.client.mysql.ddl.execute.MysqlSchemaUpdateService;
import io.datarouter.client.mysql.op.Isolation;
import io.datarouter.instrumentation.trace.TraceSpanGroupType;
import io.datarouter.instrumentation.trace.TracerTool;
import io.datarouter.model.exception.DataAccessException;
import io.datarouter.storage.client.BaseClientManager;
import io.datarouter.storage.client.ClientId;
import io.datarouter.storage.client.ConnectionHandle;
import io.datarouter.storage.config.schema.SchemaUpdateOptions;
import io.datarouter.storage.config.schema.SchemaUpdateResult;
import io.datarouter.storage.node.type.physical.PhysicalNode;
import io.datarouter.storage.util.DatarouterCounters;
import io.datarouter.util.timer.PhaseTimer;

@Singleton
public class MysqlClientManager extends BaseClientManager implements MysqlConnectionClientManager, TxnClientManager{
	private static final Logger logger = LoggerFactory.getLogger(MysqlClientManager.class);

	private final Map> handleByThreadByClient = new ConcurrentHashMap<>();
	private final Map> connectionByHandleByClient = new ConcurrentHashMap<>();
	private final Map connectionCounterByClient = new ConcurrentHashMap<>();

	@Inject
	private SchemaUpdateOptions schemaUpdateOptions;
	@Inject
	private MysqlConnectionPoolHolder mysqlConnectionPoolHolder;
	@Inject
	private MysqlSchemaUpdateService schemaUpdateService;
	@Inject
	private MysqlClientType clientType;
	@Inject
	private DatabaseCreator databaseCreator;

	@Override
	protected void safeInitClient(ClientId clientId){
		PhaseTimer timer = new PhaseTimer(clientId.getName());
		loadDriver();
		databaseCreator.createDatabaseIfNeeded(clientId);
		timer.add("databaseCreation");
		mysqlConnectionPoolHolder.createConnectionPool(clientId);
		timer.add("pool");
		logger.warn(timer.toString());
	}

	/**
	 * We need to reload the drivers when using Tomcat because it tries to register them too early.
	 * http://tomcat.apache.org/tomcat-9.0-doc/jndi-datasource-examples-howto.html#DriverManager,_the_service_provider_mechanism_and_memory_leaks
	 * Loading the class is enough to register the driver, so this method is creating an instance to load the class.
	 */
	private void loadDriver(){
		try{
			new Driver();
		}catch(SQLException e){
			throw new RuntimeException(e);
		}
	}

	private AtomicLong connectionCounter(ClientId clientId){
		return connectionCounterByClient.computeIfAbsent(clientId, $ -> new AtomicLong(-1));
	}

	private Map connectionByHandle(ClientId clientId){
		return connectionByHandleByClient.computeIfAbsent(clientId, $ -> new ConcurrentHashMap<>());
	}

	private Map handleByThread(ClientId clientId){
		return handleByThreadByClient.computeIfAbsent(clientId, $ -> new ConcurrentHashMap<>());
	}

	@Override
	protected Future> doSchemaUpdate(PhysicalNode node){
		if(schemaUpdateOptions.getEnabled()){
			return schemaUpdateService.queueNodeForSchemaUpdate(node.getFieldInfo().getClientId(), node);
		}
		return CompletableFuture.completedFuture(Optional.empty());
	}

	@Override
	public void gatherSchemaUpdates(){
		schemaUpdateService.gatherSchemaUpdates(true);
	}

	@Override
	public ConnectionHandle getExistingHandle(ClientId clientId){
		Thread currentThread = Thread.currentThread();
		return handleByThread(clientId).get(currentThread.getId());
	}

	@Override
	public void reserveConnection(ClientId clientId){
		initClient(clientId);
		DatarouterCounters.incClient(clientType, "connection open", clientId.getName(), 1);
		try(var $ = TracerTool.startSpan("reserve " + clientId.getName(), TraceSpanGroupType.DATABASE)){
			ConnectionHandle existingHandle = getExistingHandle(clientId);
			if(existingHandle != null){
				// logger.warn("got existing connection:"+existingHandle);
				TracerTool.appendToSpanInfo("connection", "existing");
				DatarouterCounters.incClient(clientType, "connection open existing", clientId.getName(), 1);
				// Assert connection exists for handle
				existingHandle.incrementNumTickets();
				return;
			}
			long requestTimeNs = System.nanoTime();
			Connection newConnection = mysqlConnectionPoolHolder.getConnectionPool(clientId).checkOut();
			logIfSlowReserveConnection(clientId, requestTimeNs);

			long threadId = Thread.currentThread().getId();
			long connNumber = connectionCounter(clientId).incrementAndGet();
			var handle = new ConnectionHandle(
					Thread.currentThread(),
					clientId.getName(),
					connNumber,
					ConnectionHandle.OUTERMOST_TICKET_NUMBER);
			if(handleByThread(clientId).get(threadId) == null){
				handleByThread(clientId).put(threadId, handle);
			}
			connectionByHandle(clientId).put(handle, newConnection);
			// logger.warn("new connection:"+handle);
			TracerTool.appendToSpanInfo("connection", "new");
			DatarouterCounters.incClient(clientType, "connection open new", clientId.getName(), 1);
		}catch(SQLException e){
			DatarouterCounters.incClient(
					clientType,
					"connection open " + e.getClass().getSimpleName(),
					clientId.getName(),
					1);
			throw new DataAccessException("Could not reserve connection client=" + clientId.getName(), e);
		}
	}

	private void logIfSlowReserveConnection(ClientId clientId, long requestTimeNs){
		long elapsedUs = (System.nanoTime() - requestTimeNs) / 1000;
		if(elapsedUs > 1000){
			DatarouterCounters.incClient(clientType, "connection open > 1ms", clientId.getName(), 1L);
		}
		if(elapsedUs > 2000){
			DatarouterCounters.incClient(clientType, "connection open > 2ms", clientId.getName(), 1L);
		}
		if(elapsedUs > 5000){
			DatarouterCounters.incClient(clientType, "connection open > 5ms", clientId.getName(), 1L);
			long millis = TimeUnit.MICROSECONDS.toMillis(elapsedUs);
			logger.warn("slow reserveConnection durationMs={} client={}", millis, clientId.getName());
		}
		if(elapsedUs > 10000){
			DatarouterCounters.incClient(clientType, "connection open > 10ms", clientId.getName(), 1L);
		}
	}

	@Override
	public void releaseConnection(ClientId clientId){
		try{
			Thread currentThread = Thread.currentThread();
			ConnectionHandle handle = getExistingHandle(clientId);
			if(handle == null){
				return;// the connection probably was never opened successfully
			}

			// decrement counters
			handle.decrementNumTickets();
			if(handle.getNumTickets() > 0){
				// logger.warn("KEEPING CONNECTION OPEN for "+handle+", "+this.getStats());
				return; // others are still using this connection
			}

			// release connection
			connectionByHandle(clientId).get(handle).close();
			// on close, there will be a network round trip if isolation needs to be set back to default
			// on close, there will be another network round trip if autocommit needs to be disabled

			connectionByHandle(clientId).remove(handle);
			handleByThread(clientId).remove(currentThread.getId());
			DatarouterCounters.incClient(clientType, "releaseConnection", clientId.getName(), 1L);
		}catch(SQLException e){
			throw new DataAccessException(e);
		}
	}

	@Override
	public Connection getExistingConnection(ClientId clientId){
		ConnectionHandle handle = getExistingHandle(clientId);
		if(handle == null){
			return null;
		}
		return connectionByHandle(clientId).get(handle);
	}

	@Override
	public void beginTxn(ClientId clientId, Isolation isolation, boolean autoCommit){
		try{
			Connection connection = getExistingConnection(clientId);
			// jdbc standard says that autoCommit=true by default on each new connection
			if(!autoCommit){
				connection.setAutoCommit(false);
				logger.debug("setAutoCommit={} on {}", false, getExistingHandle(clientId));
				if(connection.getTransactionIsolation() != isolation.getJdbcVal().intValue()){
					connection.setTransactionIsolation(isolation.getJdbcVal());
					logger.debug("setTransactionIsolation={} on {}",
							isolation.toString(),
							getExistingHandle(clientId));
				}
			}
			DatarouterCounters.incClient(clientType, "beginTxn", clientId.getName(), 1L);
		}catch(SQLException e){
			throw new DataAccessException(e);
		}
	}

	@Override
	public void commitTxn(ClientId clientId){
		try{
			Connection connection = getExistingConnection(clientId);
			if(connection != null){
				if(!connection.getAutoCommit()){
					connection.commit();
					logger.debug("committed txn on:" + getExistingHandle(clientId));
				}
			}else{
				logger.warn("couldn't commit txn because connection was null.  handle=" + getExistingHandle(clientId));
			}
			DatarouterCounters.incClient(clientType, "commitTxn", clientId.getName(), 1L);
		}catch(SQLException e){
			throw new DataAccessException(e);
		}
	}

	@Override
	public void rollbackTxn(ClientId clientId){
		try{
			Connection connection = getExistingConnection(clientId);
			if(connection == null){
				logger.warn("couldn't rollback txn because connection was null clientName={} handle={}",
						clientId.getName(),
						getExistingHandle(clientId),
						new Exception());
			}else if(!connection.getAutoCommit()){
				logger.warn("ROLLING BACK TXN " + getExistingHandle(clientId));
				connection.rollback();
			}
			DatarouterCounters.incClient(clientType, "rollbackTxn", clientId.getName(), 1L);
		}catch(SQLException e){
			throw new DataAccessException(e);
		}
	}

	@Override
	public void shutdown(ClientId clientId){
		schemaUpdateService.gatherSchemaUpdates(true);
		mysqlConnectionPoolHolder.getConnectionPool(clientId).shutdown();
	}

	public String getStats(ClientId clientId){
		return String.format(
				"client=%s threadHandles=%s connectionHandles=%s",
				clientId.getName(),
				handleByThread(clientId).size(),
				connectionByHandle(clientId).size());
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy