unitycatalog / unitycatalog/unitycatalog

Concurrent creates of the same table name persist duplicate rows, permanently breaking that name

Open
#1,837 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Java
Stars
3.5k
Forks
672
Avg merge
6d 16h
Merged PRs (30d)
30

Description

Describe the bug

uc_tables has no unique constraint on (schema_id, name) -- TableInfoDAO declares only
@Index(name = "idx_name", columnList = "name") -- and TableRepository.createTable checks for an
existing name and then inserts in separate statements. Two concurrent creates of the same table name
therefore both pass the check and both insert, leaving two rows with the same (schema_id, name).

Every later lookup of that name fails, because the repository resolves a table by name with
Query.uniqueResult(), which throws NonUniqueResultException on two rows: getTable answers 500,
and so does deleteTable, so the duplicate cannot be removed through the API and the name stays
broken. The Iceberg REST endpoints resolve the same way through findTableOrThrow.

The same check-then-insert shape, with no unique constraint behind the column it checks, appears
elsewhere: SchemaRepository.createSchema (uc_schemas declares no constraints at all),
ModelRepository.createRegisteredModel (uc_registered_models has a plain name index),
StagingTableRepository, and UserRepository.createUser, whose email check has no constraint behind
it -- uc_users constrains external_id only. I have only reproduced the table case.

To Reproduce

Steps to reproduce the behavior:

  1. Start the server and create a catalog and a schema.
  2. Send two POST /api/2.1/unity-catalog/tables requests for the same table name at the same moment
    (two threads released by a barrier). Both answer 200, with different table_ids -- reproduced on
    the first attempt against main at 58d5c7b:
    [created:20b45618-2046-4c1e-83cd-8fcbcaf12cfa, created:8dc1d9a9-23e9-40ec-b936-31d44b94399f]
  3. GET /api/2.1/unity-catalog/tables/{catalog}.{schema}.{table} now answers
    500 INTERNAL, "message": "Failed to get table: Query did not return a unique result: 2 results were returned"
    (from TableRepository.getTable).
  4. DELETE on the same name answers 500 INTERNAL,
    "message": "Failed to delete table: Query did not return a unique result: 2 results were returned"
    (from TableRepository.deleteTable), so the rows cannot be removed through the API. The name stays
    unusable until the database is edited directly.

Expected behavior

One of the two creates succeeds and the other fails with TABLE_ALREADY_EXISTS, which is what a
single create against an existing name already returns. A duplicate (schema_id, name) should not be
representable in the schema.

System [please complete the following information]:

  • OS: macOS 15, server built from main at 58d5c7b (H2 store, but the missing constraint is not store-specific)

Additional context

A unique constraint on (schema_id, name) would close it at the source, and would let
createTable translate the constraint violation into TABLE_ALREADY_EXISTS instead of relying on
the preceding read. That implies a schema change for existing databases, which is adjacent to the
"database schema upgrades" item on the roadmap, so I am filing this rather than sending a patch --
happy to implement whichever direction the maintainers prefer.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with TableInfoDAO and TableRepository.createTable, getTable, and deleteTable, then compare the check-then-insert paths in SchemaRepository, ModelRepository, StagingTableRepository, and UserRepository. Reproduce the two-thread table creation against the H2 store and inspect the database schema upgrades roadmap. Done means duplicate names are prevented and the losing create reports TABLE_ALREADY_EXISTS without leaving lookups or deletion broken.

Written by the indexing model from the issue text.

Assessment

Tech stack
java, sql
Domain
backend-api-design, databases
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.