/** * Sets the user to run as. This is for the case where the request was generated by the user and * so the worker must set this value later. * * @param cq_id id of this entry in the queue * @param user user to run the jobs as */ public void setRunAs(long cq_id, String user) throws MetaException { try { Connection dbConn = getDbConn(); try { Statement stmt = dbConn.createStatement(); String s = "update COMPACTION_QUEUE set cq_run_as = '" + user + "' where cq_id = " + cq_id; LOG.debug("Going to execute update <" + s + ">"); if (stmt.executeUpdate(s) != 1) { LOG.error("Unable to update compaction record"); LOG.debug("Going to rollback"); dbConn.rollback(); } LOG.debug("Going to commit"); dbConn.commit(); } catch (SQLException e) { LOG.error("Unable to update compaction queue, " + e.getMessage()); try { LOG.debug("Going to rollback"); dbConn.rollback(); } catch (SQLException e1) { } detectDeadlock(e, "setRunAs"); } finally { closeDbConn(dbConn); } } catch (DeadlockException e) { setRunAs(cq_id, user); } finally { deadlockCnt = 0; } }
/** * Find entries in the queue that are ready to be cleaned. * * @return information on the entry in the queue. */ public List<CompactionInfo> findReadyToClean() throws MetaException { Connection dbConn = getDbConn(); List<CompactionInfo> rc = new ArrayList<CompactionInfo>(); try { Statement stmt = dbConn.createStatement(); String s = "select cq_id, cq_database, cq_table, cq_partition, " + "cq_type, cq_run_as from COMPACTION_QUEUE where cq_state = '" + READY_FOR_CLEANING + "'"; LOG.debug("Going to execute query <" + s + ">"); ResultSet rs = stmt.executeQuery(s); while (rs.next()) { CompactionInfo info = new CompactionInfo(); info.id = rs.getLong(1); info.dbname = rs.getString(2); info.tableName = rs.getString(3); info.partName = rs.getString(4); switch (rs.getString(5).charAt(0)) { case MAJOR_TYPE: info.type = CompactionType.MAJOR; break; case MINOR_TYPE: info.type = CompactionType.MINOR; break; default: throw new MetaException("Unexpected compaction type " + rs.getString(5)); } info.runAs = rs.getString(6); rc.add(info); } LOG.debug("Going to rollback"); dbConn.rollback(); return rc; } catch (SQLException e) { LOG.error("Unable to select next element for cleaning, " + e.getMessage()); try { LOG.debug("Going to rollback"); dbConn.rollback(); } catch (SQLException e1) { } throw new MetaException( "Unable to connect to transaction database " + StringUtils.stringifyException(e)); } finally { closeDbConn(dbConn); } }
/** * This will mark an entry in the queue as compacted and put it in the ready to clean state. * * @param info info on the compaciton entry to mark as compacted. */ public void markCompacted(CompactionInfo info) throws MetaException { try { Connection dbConn = getDbConn(); try { Statement stmt = dbConn.createStatement(); String s = "update COMPACTION_QUEUE set cq_state = '" + READY_FOR_CLEANING + "', " + "cq_worker_id = null where cq_id = " + info.id; LOG.debug("Going to execute update <" + s + ">"); if (stmt.executeUpdate(s) != 1) { LOG.error("Unable to update compaction record"); LOG.debug("Going to rollback"); dbConn.rollback(); } LOG.debug("Going to commit"); dbConn.commit(); } catch (SQLException e) { try { LOG.error("Unable to update compaction queue " + e.getMessage()); LOG.debug("Going to rollback"); dbConn.rollback(); } catch (SQLException e1) { } detectDeadlock(e, "markCompacted"); throw new MetaException( "Unable to connect to transaction database " + StringUtils.stringifyException(e)); } finally { closeDbConn(dbConn); } } catch (DeadlockException e) { markCompacted(info); } finally { deadlockCnt = 0; } }
/** * This will look through the completed_txn_components table and look for partitions or tables * that may be ready for compaction. Also, look through txns and txn_components tables for aborted * transactions that we should add to the list. * * @param maxAborted Maximum number of aborted queries to allow before marking this as a potential * compaction. * @return list of CompactionInfo structs. These will not have id, type, or runAs set since these * are only potential compactions not actual ones. */ public Set<CompactionInfo> findPotentialCompactions(int maxAborted) throws MetaException { Connection dbConn = getDbConn(); Set<CompactionInfo> response = new HashSet<CompactionInfo>(); try { Statement stmt = dbConn.createStatement(); // Check for completed transactions String s = "select distinct ctc_database, ctc_table, " + "ctc_partition from COMPLETED_TXN_COMPONENTS"; LOG.debug("Going to execute query <" + s + ">"); ResultSet rs = stmt.executeQuery(s); while (rs.next()) { CompactionInfo info = new CompactionInfo(); info.dbname = rs.getString(1); info.tableName = rs.getString(2); info.partName = rs.getString(3); response.add(info); } // Check for aborted txns s = "select tc_database, tc_table, tc_partition " + "from TXNS, TXN_COMPONENTS " + "where txn_id = tc_txnid and txn_state = '" + TXN_ABORTED + "' " + "group by tc_database, tc_table, tc_partition " + "having count(*) > " + maxAborted; LOG.debug("Going to execute query <" + s + ">"); rs = stmt.executeQuery(s); while (rs.next()) { CompactionInfo info = new CompactionInfo(); info.dbname = rs.getString(1); info.tableName = rs.getString(2); info.partName = rs.getString(3); info.tooManyAborts = true; response.add(info); } LOG.debug("Going to rollback"); dbConn.rollback(); } catch (SQLException e) { LOG.error("Unable to connect to transaction database " + e.getMessage()); } finally { closeDbConn(dbConn); } return response; }
/** Clean up aborted transactions from txns that have no components in txn_components. */ public void cleanEmptyAbortedTxns() throws MetaException { try { Connection dbConn = getDbConn(); try { Statement stmt = dbConn.createStatement(); String s = "select txn_id from TXNS where " + "txn_id not in (select tc_txnid from TXN_COMPONENTS) and " + "txn_state = '" + TXN_ABORTED + "'"; LOG.debug("Going to execute query <" + s + ">"); ResultSet rs = stmt.executeQuery(s); Set<Long> txnids = new HashSet<Long>(); while (rs.next()) txnids.add(rs.getLong(1)); if (txnids.size() > 0) { StringBuffer buf = new StringBuffer("delete from TXNS where txn_id in ("); boolean first = true; for (long tid : txnids) { if (first) first = false; else buf.append(", "); buf.append(tid); } buf.append(")"); LOG.debug("Going to execute update <" + buf.toString() + ">"); int rc = stmt.executeUpdate(buf.toString()); LOG.debug("Removed " + rc + " records from txns"); LOG.debug("Going to commit"); dbConn.commit(); } } catch (SQLException e) { LOG.error("Unable to delete from txns table " + e.getMessage()); LOG.debug("Going to rollback"); try { dbConn.rollback(); } catch (SQLException e1) { } detectDeadlock(e, "cleanEmptyAbortedTxns"); throw new MetaException( "Unable to connect to transaction database " + StringUtils.stringifyException(e)); } finally { closeDbConn(dbConn); } } catch (DeadlockException e) { cleanEmptyAbortedTxns(); } finally { deadlockCnt = 0; } }
/** * This call will return all compaction queue entries assigned to a worker but over the timeout * back to the initiated state. This should be called by the initiator on start up and * occasionally when running to clean up after dead threads. At start up {@link * #revokeFromLocalWorkers(String)} should be called first. * * @param timeout number of milliseconds since start time that should elapse before a worker is * declared dead. */ public void revokeTimedoutWorkers(long timeout) throws MetaException { try { Connection dbConn = getDbConn(); long latestValidStart = System.currentTimeMillis() - timeout; try { Statement stmt = dbConn.createStatement(); String s = "update COMPACTION_QUEUE set cq_worker_id = null, cq_start = null, cq_state = '" + INITIATED_STATE + "' where cq_state = '" + WORKING_STATE + "' and cq_start < " + latestValidStart; LOG.debug("Going to execute update <" + s + ">"); // It isn't an error if the following returns no rows, as the local workers could have died // with nothing assigned to them. stmt.executeUpdate(s); LOG.debug("Going to commit"); dbConn.commit(); } catch (SQLException e) { try { LOG.error( "Unable to change dead worker's records back to initiated state " + e.getMessage()); LOG.debug("Going to rollback"); dbConn.rollback(); } catch (SQLException e1) { } detectDeadlock(e, "revokeTimedoutWorkers"); throw new MetaException( "Unable to connect to transaction database " + StringUtils.stringifyException(e)); } finally { closeDbConn(dbConn); } } catch (DeadlockException e) { revokeTimedoutWorkers(timeout); } finally { deadlockCnt = 0; } }
/** * This will remove an entry from the queue after it has been compacted. * * @param info info on the compaction entry to remove */ public void markCleaned(CompactionInfo info) throws MetaException { try { Connection dbConn = getDbConn(); try { Statement stmt = dbConn.createStatement(); String s = "delete from COMPACTION_QUEUE where cq_id = " + info.id; LOG.debug("Going to execute update <" + s + ">"); if (stmt.executeUpdate(s) != 1) { LOG.error("Unable to delete compaction record"); LOG.debug("Going to rollback"); dbConn.rollback(); } // Remove entries from completed_txn_components as well, so we don't start looking there // again. s = "delete from COMPLETED_TXN_COMPONENTS where ctc_database = '" + info.dbname + "' and " + "ctc_table = '" + info.tableName + "'"; if (info.partName != null) { s += " and ctc_partition = '" + info.partName + "'"; } LOG.debug("Going to execute update <" + s + ">"); if (stmt.executeUpdate(s) < 1) { LOG.error( "Expected to remove at least one row from completed_txn_components when " + "marking compaction entry as clean!"); } s = "select txn_id from TXNS, TXN_COMPONENTS where txn_id = tc_txnid and txn_state = '" + TXN_ABORTED + "' and tc_database = '" + info.dbname + "' and tc_table = '" + info.tableName + "'"; if (info.partName != null) s += " and tc_partition = '" + info.partName + "'"; LOG.debug("Going to execute update <" + s + ">"); ResultSet rs = stmt.executeQuery(s); Set<Long> txnids = new HashSet<Long>(); while (rs.next()) txnids.add(rs.getLong(1)); if (txnids.size() > 0) { // Remove entries from txn_components, as there may be aborted txn components StringBuffer buf = new StringBuffer(); buf.append("delete from TXN_COMPONENTS where tc_txnid in ("); boolean first = true; for (long id : txnids) { if (first) first = false; else buf.append(", "); buf.append(id); } buf.append(") and tc_database = '"); buf.append(info.dbname); buf.append("' and tc_table = '"); buf.append(info.tableName); buf.append("'"); if (info.partName != null) { buf.append(" and tc_partition = '"); buf.append(info.partName); buf.append("'"); } LOG.debug("Going to execute update <" + buf.toString() + ">"); int rc = stmt.executeUpdate(buf.toString()); LOG.debug("Removed " + rc + " records from txn_components"); // Don't bother cleaning from the txns table. A separate call will do that. We don't // know here which txns still have components from other tables or partitions in the // table, so we don't know which ones we can and cannot clean. } LOG.debug("Going to commit"); dbConn.commit(); } catch (SQLException e) { try { LOG.error("Unable to delete from compaction queue " + e.getMessage()); LOG.debug("Going to rollback"); dbConn.rollback(); } catch (SQLException e1) { } detectDeadlock(e, "markCleaned"); throw new MetaException( "Unable to connect to transaction database " + StringUtils.stringifyException(e)); } finally { closeDbConn(dbConn); } } catch (DeadlockException e) { markCleaned(info); } finally { deadlockCnt = 0; } }
/** * This will grab the next compaction request off of the queue, and assign it to the worker. * * @param workerId id of the worker calling this, will be recorded in the db * @return an info element for this compaction request, or null if there is no work to do now. */ public CompactionInfo findNextToCompact(String workerId) throws MetaException { try { Connection dbConn = getDbConn(); CompactionInfo info = new CompactionInfo(); try { Statement stmt = dbConn.createStatement(); String s = "select cq_id, cq_database, cq_table, cq_partition, " + "cq_type from COMPACTION_QUEUE where cq_state = '" + INITIATED_STATE + "' for update"; LOG.debug("Going to execute query <" + s + ">"); ResultSet rs = stmt.executeQuery(s); if (!rs.next()) { LOG.debug("No compactions found ready to compact"); dbConn.rollback(); return null; } info.id = rs.getLong(1); info.dbname = rs.getString(2); info.tableName = rs.getString(3); info.partName = rs.getString(4); switch (rs.getString(5).charAt(0)) { case MAJOR_TYPE: info.type = CompactionType.MAJOR; break; case MINOR_TYPE: info.type = CompactionType.MINOR; break; default: throw new MetaException("Unexpected compaction type " + rs.getString(5)); } // Now, update this record as being worked on by this worker. long now = System.currentTimeMillis(); s = "update COMPACTION_QUEUE set cq_worker_id = '" + workerId + "', " + "cq_start = " + now + ", cq_state = '" + WORKING_STATE + "' where cq_id = " + info.id; LOG.debug("Going to execute update <" + s + ">"); if (stmt.executeUpdate(s) != 1) { LOG.error("Unable to update compaction record"); LOG.debug("Going to rollback"); dbConn.rollback(); } LOG.debug("Going to commit"); dbConn.commit(); return info; } catch (SQLException e) { LOG.error("Unable to select next element for compaction, " + e.getMessage()); try { LOG.debug("Going to rollback"); dbConn.rollback(); } catch (SQLException e1) { } detectDeadlock(e, "findNextToCompact"); throw new MetaException( "Unable to connect to transaction database " + StringUtils.stringifyException(e)); } finally { closeDbConn(dbConn); } } catch (DeadlockException e) { return findNextToCompact(workerId); } finally { deadlockCnt = 0; } }