use of org.apache.iceberg.exceptions.CommitFailedException in project presto by prestodb.
the class HiveTableOperations method commit.
@Override
public void commit(@Nullable TableMetadata base, TableMetadata metadata) {
requireNonNull(metadata, "metadata is null");
// if the metadata is already out of date, reject it
if (!Objects.equals(base, current())) {
throw new CommitFailedException("Cannot commit: stale table metadata for %s", getSchemaTableName());
}
// if the metadata is not changed, return early
if (Objects.equals(base, metadata)) {
return;
}
String newMetadataLocation = writeNewMetadata(metadata, version + 1);
Table table;
// getting a process-level lock per table to avoid concurrent commit attempts to the same table from the same
// JVM process, which would result in unnecessary and costly HMS lock acquisition requests
Optional<Long> lockId = Optional.empty();
ReentrantLock tableLevelMutex = commitLockCache.getUnchecked(database + "." + tableName);
tableLevelMutex.lock();
try {
try {
lockId = Optional.of(metastore.lock(metastoreContext, database, tableName));
if (base == null) {
String tableComment = metadata.properties().get(TABLE_COMMENT);
Map<String, String> parameters = new HashMap<>();
parameters.put("EXTERNAL", "TRUE");
parameters.put(TABLE_TYPE_PROP, ICEBERG_TABLE_TYPE_VALUE);
parameters.put(METADATA_LOCATION, newMetadataLocation);
if (tableComment != null) {
parameters.put(TABLE_COMMENT, tableComment);
}
Table.Builder builder = Table.builder().setDatabaseName(database).setTableName(tableName).setOwner(owner.orElseThrow(() -> new IllegalStateException("Owner not set"))).setTableType(PrestoTableType.EXTERNAL_TABLE).setDataColumns(toHiveColumns(metadata.schema().columns())).withStorage(storage -> storage.setLocation(metadata.location())).withStorage(storage -> storage.setStorageFormat(STORAGE_FORMAT)).setParameters(parameters);
table = builder.build();
} else {
Table currentTable = getTable();
checkState(currentMetadataLocation != null, "No current metadata location for existing table");
String metadataLocation = currentTable.getParameters().get(METADATA_LOCATION);
if (!currentMetadataLocation.equals(metadataLocation)) {
throw new CommitFailedException("Metadata location [%s] is not same as table metadata location [%s] for %s", currentMetadataLocation, metadataLocation, getSchemaTableName());
}
table = Table.builder(currentTable).setDataColumns(toHiveColumns(metadata.schema().columns())).withStorage(storage -> storage.setLocation(metadata.location())).setParameter(METADATA_LOCATION, newMetadataLocation).setParameter(PREVIOUS_METADATA_LOCATION, currentMetadataLocation).build();
}
} catch (RuntimeException e) {
try {
io().deleteFile(newMetadataLocation);
} catch (RuntimeException exception) {
e.addSuppressed(exception);
}
throw e;
}
PrestoPrincipal owner = new PrestoPrincipal(USER, table.getOwner());
PrincipalPrivileges privileges = new PrincipalPrivileges(ImmutableMultimap.<String, HivePrivilegeInfo>builder().put(table.getOwner(), new HivePrivilegeInfo(SELECT, true, owner, owner)).put(table.getOwner(), new HivePrivilegeInfo(INSERT, true, owner, owner)).put(table.getOwner(), new HivePrivilegeInfo(UPDATE, true, owner, owner)).put(table.getOwner(), new HivePrivilegeInfo(DELETE, true, owner, owner)).build(), ImmutableMultimap.of());
if (base == null) {
metastore.createTable(metastoreContext, table, privileges);
} else {
metastore.replaceTable(metastoreContext, database, tableName, table, privileges);
}
} finally {
shouldRefresh = true;
try {
lockId.ifPresent(id -> metastore.unlock(metastoreContext, id));
} catch (Exception e) {
log.error(e, "Failed to unlock: %s", lockId.orElse(null));
} finally {
tableLevelMutex.unlock();
}
}
}
use of org.apache.iceberg.exceptions.CommitFailedException in project hive by apache.
the class HiveTableOperations method acquireLock.
@VisibleForTesting
long acquireLock() throws UnknownHostException, TException, InterruptedException {
final LockComponent lockComponent = new LockComponent(LockType.EXCL_WRITE, LockLevel.TABLE, database);
lockComponent.setTablename(tableName);
final LockRequest lockRequest = new LockRequest(Lists.newArrayList(lockComponent), System.getProperty("user.name"), InetAddress.getLocalHost().getHostName());
LockResponse lockResponse = metaClients.run(client -> client.lock(lockRequest));
AtomicReference<LockState> state = new AtomicReference<>(lockResponse.getState());
long lockId = lockResponse.getLockid();
final long start = System.currentTimeMillis();
long duration = 0;
boolean timeout = false;
try {
if (state.get().equals(LockState.WAITING)) {
// Retry count is the typical "upper bound of retries" for Tasks.run() function. In fact, the maximum number of
// attempts the Tasks.run() would try is `retries + 1`. Here, for checking locks, we use timeout as the
// upper bound of retries. So it is just reasonable to set a large retry count. However, if we set
// Integer.MAX_VALUE, the above logic of `retries + 1` would overflow into Integer.MIN_VALUE. Hence,
// the retry is set conservatively as `Integer.MAX_VALUE - 100` so it doesn't hit any boundary issues.
Tasks.foreach(lockId).retry(Integer.MAX_VALUE - 100).exponentialBackoff(lockCheckMinWaitTime, lockCheckMaxWaitTime, lockAcquireTimeout, 1.5).throwFailureWhenFinished().onlyRetryOn(WaitingForLockException.class).run(id -> {
try {
LockResponse response = metaClients.run(client -> client.checkLock(id));
LockState newState = response.getState();
state.set(newState);
if (newState.equals(LockState.WAITING)) {
throw new WaitingForLockException("Waiting for lock.");
}
} catch (InterruptedException e) {
// Clear the interrupt status flag
Thread.interrupted();
LOG.warn("Interrupted while waiting for lock.", e);
}
}, TException.class);
}
} catch (WaitingForLockException waitingForLockException) {
timeout = true;
duration = System.currentTimeMillis() - start;
} finally {
if (!state.get().equals(LockState.ACQUIRED)) {
unlock(Optional.of(lockId));
}
}
// timeout and do not have lock acquired
if (timeout && !state.get().equals(LockState.ACQUIRED)) {
throw new CommitFailedException("Timed out after %s ms waiting for lock on %s.%s", duration, database, tableName);
}
if (!state.get().equals(LockState.ACQUIRED)) {
throw new CommitFailedException("Could not acquire the lock on %s.%s, " + "lock request ended in state %s", database, tableName, state);
}
return lockId;
}
use of org.apache.iceberg.exceptions.CommitFailedException in project hive by apache.
the class HiveTableOperations method doCommit.
@SuppressWarnings("checkstyle:CyclomaticComplexity")
@Override
protected void doCommit(TableMetadata base, TableMetadata metadata) {
String newMetadataLocation = writeNewMetadata(metadata, currentVersion() + 1);
boolean hiveEngineEnabled = hiveEngineEnabled(metadata, conf);
boolean keepHiveStats = conf.getBoolean(KEEP_HIVE_STATS, false);
CommitStatus commitStatus = CommitStatus.FAILURE;
boolean updateHiveTable = false;
Optional<Long> lockId = Optional.empty();
// getting a process-level lock per table to avoid concurrent commit attempts to the same table from the same
// JVM process, which would result in unnecessary and costly HMS lock acquisition requests
ReentrantLock tableLevelMutex = commitLockCache.get(fullName, t -> new ReentrantLock(true));
tableLevelMutex.lock();
try {
lockId = Optional.of(acquireLock());
// TODO add lock heart beating for cases where default lock timeout is too low.
Table tbl = loadHmsTable();
if (tbl != null) {
// If we try to create the table but the metadata location is already set, then we had a concurrent commit
if (base == null && tbl.getParameters().get(BaseMetastoreTableOperations.METADATA_LOCATION_PROP) != null) {
throw new AlreadyExistsException("Table already exists: %s.%s", database, tableName);
}
updateHiveTable = true;
LOG.debug("Committing existing table: {}", fullName);
} else {
tbl = newHmsTable();
LOG.debug("Committing new table: {}", fullName);
}
// set to pickup any schema changes
tbl.setSd(storageDescriptor(metadata, hiveEngineEnabled));
String metadataLocation = tbl.getParameters().get(METADATA_LOCATION_PROP);
String baseMetadataLocation = base != null ? base.metadataFileLocation() : null;
if (!Objects.equals(baseMetadataLocation, metadataLocation)) {
throw new CommitFailedException("Base metadata location '%s' is not same as the current table metadata location '%s' for %s.%s", baseMetadataLocation, metadataLocation, database, tableName);
}
// get Iceberg props that have been removed
Set<String> removedProps = Collections.emptySet();
if (base != null) {
removedProps = base.properties().keySet().stream().filter(key -> !metadata.properties().containsKey(key)).collect(Collectors.toSet());
}
Map<String, String> summary = Optional.ofNullable(metadata.currentSnapshot()).map(Snapshot::summary).orElseGet(ImmutableMap::of);
setHmsTableParameters(newMetadataLocation, tbl, metadata.properties(), removedProps, hiveEngineEnabled, summary);
if (!keepHiveStats) {
StatsSetupConst.setBasicStatsState(tbl.getParameters(), StatsSetupConst.FALSE);
StatsSetupConst.clearColumnStatsState(tbl.getParameters());
}
try {
persistTable(tbl, updateHiveTable);
commitStatus = CommitStatus.SUCCESS;
} catch (Throwable persistFailure) {
LOG.error("Cannot tell if commit to {}.{} succeeded, attempting to reconnect and check.", database, tableName, persistFailure);
commitStatus = checkCommitStatus(newMetadataLocation, metadata);
switch(commitStatus) {
case SUCCESS:
break;
case FAILURE:
throw persistFailure;
case UNKNOWN:
throw new CommitStateUnknownException(persistFailure);
}
}
} catch (org.apache.hadoop.hive.metastore.api.AlreadyExistsException e) {
throw new AlreadyExistsException("Table already exists: %s.%s", database, tableName);
} catch (TException | UnknownHostException e) {
if (e.getMessage() != null && e.getMessage().contains("Table/View 'HIVE_LOCKS' does not exist")) {
throw new RuntimeException("Failed to acquire locks from metastore because 'HIVE_LOCKS' doesn't " + "exist, this probably happened when using embedded metastore or doesn't create a " + "transactional meta table. To fix this, use an alternative metastore", e);
}
throw new RuntimeException(String.format("Metastore operation failed for %s.%s", database, tableName), e);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("Interrupted during commit", e);
} finally {
cleanupMetadataAndUnlock(commitStatus, newMetadataLocation, lockId, tableLevelMutex);
}
}
use of org.apache.iceberg.exceptions.CommitFailedException in project hive by apache.
the class TestHiveIcebergStorageHandlerNoScan method testAlterTableAddColumnsConcurrently.
@Test
public void testAlterTableAddColumnsConcurrently() throws Exception {
TableIdentifier identifier = TableIdentifier.of("default", "customers");
testTables.createTable(shell, identifier.name(), HiveIcebergStorageHandlerTestUtils.CUSTOMER_SCHEMA, SPEC, FileFormat.PARQUET, ImmutableList.of());
org.apache.iceberg.Table icebergTable = testTables.loadTable(identifier);
UpdateSchema updateSchema = icebergTable.updateSchema().addColumn("newfloatcol", Types.FloatType.get());
shell.executeStatement("ALTER TABLE default.customers ADD COLUMNS " + "(newintcol int, newstringcol string COMMENT 'Column with description')");
try {
updateSchema.commit();
Assert.fail();
} catch (CommitFailedException expectedException) {
// Should fail to commit the addition of newfloatcol as another commit went in from Hive side adding 2 other cols
}
// Same verification should be applied, as we expect newfloatcol NOT to be added to the schema
verifyAlterTableAddColumnsTests();
}
use of org.apache.iceberg.exceptions.CommitFailedException in project hive by apache.
the class TestHiveCommitLocks method testTableLevelProcessLockBlocksConcurrentHMSRequestsForSameTable.
@Test
public void testTableLevelProcessLockBlocksConcurrentHMSRequestsForSameTable() throws Exception {
int numConcurrentCommits = 10;
// resetting the spy client to forget about prior call history
reset(spyClient);
// simulate several concurrent commit operations on the same table
ExecutorService executor = Executors.newFixedThreadPool(numConcurrentCommits);
IntStream.range(0, numConcurrentCommits).forEach(i -> executor.submit(() -> {
try {
spyOps.doCommit(metadataV2, metadataV1);
} catch (CommitFailedException e) {
// failures are expected here when checking the base version
// it's no problem, we're not testing the actual commit success here, only the HMS lock acquisition attempts
}
}));
executor.shutdown();
executor.awaitTermination(30, TimeUnit.SECONDS);
// intra-process commits to the same table should be serialized now
// i.e. no thread should receive WAITING state from HMS and have to call checkLock periodically
verify(spyClient, never()).checkLock(any(Long.class));
// all threads eventually got their turn
verify(spyClient, times(numConcurrentCommits)).lock(any(LockRequest.class));
}
Aggregations