[
https://issues.apache.org/jira/browse/HIVE-29843?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Work on HIVE-29843 started by Zhenqi Li.
----------------------------------------
> Concurrent create_database requests may recursively delete a shared database
> directory
> --------------------------------------------------------------------------------------
>
> Key: HIVE-29843
> URL: https://issues.apache.org/jira/browse/HIVE-29843
> Project: Hive
> Issue Type: Bug
> Components: Standalone Metastore
> Affects Versions: 4.0.0-beta-1
> Environment: Hive Metastore: 4.0.0-beta-1
> MySQL: 8.0.30
> Hadoop: 3.3.1
> FileSystem: S3A-compatible object storage
> Standalone reproduction object storage: MinIO
> Iceberg: 1.6.1
> Concurrency: 16 independent Hive Metastore clients
> Reporter: Zhenqi Li
> Assignee: Zhenqi Li
> Priority: Major
>
> When multiple clients concurrently initialize the same Iceberg namespace,
> Hive Metastore may process identical {{create_database}} requests and create
> the same database directory. Only one request can successfully insert the
> database metadata; the other requests fail because of the database unique
> constraint.
> The failed requests then use a local {{madeDir}} or {{madeExternalDir}} flag
> to decide whether to recursively clean up the entire database directory.
> However, this flag only records that the request created or observed a usable
> directory; it does not prove that the request still has exclusive ownership
> of that directory. Another request may already have committed metadata for
> the same path or written data below it.
> As a result, a failed {{create_database}} request may move or delete a shared
> database directory, leaving valid Metastore metadata pointing to missing
> object-storage files.
> h2. Affected Versions
> The issue was reproduced against Hive Metastore {{{}4.0.0-beta-1{}}}.
> Source inspection found the same directory-ownership and rollback-cleanup
> pattern in Hive branches from {{1.0}} through {{{}4.2.1{}}}. I did not find
> an upstream fix specifically addressing the {{create_database}} directory
> ownership race.
> h2. Description
> Multiple clients initializing the same Iceberg namespace can concurrently
> call Hive Metastore with identical {{create_database}} requests.
> A typical target path is:
> {code}
> Unable to find source-code formatter for language: text. Available languages
> are: actionscript, ada, applescript, bash, c, c#, c++, cpp, css, erlang, go,
> groovy, haskell, html, java, javascript, js, json, lua, none, nyan, objc,
> perl, php, python, r, rainbow, ruby, scala, sh, sql, swift, visualbasic, xml,
> yamls3a://warehouse/iceberg/test1.db
> {code}
> The following sequence can occur:
> # Multiple requests check that the database does not exist.
> # Multiple requests check that the target directory does not exist.
> # Multiple requests call {{mkdirs}} for the same path.
> # Multiple requests set a local {{madeDir}} or {{madeExternalDir}} flag to
> {{{}true{}}}.
> # One request successfully inserts the database into the Metastore {{DBS}}
> table.
> # The other requests fail because of the {{DBS.UNIQUE_DATABASE}} unique
> constraint.
> # The failed requests enter the rollback cleanup path.
> # Based on their local flag, they assume that they own the database
> directory.
> # A failed request recursively moves or deletes the entire database
> directory.
> There is also a more general interleaving, similar to the one documented in
> HIVE-23437:
> # Request R1 creates the directory and is paused before committing metadata.
> # Request R2 observes that the directory already exists, so R2 does not
> create it.
> # R2 successfully commits database metadata referencing that directory.
> # R1 resumes, loses the metadata race, and rolls back.
> # R1 still has {{madeDir=true}} and recursively deletes the directory
> already adopted by R2.
> This second interleaving shows that even a perfectly atomic {{mkdir}}
> implementation would not be sufficient. Directory creation provenance is not
> equivalent to current deletion ownership.
> h2. Steps to Reproduce
> h3. 1. Prepare Hive Metastore
> Start Hive Metastore with a MySQL metadata database and S3A-compatible object
> storage. Confirm that the target database path does not exist.
> For example:
> {code}
> Unable to find source-code formatter for language: text. Available languages
> are: actionscript, ada, applescript, bash, c, c#, c++, cpp, css, erlang, go,
> groovy, haskell, html, java, javascript, js, json, lua, none, nyan, objc,
> perl, php, python, r, rainbow, ruby, scala, sh, sql, swift, visualbasic, xml,
> yamls3a://warehouse/hms_direct_race.db
> {code}
> h3. 2. Concurrently invoke create_database
> Use multiple independent Hive Metastore clients and concurrently call:
> {code:java}
> client.createDatabase(database);
> {code}
> All requests must use the same catalog, database name, and location. The
> standalone reproduction used:
> {code}
> Unable to find source-code formatter for language: text. Available languages
> are: actionscript, ada, applescript, bash, c, c#, c++, cpp, css, erlang, go,
> groovy, haskell, html, java, javascript, js, json, lua, none, nyan, objc,
> perl, php, python, r, rainbow, ruby, scala, sh, sql, swift, visualbasic, xml,
> yamlcatalog: hive
> database: hms_direct_race
> concurrency: 16
> {code}
> h3. 3. Observe the Metastore result
> The reproduced result was:
> * 1 request succeeded.
> * 15 requests failed.
> * The failed requests reported a MySQL unique-key conflict:
> {code}
> Unable to find source-code formatter for language: text. Available languages
> are: actionscript, ada, applescript, bash, c, c#, c++, cpp, css, erlang, go,
> groovy, haskell, html, java, javascript, js, json, lua, none, nyan, objc,
> perl, php, python, r, rainbow, ruby, scala, sh, sql, swift, visualbasic, xml,
> yamlDuplicate entry 'hms_direct_race-hive'
> for key 'DBS.UNIQUE_DATABASE'
> {code}
> * Multiple handlers logged that the same database path was created.
> * Failed requests entered the database-directory cleanup path.
> h3. 4. Observe directory cleanup
> If another request writes content below the database directory before
> rollback cleanup runs, the failed request may move or delete that content
> together with the entire database directory.
> For example, affected Iceberg content may include:
> {code}
> Unable to find source-code formatter for language: text. Available languages
> are: actionscript, ada, applescript, bash, c, c#, c++, cpp, css, erlang, go,
> groovy, haskell, html, java, javascript, js, json, lua, none, nyan, objc,
> perl, php, python, r, rainbow, ruby, scala, sh, sql, swift, visualbasic, xml,
> yamliceberg/test1.db/test3/metadata/00000-....metadata.json
> {code}
> When Hadoop Trash is enabled, the path may be moved under a location similar
> to:
> {code}
> Unable to find source-code formatter for language: text. Available languages
> are: actionscript, ada, applescript, bash, c, c#, c++, cpp, css, erlang, go,
> groovy, haskell, html, java, javascript, js, json, lua, none, nyan, objc,
> perl, php, python, r, rainbow, ruby, scala, sh, sql, swift, visualbasic, xml,
> yamls3a://warehouse/user/root/.Trash/Current/iceberg/test1.db
> {code}
> h2. Reproduction Log Excerpts
> The following logs are from a standalone reproduction that bypassed all
> higher-level application code. It used 16 independent Hive Metastore clients.
> The run started at {{2026-08-27 02:23:40 UTC}} and targeted database
> {{{}hms_direct_race_20260827T022340Z{}}}.
> h3. Client result
> {code}
> Unable to find source-code formatter for language: text. Available languages
> are: actionscript, ada, applescript, bash, c, c#, c++, cpp, css, erlang, go,
> groovy, haskell, html, java, javascript, js, json, lua, none, nyan, objc,
> perl, php, python, r, rainbow, ruby, scala, sh, sql, swift, visualbasic, xml,
> yamlHMS_CREATE_DATABASE_RACE host=hive-metastore port=9083
> database=hms_direct_race_20260827T022340Z workers=16
> location=s3a://warehouse/hms_direct_race_20260827T022340Z.db
> worker=00 result=FAILURE error=MetaException: JDODataStoreException:
> Exception thrown flushing changes to datastore Root cause:
> java.sql.SQLIntegrityConstraintViolationException: Duplicate entry
> 'hms_direct_race_20260827t022340z-hive' for key 'DBS.UNIQUE_DATABASE'
> worker=01 result=FAILURE error=MetaException: JDODataStoreException:
> Exception thrown flushing changes to datastore Root cause:
> java.sql.SQLIntegrityConstraintViolationException: Duplicate entry
> 'hms_direct_race_20260827t022340z-hive' for key 'DBS.UNIQUE_DATABASE'
> ...
> worker=11 result=SUCCESS
> ...
> worker=15 result=FAILURE error=MetaException: JDODataStoreException:
> Exception thrown flushing changes to datastore Root cause:
> java.sql.SQLIntegrityConstraintViolationException: Duplicate entry
> 'hms_direct_race_20260827t022340z-hive' for key 'DBS.UNIQUE_DATABASE'
> HMS_CREATE_DATABASE_RACE_COMPLETE elapsed_ms=10380
> {code}
> The final result was one successful request and 15 failures caused by
> {{{}DBS.UNIQUE_DATABASE{}}}.
> h3. HMS audit and directory creation
> Hive Metastore audit logs recorded multiple concurrent {{create_database}}
> requests for the same database. One example is:
> {code}
> Unable to find source-code formatter for language: text. Available languages
> are: actionscript, ada, applescript, bash, c, c#, c++, cpp, css, erlang, go,
> groovy, haskell, html, java, javascript, js, json, lua, none, nyan, objc,
> perl, php, python, r, rainbow, ruby, scala, sh, sql, swift, visualbasic, xml,
> yaml2026-08-27T02:23:48,142 INFO [Metastore-Handler-Pool: Thread-1341]
> HiveMetaStore.audit: ugi=root ip=172.19.0.6 cmd=source:172.19.0.6
> create_database: Database(name:hms_direct_race_20260827T022340Z,
> description:direct concurrent create_database test,
> locationUri:s3a://warehouse/hms_direct_race_20260827T022340Z.db,
> parameters:{}, catalogName:hive)
> {code}
> Multiple handlers then processed the same database path:
> {code}
> Unable to find source-code formatter for language: text. Available languages
> are: actionscript, ada, applescript, bash, c, c#, c++, cpp, css, erlang, go,
> groovy, haskell, html, java, javascript, js, json, lua, none, nyan, objc,
> perl, php, python, r, rainbow, ruby, scala, sh, sql, swift, visualbasic, xml,
> yaml2026-08-27T02:23:48,401 INFO [Metastore-Handler-Pool: Thread-1340]
> HMSHandler: Creating database path in external directory
> s3a://warehouse/hms_direct_race_20260827T022340Z.db
> 2026-08-27T02:23:48,403 INFO [Metastore-Handler-Pool: Thread-1334]
> HMSHandler: Creating database path in external directory
> s3a://warehouse/hms_direct_race_20260827T022340Z.db
> 2026-08-27T02:23:48,434 INFO [Metastore-Handler-Pool: Thread-1338]
> HMSHandler: Created database path in external directory
> s3a://warehouse/hms_direct_race_20260827T022340Z.db
> 2026-08-27T02:23:48,438 INFO [Metastore-Handler-Pool: Thread-1334]
> HMSHandler: Created database path in external directory
> s3a://warehouse/hms_direct_race_20260827T022340Z.db
> {code}
> h3. Sentinel and cleanup
> A watcher observed the database directory during the race window and uploaded
> a sentinel object below it:
> {code}
> Unable to find source-code formatter for language: text. Available languages
> are: actionscript, ada, applescript, bash, c, c#, c++, cpp, css, erlang, go,
> groovy, haskell, html, java, javascript, js, json, lua, none, nyan, objc,
> perl, php, python, r, rainbow, ruby, scala, sh, sql, swift, visualbasic, xml,
> yamlWATCHER_START 2026-08-27T02:23:41Z
> DIRECTORY_VISIBLE 2026-08-27T02:23:48Z
> s3://warehouse/hms_direct_race_20260827T022340Z.db
> 46 bytes ->
> `minio/warehouse/hms_direct_race_20260827T022340Z.db/sentinel/metadata/race-sentinel.txt`
> SENTINEL_UPLOAD_DONE 2026-08-27T02:23:48Z
> WATCHER_END 2026-08-27T02:23:48Z
> {code}
> The unique-key conflict and subsequent cleanup appeared together in the Hive
> Metastore logs:
> {code}
> Unable to find source-code formatter for language: text. Available languages
> are: actionscript, ada, applescript, bash, c, c#, c++, cpp, css, erlang, go,
> groovy, haskell, html, java, javascript, js, json, lua, none, nyan, objc,
> perl, php, python, r, rainbow, ruby, scala, sh, sql, swift, visualbasic, xml,
> yamlCaused by: java.sql.SQLIntegrityConstraintViolationException: Duplicate
> entry 'hms_direct_race_20260827t022340z-hive' for key 'DBS.UNIQUE_DATABASE'
> at
> org.apache.hadoop.hive.metastore.HMSHandler.create_database_core(HMSHandler.java:1274)
> at
> org.apache.hadoop.hive.metastore.HMSHandler.create_database(HMSHandler.java:1343)
> 2026-08-27T02:23:58,482 WARN [Metastore-Handler-Pool: Thread-1342] FileUtils:
> No such file or directory:
> s3a://warehouse/hms_direct_race_20260827T022340Z.db; Force to delete it.
> 2026-08-27T02:23:58,487 ERROR [Metastore-Handler-Pool: Thread-1341]
> FileUtils: Failed to delete
> s3a://warehouse/hms_direct_race_20260827T022340Z.db
> {code}
> After the test, no objects remained under the original path or the
> corresponding Trash path:
> {code}
> Unable to find source-code formatter for language: text. Available languages
> are: actionscript, ada, applescript, bash, c, c#, c++, cpp, css, erlang, go,
> groovy, haskell, html, java, javascript, js, json, lua, none, nyan, objc,
> perl, php, python, r, rainbow, ruby, scala, sh, sql, swift, visualbasic, xml,
> yaml=== OBJECTS UNDER DIRECT PATH ===
> === OBJECTS UNDER TRASH PATH ===
> === DIRECT PATH STAT ===
> mc: <ERROR> Unable to stat
> `minio/warehouse/hms_direct_race_20260827T022340Z.db/`. Object does not exist.
> {code}
> At the same time, the database row still existed in the MySQL {{DBS}} table:
> {code}
> Unable to find source-code formatter for language: text. Available languages
> are: actionscript, ada, applescript, bash, c, c#, c++, cpp, css, erlang, go,
> groovy, haskell, html, java, javascript, js, json, lua, none, nyan, objc,
> perl, php, python, r, rainbow, ruby, scala, sh, sql, swift, visualbasic, xml,
> yamlDB_ID NAME CTLG_NAME DB_LOCATION_URI
> 104 hms_direct_race_20260827t022340z hive
> s3a://warehouse/hms_direct_race_20260827T022340Z.db
> {code}
> Trash was disabled in this standalone reproduction, so cleanup directly
> deleted the directory instead of moving it to {{{}.Trash/Current{}}}.
> h2. Actual Result
> A failed {{create_database}} request may recursively clean up the entire
> final database directory rather than cleaning up only request-private
> resources.
> This can leave the system in the following state:
> * Database metadata still exists in Hive Metastore.
> * Table metadata may still exist in Hive Metastore.
> * Iceberg {{metadata_location}} values still reference the original
> object-storage paths.
> * The corresponding Iceberg metadata files have been moved to Trash or
> recursively deleted.
> * Subsequent Iceberg queries, startup recovery, or metadata commits receive
> 404/not-found errors.
> * Iceberg tables become unreadable or unwritable.
> h2. Expected Result
> When multiple requests concurrently create the same database:
> * One request should successfully create the database.
> * Other requests should receive {{AlreadyExistsException}} or an equivalent
> database-already-exists error.
> * Failed requests must not delete, move, or otherwise modify the shared
> final database directory.
> * Failed requests may only recursively clean up resources that are provably
> private to that request, such as a request-scoped staging path.
> h2. Root Cause
> {{create_database}} uses a non-atomic check-then-create sequence:
> {code:java}
> get_database_core(...);
> if (database does not exist) {
> create_database_core(...);
> }
> {code}
> Inside {{{}create_database_core{}}}, filesystem ownership is inferred from
> logic similar to:
> {code:java}
> if (!wh.isDir(dbPath)) {
> if (!wh.mkdirs(dbPath)) {
> throw new MetaException(...);
> }
> madeDir = true;
> }
> {code}
> A successful {{mkdirs}} call only shows that the directory became available
> through this request. It does not prove continuing exclusive ownership.
> Multiple requests may set {{madeDir}} to {{{}true{}}}, and even a directory
> created exclusively by one request may subsequently be adopted by a different
> request that wins the metadata transaction.
> When one request commits the Metastore metadata and other requests fail
> because of {{{}DBS.UNIQUE_DATABASE{}}}, the failed requests execute cleanup
> similar to:
> {code:java}
> if (!success) {
> ms.rollbackTransaction();
> if (madeDir) {
> wh.deleteDir(dbPath, true);
> }
> }
> {code}
> The corresponding external database path cleanup in Hive {{4.0.0-beta-1}} is
> equivalent to:
> {code:java}
> if (madeExternalDir && dbExtPath != null) {
> wh.deleteDir(dbExtPath, true, db);
> }
> {code}
> The cleanup scope is the entire final database directory, not an empty
> request-private directory or an object written only by the failed request.
> The unique-key conflict itself is an expected concurrency outcome. The
> data-loss risk comes from treating local directory-creation provenance as
> authorization to recursively delete a shared final location after metadata
> rollback.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)