Search in sources :

Example 1 with CommitFailedException

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();
        }
    }
}
Also used : ReentrantLock(java.util.concurrent.locks.ReentrantLock) HdfsEnvironment(com.facebook.presto.hive.HdfsEnvironment) LocationProviders(org.apache.iceberg.LocationProviders) LoadingCache(com.google.common.cache.LoadingCache) IcebergUtil.isIcebergTable(com.facebook.presto.iceberg.IcebergUtil.isIcebergTable) PrestoPrincipal(com.facebook.presto.spi.security.PrestoPrincipal) HiveSchemaUtil(org.apache.iceberg.hive.HiveSchemaUtil) MetastoreContext(com.facebook.presto.hive.metastore.MetastoreContext) ICEBERG_TABLE_TYPE_VALUE(org.apache.iceberg.BaseMetastoreTableOperations.ICEBERG_TABLE_TYPE_VALUE) TABLE_TYPE_PROP(org.apache.iceberg.BaseMetastoreTableOperations.TABLE_TYPE_PROP) TableMetadata(org.apache.iceberg.TableMetadata) LocationProvider(org.apache.iceberg.io.LocationProvider) TableOperations(org.apache.iceberg.TableOperations) SchemaTableName(com.facebook.presto.spi.SchemaTableName) ExtendedHiveMetastore(com.facebook.presto.hive.metastore.ExtendedHiveMetastore) Map(java.util.Map) ICEBERG_INVALID_METADATA(com.facebook.presto.iceberg.IcebergErrorCode.ICEBERG_INVALID_METADATA) HdfsContext(com.facebook.presto.hive.HdfsContext) TableMetadataParser(org.apache.iceberg.TableMetadataParser) CommitFailedException(org.apache.iceberg.exceptions.CommitFailedException) StorageFormat(com.facebook.presto.hive.metastore.StorageFormat) PrincipalPrivileges(com.facebook.presto.hive.metastore.PrincipalPrivileges) FileInputFormat(org.apache.hadoop.mapred.FileInputFormat) NestedField(org.apache.iceberg.types.Types.NestedField) ImmutableList.toImmutableList(com.google.common.collect.ImmutableList.toImmutableList) SELECT(com.facebook.presto.hive.metastore.HivePrivilegeInfo.HivePrivilege.SELECT) String.format(java.lang.String.format) Preconditions.checkState(com.google.common.base.Preconditions.checkState) CacheLoader(com.google.common.cache.CacheLoader) Objects(java.util.Objects) List(java.util.List) PrestoTableType(com.facebook.presto.hive.metastore.PrestoTableType) Optional(java.util.Optional) HivePrivilegeInfo(com.facebook.presto.hive.metastore.HivePrivilegeInfo) CacheBuilder(com.google.common.cache.CacheBuilder) FileOutputFormat(org.apache.hadoop.mapred.FileOutputFormat) TableMetadataParser.getFileExtension(org.apache.iceberg.TableMetadataParser.getFileExtension) Logger(com.facebook.airlift.log.Logger) Table(com.facebook.presto.hive.metastore.Table) Column(com.facebook.presto.hive.metastore.Column) HiveType(com.facebook.presto.hive.HiveType) WRITE_METADATA_LOCATION(org.apache.iceberg.TableProperties.WRITE_METADATA_LOCATION) OutputFile(org.apache.iceberg.io.OutputFile) HashMap(java.util.HashMap) PrestoException(com.facebook.presto.spi.PrestoException) AtomicReference(java.util.concurrent.atomic.AtomicReference) Objects.requireNonNull(java.util.Objects.requireNonNull) TABLE_COMMENT(com.facebook.presto.hive.HiveMetadata.TABLE_COMMENT) METADATA_COMPRESSION_DEFAULT(org.apache.iceberg.TableProperties.METADATA_COMPRESSION_DEFAULT) DELETE(com.facebook.presto.hive.metastore.HivePrivilegeInfo.HivePrivilege.DELETE) ImmutableMultimap(com.google.common.collect.ImmutableMultimap) Nullable(javax.annotation.Nullable) USER(com.facebook.presto.spi.security.PrincipalType.USER) INSERT(com.facebook.presto.hive.metastore.HivePrivilegeInfo.HivePrivilege.INSERT) UPDATE(com.facebook.presto.hive.metastore.HivePrivilegeInfo.HivePrivilege.UPDATE) METADATA_COMPRESSION(org.apache.iceberg.TableProperties.METADATA_COMPRESSION) ReentrantLock(java.util.concurrent.locks.ReentrantLock) LazySimpleSerDe(org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe) Integer.parseInt(java.lang.Integer.parseInt) TimeUnit(java.util.concurrent.TimeUnit) UUID.randomUUID(java.util.UUID.randomUUID) TableNotFoundException(com.facebook.presto.spi.TableNotFoundException) Tasks(org.apache.iceberg.util.Tasks) FileIO(org.apache.iceberg.io.FileIO) NotThreadSafe(javax.annotation.concurrent.NotThreadSafe) HivePrivilegeInfo(com.facebook.presto.hive.metastore.HivePrivilegeInfo) IcebergUtil.isIcebergTable(com.facebook.presto.iceberg.IcebergUtil.isIcebergTable) Table(com.facebook.presto.hive.metastore.Table) PrincipalPrivileges(com.facebook.presto.hive.metastore.PrincipalPrivileges) HashMap(java.util.HashMap) CommitFailedException(org.apache.iceberg.exceptions.CommitFailedException) PrestoException(com.facebook.presto.spi.PrestoException) TableNotFoundException(com.facebook.presto.spi.TableNotFoundException) CommitFailedException(org.apache.iceberg.exceptions.CommitFailedException) PrestoPrincipal(com.facebook.presto.spi.security.PrestoPrincipal)

