Skip to content

direct: retry recreate's create while the previous delete clears - #6748

Open
denik wants to merge 6 commits into
mainfrom
denik/recreate-ensure-deleted
Open

denik wants to merge 6 commits into
mainfrom
denik/recreate-ensure-deleted

Conversation

@denik

@denik denik commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

Changes

On recreate, retry the create while it keeps hitting a resource conflict — unless the recreate changed the resource's id.

Why

The create can race the previous delete's async teardown (e.g. a synced table's destination Postgres table is dropped on a slower schedule), so it still 409s after WaitAfterDelete returns; retrying rides it out. A recreate that changed a provided-id field (a rename) fails fast instead, since a separate resource owns that name and no wait frees it.

This pull request and its description were written by Isaac.

@denik
denik force-pushed the denik/recreate-ensure-deleted branch from eb4ce09 to 78c1a09 Compare September 18, 2026 09:51
@eng-dev-ecosystem-bot

eng-dev-ecosystem-bot commented Sep 18, 2026

Copy link
Copy Markdown
Collaborator

Integration test report

Commit: cf3f2c1

Run: 35600913446

Env ✅​pass 🙈​skip Time
✅​ aws linux 276 17 5:29
✅​ aws windows 278 15 7:19
✅​ azure linux 275 17 5:49
✅​ azure windows 277 15 7:39
✅​ gcp linux 276 17 5:41
✅​ gcp windows 278 15 3:55
Top 6 slowest tests (at least 2 minutes):
duration env testname
7:36 azure windows TestAccept
7:17 aws windows TestAccept
4:04 azure linux TestAccept
3:59 gcp linux TestAccept
3:58 aws linux TestAccept
3:54 gcp windows TestAccept

Base automatically changed from denik/already-exists to main September 18, 2026 10:25
denik added a commit that referenced this pull request Sep 18, 2026
Co-authored-by: Isaac <no-reply@databricks.com>
@denik
denik force-pushed the denik/recreate-ensure-deleted branch from 78c1a09 to 5f3e228 Compare September 18, 2026 14:02
@denik denik changed the title direct: confirm the resource is gone before recreating direct: recreate retries the create after waiting out a still-pending delete Sep 18, 2026
A synced table's delete drops its destination Postgres table asynchronously, on a
slower schedule than the synced-table record that WaitAfterDelete polls. So even
after the delete-wait completes, the re-create can fail with 409 ALREADY_EXISTS
("Destination table ... already exists"), as seen on an AWS nightly (#6728 waited
on the wrong object). Recreate now keeps WaitAfterDelete as the fast path and, if
the create still fails with ALREADY_EXISTS, retries it until the teardown finishes
(bounded at 5m) before surfacing the result. A create that fails for any other
reason, or a resource that never re-creates the same id (e.g. job_runs), is
returned immediately.

The testserver models the race under eventual consistency: a deleted synced table
disappears from GET at once but the next create for that id conflicts once. The
recreate_capped test becomes recreate_conflict_retry, asserting the retry succeeds.

Co-authored-by: Isaac <no-reply@databricks.com>
@denik
denik force-pushed the denik/recreate-ensure-deleted branch from 5f3e228 to 888bbc8 Compare September 21, 2026 08:32
@denik denik changed the title direct: recreate retries the create after waiting out a still-pending delete direct: retry recreate's create while ALREADY_EXISTS clears Sep 21, 2026
…from its test

No measured teardown latency to size the create-retry cap, so raise it 5m->15m to
match deleteIndexTimeout (the closest async-teardown analog). Convert
recreate_conflict_retry off the databricks.yml.tmpl + envsubst pattern to a committed
databricks.yml edited in place with update_file.py.

Co-authored-by: Isaac <no-reply@databricks.com>
@denik
denik added this pull request to the merge queue Sep 21, 2026
Comment thread bundle/direct/apply.go Outdated
case createErr == nil:
return &struct{}{}, nil
case errors.Is(createErr, apierr.ErrAlreadyExists):
log.Warnf(ctx, "Create hit ALREADY_EXISTS; the previous delete is likely still finishing, retrying: %s", createErr)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't it become an issue when, let's say, I want to rename the app I have (which should trigger a recreation), but I use the name that already exists? Won't it be stuck here for 15 minutes unnecessarily?

Shall we move this logic to a specific resource instead?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question. Rather than making it per-resource setting, how about we trace what fields changed? If it's id fields, then this is a real conflict that we must abort on; if it's other immutable fields but id stays the same then we apply this retries.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So it does not actually gets stuck because apps return RESOURCE_ALREADY_EXISTS and not ALREADY_EXISTS. However, I'd rather not rely on that, this looks accidental and other resources can use codes differently.

@denik
denik removed this pull request from the merge queue due to a manual request Sep 21, 2026
denik and others added 3 commits September 21, 2026 12:32
The recreate create-retry waits out a slow async teardown, but only makes
sense when the create re-creates the same id it just deleted. If the recreate
changed a provided-id field (e.g. renamed an app, or pointed a synced table at
a new synced_table_id), the create targets a different id that a separate,
pre-existing resource owns — no wait can free that name, so surface the
ALREADY_EXISTS immediately instead of retrying for minutes.

Adds local+cloud coverage for a synced-table rename onto an existing id, plus
local fail-fast coverage for apps and vector search endpoints.

Co-authored-by: Isaac <no-reply@databricks.com>
…edundant VS test

On cloud a synced-table create with an already-taken id replaces the existing
table (replace_existing) instead of returning ALREADY_EXISTS, so the rename
never conflicts there and the cloud run "succeeded" unexpectedly. The retry the
same-id guard gates only matches error_code ALREADY_EXISTS (postgres synced
tables); apps and vector search endpoints return RESOURCE_ALREADY_EXISTS, a
sibling sentinel the retry never caught, so they already fail fast. The guard is
therefore only observable against the testserver — keep the synced-table test as
its local regression, and drop the vector-search test, which duplicated the
existing recreate/create-fails coverage.

Co-authored-by: Isaac <no-reply@databricks.com>
Match the whole conflict class (ErrResourceConflict) instead of just error_code
ALREADY_EXISTS: the lingering-teardown race the retry waits out can surface as
either ALREADY_EXISTS (postgres synced tables) or RESOURCE_ALREADY_EXISTS (apps,
vector search endpoints), and which code a backend returns should not decide
whether we retry. The same-id guard still fails a rename (a provided-id change)
fast, so a genuine name collision never spins for minutes regardless of code.

The testserver no longer returns ALREADY_EXISTS when creating a synced table
whose id already exists — the real backend replaces it rather than conflicting,
so the previous rename-conflict test could not reproduce on cloud. Drop that
test; the guard's regression coverage is the apps rename and the existing
vector_search_endpoints/recreate/create-fails, which now exercise the retry path
and would spin without the guard. The destination-table teardown race stays.

Co-authored-by: Isaac <no-reply@databricks.com>
@denik denik changed the title direct: retry recreate's create while ALREADY_EXISTS clears direct: retry recreate's create while the previous delete clears Sep 21, 2026
The same-id guard should attempt the rename's create exactly once. Record the
requests and print the recreate's /apps calls so the golden shows a single
POST /api/2.0/apps (create app-beta) after the delete — a retry regression would
show it repeated.

Co-authored-by: Isaac <no-reply@databricks.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants