package com.linkedin.databus.bootstrap.utils;
/*
*
* Copyright 2013 LinkedIn Corp. All rights reserved
*
* 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.
*
*/
import java.io.ByteArrayInputStream;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.log4j.Logger;
import com.linkedin.databus.bootstrap.api.BootstrapProducerStatus;
import com.linkedin.databus.bootstrap.common.BootstrapConn;
import com.linkedin.databus.bootstrap.common.BootstrapDBMetaDataDAO;
import com.linkedin.databus.bootstrap.common.BootstrapReadOnlyConfig;
import com.linkedin.databus.core.DbusEventBufferAppendable;
import com.linkedin.databus.core.DbusEventBufferStreamAppendable;
import com.linkedin.databus.core.DbusEventFactory;
import com.linkedin.databus.core.DbusEventInfo;
import com.linkedin.databus.core.DbusEventKey;
import com.linkedin.databus.core.DbusEventKey.KeyType;
import com.linkedin.databus.core.DbusOpcode;
import com.linkedin.databus.core.InternalDatabusEventsListener;
import com.linkedin.databus.core.InvalidEventException;
import com.linkedin.databus.core.KeyTypeNotImplementedException;
import com.linkedin.databus.core.monitoring.mbean.DbusEventsStatisticsCollector;
import com.linkedin.databus2.producers.db.OracleTriggerMonitoredSourceInfo;
import com.linkedin.databus2.util.DBHelper;
public class BootstrapDBSeeder
implements DbusEventBufferAppendable, DbusEventBufferStreamAppendable
{
private static final int FIFTY_MB_IN_BYTES = 50000000;
public static final String MODULE = BootstrapDBSeeder.class.getName();
public static final Logger LOG = Logger.getLogger(MODULE);
private BootstrapDBMetaDataDAO _bootstrapDao = null;
private BootstrapReadOnlyConfig _config = null;
private ByteBuffer _buf = null;
private ByteArrayInputStream _bufStream = null;
private ByteArrayInputStream _bufStream2 = null;
private final Map<Short, PreparedStatement> _statementMap;
private short _currSrcId = -1;
private long _totLatency = 0;
private long _scn = 0;
private List<OracleTriggerMonitoredSourceInfo> _sources = null;
private Map<String, Long> _lastRows = null;
private Map<String, String> _lastKeys = null;
private String _lastSeenKey = null;
private long _startSCN = -1;
public BootstrapDBSeeder(BootstrapReadOnlyConfig config,
List<OracleTriggerMonitoredSourceInfo> sources)
throws Exception
{
_config = config;
_statementMap = new HashMap<Short, PreparedStatement>();
byte[] b = new byte[FIFTY_MB_IN_BYTES];
_buf = ByteBuffer.wrap(b);
_bufStream = new ByteArrayInputStream(b);
_bufStream2 = new ByteArrayInputStream(b);
getConnection();
_sources = sources;
initSources();
}
public void initSources()
throws SQLException
{
LOG.info("MySQL JDBC Version :" + getConnection().getMetaData().getDriverVersion());
_lastRows = new HashMap<String, Long>();
_lastKeys = new HashMap<String, String>();
for ( OracleTriggerMonitoredSourceInfo sourceInfo : _sources)
{
createBootStrapSourceDB(sourceInfo);
_lastRows.put(sourceInfo.getEventView(), getRowIdFromCheckpoint(sourceInfo));
String k = geSrcKeyFromCheckpoint(sourceInfo);
_lastKeys.put(sourceInfo.getEventView(), k);
if ( -1 == _currSrcId)
_currSrcId = sourceInfo.getSourceId();
if ( null == _lastSeenKey)
_lastSeenKey = k;
}
}
public void startSeeding()
throws SQLException
{
setBootstrapSourceStatus(BootstrapProducerStatus.SEEDING);
}
public Map<String, Long> getLastRows()
{
return _lastRows;
}
public Map<String, String> getLastKeys()
{
return _lastKeys;
}
private PreparedStatement prepareInsertStatement(short srcId) throws SQLException
{
Connection conn = null;
PreparedStatement stmt = null;
StringBuilder sql = new StringBuilder();
try
{
conn = getConnection();
sql.append("insert into ");
sql.append(getTableName(srcId));
sql.append("(scn, srckey, val) ");
sql.append(" values(?,?,?) ");
sql.append("on duplicate key update scn = ?, ");
sql.append("val = ?");
// sql.append("(scn, windowscn, srckey) ");
// sql.append(" values(?, ?,?)");
stmt = conn.prepareStatement(sql.toString());
} catch (SQLException e) {
LOG.fatal("Unable to create insert statement for Statement : (" + sql + ")", e);
throw e;
}
return stmt;
}
/*
* Returns a connection object.
* Note: The connection object is still owned by BootstrapConn. SO dont close it
*/
public Connection getConnection()
{
Connection conn = null;
if (_bootstrapDao == null)
{
LOG.info("<<<< Creating Bootstrap Connection!! >>>>");
BootstrapConn dbConn = new BootstrapConn();
try
{
dbConn.initBootstrapConn(false,
java.sql.Connection.TRANSACTION_READ_UNCOMMITTED,
_config.getBootstrapDBUsername(),
_config.getBootstrapDBPassword(),
_config.getBootstrapDBHostname(),
_config.getBootstrapDBName());
_bootstrapDao = new BootstrapDBMetaDataDAO(dbConn,
_config.getBootstrapDBHostname(),
_config.getBootstrapDBUsername(),
_config.getBootstrapDBPassword(),
_config.getBootstrapDBName(),
false);
} catch (Exception e) {
LOG.fatal("Unable to open BootstrapDB Connection !!", e);
throw new RuntimeException("Got exception when getting bootstrap DB Connection.",e);
}
}
try
{
conn = _bootstrapDao.getBootstrapConn().getDBConn();
} catch ( SQLException e) {
LOG.fatal("Not able to open BootstrapDB Connection !!", e);
throw new RuntimeException("Got exception when getting bootstrap DB Connection.",e);
}
return conn;
}
/*
* Initialize Bootstrap Sources entry
*/
public void setBootstrapSourceStatus(int status)
throws SQLException
{
Connection conn = null;
PreparedStatement stmt = null;
try
{
String sql = "insert into bootstrap_sources (id, src, status) values (?, ?, ?) on duplicate key update id = ?, src= ?, status=?";
conn = getConnection();
stmt = conn.prepareStatement(sql);
for ( OracleTriggerMonitoredSourceInfo sourceInfo : _sources)
{
stmt.setInt(1, sourceInfo.getSourceId());
stmt.setString(2, sourceInfo.getSourceName());
stmt.setInt(3,status);
stmt.setInt(4, sourceInfo.getSourceId());
stmt.setString(5, sourceInfo.getSourceName());
stmt.setInt(6,status );
stmt.executeUpdate();
}
conn.commit();
} catch (SQLException sqlEx) {
LOG.error("Got Exception while initializing Bootstrap Sources !!", sqlEx);
if ( conn != null)
conn.rollback();
throw sqlEx;
} finally {
DBHelper.close(stmt);
}
}
/*
* Creates Bootstrap source DB only if it is not already available
*/
public void createBootStrapSourceDB(OracleTriggerMonitoredSourceInfo source ) throws SQLException
{
_bootstrapDao.createNewSrcTable(source.getSourceId());
}
public String getTableName(int srcId) throws SQLException
{
return _bootstrapDao.getBootstrapConn().getSrcTableName(srcId);
}
@Override
public void start(long startSCN)
{
_scn = startSCN;
}
@Override
public void startEvents() {
}
@Override
public boolean appendEvent(DbusEventKey key,
short pPartitionId,
short lPartitionId,
long timeStamp,
short srcId,
byte[] schemaId,
byte[] value,
boolean enableTracing,
DbusEventsStatisticsCollector statsCollector)
{
return appendEvent(key, _scn, pPartitionId, lPartitionId, timeStamp, srcId,
schemaId, value, enableTracing, statsCollector);
}
@Override
@Deprecated
public boolean appendEvent(DbusEventKey key,
short pPartitionId,
short lPartitionId,
long timeStamp,
short srcId,
byte[] schemaId,
byte[] value,
boolean enableTracing) {
throw new RuntimeException("Not implemented!!!");
}
public boolean appendEvent(DbusEventKey key,
DbusEventKey seederChunkKey,
long sequenceId,
short pPartitionId,
short lPartitionId,
long timeStamp,
short srcId,
byte[] schemaId,
byte[] value,
boolean enableTracing,
DbusEventsStatisticsCollector statsCollector)
{
DbusEventInfo eventInfo = new DbusEventInfo(DbusOpcode.UPSERT, sequenceId, pPartitionId, lPartitionId,
timeStamp, srcId, schemaId, value, enableTracing,
false);
return appendEvent(key, seederChunkKey, eventInfo, statsCollector);
}
@Override
public boolean appendEvent(DbusEventKey key,
short pPartitionId,
short lPartitionId,
long timeStamp,
short srcId,
byte[] schemaId,
byte[] value,
boolean enableTracing,
boolean isReplicated,
DbusEventsStatisticsCollector statsCollector)
{
throw new RuntimeException("This API not expected to be called on BootstrapDBSeeder !!");
}
public boolean appendEvent(DbusEventKey key,
DbusEventKey seederChunkKey,
DbusEventInfo eventInfo,
DbusEventsStatisticsCollector statsCollector)
{
boolean isDebugEnabled = LOG.isDebugEnabled();
boolean ret = false;
PreparedStatement stmt = null;
/*
* Track the startSCN
*/
if ( _startSCN == -1 )
_startSCN = eventInfo.getSequenceId();
short srcId = eventInfo.getSrcId();
long sequenceId = eventInfo.getSequenceId();
try
{
//long start = System.nanoTime();
if ( seederChunkKey.getKeyType() == KeyType.LONG )
{
_lastSeenKey = "" + seederChunkKey.getLongKey();
} else {
_lastSeenKey = seederChunkKey.getStringKey();
}
if (isDebugEnabled)
LOG.debug("Seeder Chunk Key is: " + seederChunkKey + ",EventInfo :" + eventInfo + ", Key is :" + key);
eventInfo.setOpCode(null);
eventInfo.setAutocommit(true);
if (eventInfo.getValueLength() >= FIFTY_MB_IN_BYTES)
{
LOG.fatal("Event Size larger than 50 MB. For Key :" + key + ", avro record size is : "+ eventInfo.getValueLength());
}
DbusEventFactory.serializeEvent(key, _buf, eventInfo);
long end = System.nanoTime();
stmt = _statementMap.get(srcId);
if ( null == stmt)
{
stmt = prepareInsertStatement(srcId);
_statementMap.put(srcId, stmt);
}
if ( isDebugEnabled)
{
LOG.debug("Number of Bytes in serialized format:" + _buf.position());
LOG.debug("Key is :" + ( (key.getKeyType() == KeyType.LONG) ? key.getLongKey() : key.getStringKey()));
}
stmt.setLong(1,sequenceId);
String keyStr = null;
if (key.getKeyType() == DbusEventKey.KeyType.LONG)
{
keyStr = key.getLongKey().toString();
} else {
keyStr = key.getStringKey();
}
stmt.setString(2, keyStr);
// Reuse the iStream to set the blob
_bufStream.reset();
stmt.setBlob(3, _bufStream, _buf.position());
//stmt.setBytes(3, _buf.array());
stmt.setLong(4,sequenceId);
// Reuse the iStream to se the blob
_bufStream2.reset();
stmt.setBlob(5, _bufStream2, _buf.position());
//stmt.setBytes(5, _buf.array());
stmt.executeUpdate();
long end2 = System.nanoTime();
_totLatency += (end2 - end);
_currSrcId = srcId;
ret = true;
} catch (SQLException sqlEx) {
LOG.error("Error occured while inserting record for key:" +
key.getStringKey() + "(" + key.getLongKey() + ") with sequenceId:" + sequenceId, sqlEx);
throw new RuntimeException(sqlEx);
} catch (KeyTypeNotImplementedException ex) {
LOG.error("KeyNotImplemented error while inserting record for key:" +
key.getStringKey() + "(" + key.getLongKey() + ") with sequenceId:" + sequenceId, ex);
throw new RuntimeException(ex);
}
finally {
_buf.clear();
}
return ret;
}
@Override
public void rollbackEvents() {
Connection conn = getConnection();
try
{
LOG.error("Rolling back uncommitted bootstrap events");
conn.rollback();
} catch (SQLException e) {
LOG.error("Unable to rollback while seeding bootstrap.", e);
}
}
@Override
public void endEvents(boolean updateWindowScn, long windowScn,
boolean updateIndex, boolean callListener, DbusEventsStatisticsCollector statsCollector) {
}
/*
* Callback for processing end of source
*/
public void endSource(long scn)
{
String seederSql = "update bootstrap_seeder_state set endscn = ? where srcid = ? ";
Connection conn = null;
PreparedStatement stmt = null;
try
{
conn = getConnection();
stmt = conn.prepareStatement(seederSql);
stmt.setLong(1,scn);
stmt.setInt(2,_currSrcId);
stmt.executeUpdate();
conn.commit();
} catch (SQLException sqlEx) {
throw new RuntimeException(sqlEx);
} finally {
DBHelper.close(stmt);
}
}
/*
* Callback for processing end of Seeding
*/
public void endSeeding()
{
//Copy seeder state to producer and applier state
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
long minScn = -1;
try
{
String sql = getMinSCNSeederQuery();
conn = getConnection();
stmt = conn.createStatement();
LOG.info("Executing Min startSCN Query in Bootstrap DB:" + sql);
rs = stmt.executeQuery(sql);
rs.next();
minScn = rs.getLong(1);
LOG.info("Minimum startSCN of all the sources which were seeded was :" + minScn);
updateBootstrapStateTable(minScn,"bootstrap_applier_state");
updateBootstrapStateTable(minScn,"bootstrap_producer_state");
createLogTableRows();
// Go to the next bootstrap state for this Source
setBootstrapSourceStatus(BootstrapProducerStatus.SEEDING_CATCHUP);
conn.commit();
} catch (SQLException sqlEx) {
LOG.error("Got Exception while updating bootstrap state tables",sqlEx);
try
{
conn.rollback();
}catch(Exception ex) {
LOG.error("Error while rolling back ...",ex);
}
throw new RuntimeException(sqlEx);
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
DBHelper.close(rs,stmt,null);
}
}
private void updateBootstrapStateTable(long scn, String table)
throws SQLException
{
String upsertSQL = getBootstrapStateInsertStmt(table);
Connection conn = null;
PreparedStatement stmt = null;
try
{
conn = getConnection();
stmt = conn.prepareStatement(upsertSQL);
for (OracleTriggerMonitoredSourceInfo srcInfo : _sources)
{
stmt.setInt(1, srcInfo.getSourceId());
stmt.setInt(2,0);
stmt.setLong(3,scn);
stmt.setLong(4, 0);
stmt.setInt(5,0);
stmt.setLong(6, scn);
stmt.setLong(7,0);
stmt.executeUpdate();
}
} finally {
DBHelper.close(stmt);
}
}
private void createLogTableRows()
throws SQLException
{
String sql = "insert into bootstrap_loginfo (srcid, logid, minwindowscn, maxwindowscn, maxrid) values ( ?, 0, -1, -1, 0)";
Connection conn = null;
PreparedStatement stmt = null;
try
{
conn = getConnection();
stmt = conn.prepareStatement(sql);
for (OracleTriggerMonitoredSourceInfo srcInfo : _sources)
{
try
{
stmt.setInt(1,srcInfo.getSourceId());
stmt.executeUpdate();
_bootstrapDao.createNewLogTable(srcInfo.getSourceId());
} catch ( SQLException sqlEx) {
LOG.error("Got Error inserting entry into bootstrap_loginfo but proceeding !!", sqlEx);
}
}
} catch ( SQLException sqlEx) {
LOG.error("Got Error inserting entry into bootstrap_loginfo !!", sqlEx);
throw sqlEx;
} finally {
DBHelper.close(stmt);
}
}
private String getBootstrapStateInsertStmt(String table)
{
StringBuilder sql = new StringBuilder();
sql.append("insert into ").append(table);
sql.append(" (srcid, logid, windowscn, rid) values ( ?, ?, ?, ?) ");
sql.append(" on duplicate key update logid = ?, windowscn = ?, rid = ?");
return sql.toString();
}
private String getMinSCNSeederQuery()
{
StringBuilder buf = new StringBuilder();
buf.append("select min(startscn) from bootstrap_seeder_state where srcid IN (");
for (int i = 0 ; i < _sources.size() - 1; i++ )
{
buf.append(_sources.get(i).getSourceId()).append(", ");
}
buf.append(_sources.get(_sources.size() -1).getSourceId());
buf.append(")");
return buf.toString();
}
@Override
public void endEvents(long rowId, DbusEventsStatisticsCollector statsCollector)
{
Connection conn = null;
PreparedStatement stmt = null;
try
{
StringBuilder sql = new StringBuilder();
//sql.append("insert into bootstrap_applier_state ");
//sql.append("values (?,?,?,?) ");
//sql.append("on duplicate key update rid = ?");
sql.append("insert into bootstrap_seeder_state ");
sql.append("values ( ?, ?, -1, ?, ?)");
sql.append("on duplicate key update rid = ?, srckey = ?"); //startscn set only at the first insert
conn = getConnection();
stmt = conn.prepareStatement(sql.toString());
stmt.setInt(1,_currSrcId);
stmt.setLong(2, _startSCN);
stmt.setLong(3,rowId);
stmt.setString(4,_lastSeenKey);
stmt.setLong(5,rowId);
stmt.setString(6, _lastSeenKey);
LOG.info("\t Total Latency before commit :" + (_totLatency/1000000000));
stmt.executeUpdate();
long start = System.nanoTime();
conn.commit();
long end = System.nanoTime();
long lat = (end - start)/1000000;
LOG.info("\t Comit time (msec) :" + lat);
} catch (Exception e) {
LOG.fatal("Got Exception in endEvents !!", e);
throw new RuntimeException("Got Exception in endEvents !!", e);
} finally {
// Dont close conn as it is being reused.
DBHelper.close(stmt);
}
}
@Override
public boolean empty() {
return false;
}
@Override
public int readEvents(ReadableByteChannel readChannel,
Iterable<InternalDatabusEventsListener> eventListeners,
DbusEventsStatisticsCollector statsCollector)
throws InvalidEventException {
return 0;
}
public long getRowIdFromCheckpoint(OracleTriggerMonitoredSourceInfo sourceInfo)
{
Connection conn = null;
PreparedStatement stmt = null;
ResultSet row = null;
long rowId = -1;
try
{
StringBuilder sql = new StringBuilder();
sql.append("select rid from bootstrap_seeder_state ");
sql.append("where srcid = ?");
conn = getConnection();
stmt = conn.prepareStatement(sql.toString());
stmt.setInt(1, sourceInfo.getSourceId());
row = stmt.executeQuery();
if (row.next())
{
rowId = row.getLong(1);
}
} catch (Exception e) {
LOG.error("Got exception while trying to fetch the rowid from bootstrap_seeder_state !!", e);
} finally {
DBHelper.close(row, stmt, null);
}
return rowId;
}
/*
* TODO: Refactor to do one query to get rid and srckey from bootstrap_seeder_state
*/
public String geSrcKeyFromCheckpoint(OracleTriggerMonitoredSourceInfo sourceInfo)
{
Connection conn = null;
PreparedStatement stmt = null;
ResultSet row = null;
String srcKey = null;
try
{
StringBuilder sql = new StringBuilder();
sql.append("select srckey from bootstrap_seeder_state ");
sql.append("where srcid = ?");
conn = getConnection();
stmt = conn.prepareStatement(sql.toString());
stmt.setInt(1, sourceInfo.getSourceId());
row = stmt.executeQuery();
if (row.next())
{
srcKey = row.getString(1);
}
} catch (Exception e) {
LOG.error("Got exception while trying to fetch the srckey from bootstrap_seeder_state !!", e);
} finally {
DBHelper.close(row, stmt, null);
}
return srcKey;
}
@Override
public long getMinScn()
{
return 0;
}
@Override
public long lastWrittenScn()
{
return 0;
}
@Override
public void setStartSCN(long sinceSCN) { }
@Override
public long getPrevScn()
{
return 0;
}
@Override
public boolean appendEvent(DbusEventKey key, long sequenceId,
short pPartitionId, short lPartitionId, long timeStamp, short srcId,
byte[] schemaId, byte[] value, boolean enableTracing,
DbusEventsStatisticsCollector statsCollector) {
return false;
}
@Override
public boolean appendEvent(DbusEventKey key, DbusEventInfo eventInfo,
DbusEventsStatisticsCollector statsCollector) {
return false;
}
}