Example 2 with CommitFailedException

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;
}
Also used : LockComponent(org.apache.hadoop.hive.metastore.api.LockComponent) LockResponse(org.apache.hadoop.hive.metastore.api.LockResponse) AtomicReference(java.util.concurrent.atomic.AtomicReference) LockState(org.apache.hadoop.hive.metastore.api.LockState) LockRequest(org.apache.hadoop.hive.metastore.api.LockRequest) CommitFailedException(org.apache.iceberg.exceptions.CommitFailedException) VisibleForTesting(org.apache.iceberg.relocated.com.google.common.annotations.VisibleForTesting)

Example 3 with CommitFailedException

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);
    }
}
Also used : ReentrantLock(java.util.concurrent.locks.ReentrantLock) TException(org.apache.thrift.TException) Table(org.apache.hadoop.hive.metastore.api.Table) AlreadyExistsException(org.apache.iceberg.exceptions.AlreadyExistsException) UnknownHostException(java.net.UnknownHostException) CommitStateUnknownException(org.apache.iceberg.exceptions.CommitStateUnknownException) ImmutableMap(org.apache.iceberg.relocated.com.google.common.collect.ImmutableMap) CommitFailedException(org.apache.iceberg.exceptions.CommitFailedException)

Example 4 with CommitFailedException

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();
}
Also used : TableIdentifier(org.apache.iceberg.catalog.TableIdentifier) Table(org.apache.iceberg.Table) UpdateSchema(org.apache.iceberg.UpdateSchema) CommitFailedException(org.apache.iceberg.exceptions.CommitFailedException) Test(org.junit.Test)

Example 5 with CommitFailedException

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));
}
Also used : ExecutorService(java.util.concurrent.ExecutorService) CommitFailedException(org.apache.iceberg.exceptions.CommitFailedException) LockRequest(org.apache.hadoop.hive.metastore.api.LockRequest) Test(org.junit.Test)

Aggregations

CommitFailedException (org.apache.iceberg.exceptions.CommitFailedException)5 AtomicReference (java.util.concurrent.atomic.AtomicReference)2 ReentrantLock (java.util.concurrent.locks.ReentrantLock)2 Test (org.junit.Test)2 Logger (com.facebook.airlift.log.Logger)1 HdfsContext (com.facebook.presto.hive.HdfsContext)1 HdfsEnvironment (com.facebook.presto.hive.HdfsEnvironment)1 TABLE_COMMENT (com.facebook.presto.hive.HiveMetadata.TABLE_COMMENT)1 HiveType (com.facebook.presto.hive.HiveType)1 Column (com.facebook.presto.hive.metastore.Column)1 ExtendedHiveMetastore (com.facebook.presto.hive.metastore.ExtendedHiveMetastore)1 HivePrivilegeInfo (com.facebook.presto.hive.metastore.HivePrivilegeInfo)1 DELETE (com.facebook.presto.hive.metastore.HivePrivilegeInfo.HivePrivilege.DELETE)1 INSERT (com.facebook.presto.hive.metastore.HivePrivilegeInfo.HivePrivilege.INSERT)1 SELECT (com.facebook.presto.hive.metastore.HivePrivilegeInfo.HivePrivilege.SELECT)1 UPDATE (com.facebook.presto.hive.metastore.HivePrivilegeInfo.HivePrivilege.UPDATE)1 MetastoreContext (com.facebook.presto.hive.metastore.MetastoreContext)1 PrestoTableType (com.facebook.presto.hive.metastore.PrestoTableType)1 PrincipalPrivileges (com.facebook.presto.hive.metastore.PrincipalPrivileges)1 StorageFormat (com.facebook.presto.hive.metastore.StorageFormat)1