Skip to content

Commit e645039

Browse files
committed
Drop the vector-search recreate-cascade HACK from bundle_plan
The HACK in bundle_plan.go force-cascaded Recreate from vector_search_endpoints to dependent vector_search_indexes. Remove it and surface the gap explicitly in the acceptance test: after recreating the endpoint, a second plan/deploy is required to reconcile the index's stored endpoint_uuid. A generic per-resource cascade rule is the proper fix and will land in a follow-up. Also have the testserver capture the endpoint UUID on indexes at create time (fakeVectorSearchIndex wrapper, omitted from JSON responses) so future tests can reason about the orphan scenario without inventing state out of thin air. Co-authored-by: Isaac
1 parent 4d9d531 commit e645039

6 files changed

Lines changed: 56 additions & 59 deletions

File tree

acceptance/bundle/resources/vector_search_indexes/recreate/with_endpoint/output.txt

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,13 @@ Deploying resources...
66
Updating deployment state...
77
Deployment complete!
88

9-
=== Change endpoint_type (should recreate endpoint AND its dependent index)
9+
=== Change endpoint_type (recreates endpoint; index recreate cascade is not yet implemented)
1010
>>> update_file.py databricks.yml endpoint_type: STANDARD endpoint_type: STORAGE_OPTIMIZED
1111

1212
>>> [CLI] bundle plan
1313
recreate vector_search_endpoints.my_endpoint
14-
recreate vector_search_indexes.my_index
1514

16-
Plan: 2 to add, 0 to change, 2 to delete, 0 unchanged
15+
Plan: 1 to add, 0 to change, 1 to delete, 1 unchanged
1716

1817
>>> [CLI] bundle plan --output json
1918
{
@@ -60,18 +59,7 @@ Plan: 2 to add, 0 to change, 2 to delete, 0 unchanged
6059
"label": "${resources.vector_search_endpoints.my_endpoint.name}"
6160
}
6261
],
63-
"action": "recreate",
64-
"new_state": {
65-
"value": {
66-
"direct_access_index_spec": {
67-
"schema_json": "{\"columns\":[{\"name\":\"id\",\"type\":\"integer\"}]}"
68-
},
69-
"endpoint_name": "vs-endpoint-[UNIQUE_NAME]",
70-
"index_type": "DIRECT_ACCESS",
71-
"name": "vs-index-[UNIQUE_NAME]",
72-
"primary_key": "id"
73-
}
74-
},
62+
"action": "skip",
7563
"remote_state": {
7664
"creator": "[USERNAME]",
7765
"direct_access_index_spec": {
@@ -100,11 +88,6 @@ Plan: 2 to add, 0 to change, 2 to delete, 0 unchanged
10088

10189
>>> [CLI] bundle deploy --auto-approve
10290
Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/deploy-vs-index-with-endpoint-[UNIQUE_NAME]/default/files...
103-
104-
This action will result in the deletion or recreation of the following Vector Search indexes.
105-
Recreating a Delta Sync index re-runs the full embedding pipeline; recreating a Direct Access
106-
index drops all upserted vectors. Both can be expensive to rebuild:
107-
recreate resources.vector_search_indexes.my_index
10891
Deploying resources...
10992
Updating deployment state...
11093
Deployment complete!
@@ -123,6 +106,23 @@ Deployment complete!
123106
"primary_key": "id"
124107
}
125108

109+
=== Re-plan exposes the leftover drift: the index still points at the old endpoint UUID
110+
>>> [CLI] bundle plan
111+
recreate vector_search_indexes.my_index
112+
113+
Plan: 1 to add, 0 to change, 1 to delete, 1 unchanged
114+
115+
>>> [CLI] bundle deploy --auto-approve
116+
Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/deploy-vs-index-with-endpoint-[UNIQUE_NAME]/default/files...
117+
118+
This action will result in the deletion or recreation of the following Vector Search indexes.
119+
Recreating a Delta Sync index re-runs the full embedding pipeline; recreating a Direct Access
120+
index drops all upserted vectors. Both can be expensive to rebuild:
121+
recreate resources.vector_search_indexes.my_index
122+
Deploying resources...
123+
Updating deployment state...
124+
Deployment complete!
125+
126126
>>> [CLI] bundle destroy --auto-approve
127127
The following resources will be deleted:
128128
delete resources.vector_search_endpoints.my_endpoint

acceptance/bundle/resources/vector_search_indexes/recreate/with_endpoint/script

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ title "Initial deployment with STANDARD endpoint_type"
1010
rm -f out.requests.txt
1111
trace $CLI bundle deploy
1212

13-
title "Change endpoint_type (should recreate endpoint AND its dependent index)"
13+
title "Change endpoint_type (recreates endpoint; index recreate cascade is not yet implemented)"
1414
trace update_file.py databricks.yml "endpoint_type: STANDARD" "endpoint_type: STORAGE_OPTIMIZED"
1515

1616
trace $CLI bundle plan
@@ -23,3 +23,7 @@ endpoint_name="vs-endpoint-${UNIQUE_NAME}"
2323
index_name="vs-index-${UNIQUE_NAME}"
2424
trace $CLI vector-search-endpoints get-endpoint "${endpoint_name}" | jq '{name, endpoint_type}'
2525
trace $CLI vector-search-indexes get-index "${index_name}" | jq '{name, endpoint_name, index_type, primary_key}'
26+
27+
title "Re-plan exposes the leftover drift: the index still points at the old endpoint UUID"
28+
trace $CLI bundle plan
29+
trace $CLI bundle deploy --auto-approve
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
Cloud = false
2+
3+
Badness = "Recreating a vector_search_endpoint does not cascade to its dependent vector_search_indexes. The first deploy recreates the endpoint and leaves the index attached to a stale endpoint UUID; a second plan/deploy is required to reconcile the index. Follow-up will add a generic per-resource recreate cascade rule to the framework."

bundle/direct/bundle_plan.go

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -283,31 +283,6 @@ func (b *DeploymentBundle) CalculatePlan(ctx context.Context, client *databricks
283283
return nil, errors.New("planning failed")
284284
}
285285

286-
// HACK: cascade Recreate from vector_search_endpoints to dependent
287-
// vector_search_indexes. The framework has no per-resource recreate
288-
// cascade rule yet; without this, recreating an endpoint orphans its
289-
// indexes (or fails because indexes are still attached on delete).
290-
// Replace with a generic rule when the framework grows one.
291-
for resourceKey, entry := range plan.Plan {
292-
if config.GetResourceTypeFromKey(resourceKey) != "vector_search_indexes" {
293-
continue
294-
}
295-
if entry.Action == deployplan.Create || entry.Action == deployplan.Recreate || entry.Action == deployplan.Delete {
296-
continue
297-
}
298-
for _, dep := range entry.DependsOn {
299-
if config.GetResourceTypeFromKey(dep.Node) != "vector_search_endpoints" {
300-
continue
301-
}
302-
parent, ok := plan.Plan[dep.Node]
303-
if !ok || parent.Action != deployplan.Recreate {
304-
continue
305-
}
306-
entry.Action = deployplan.Recreate
307-
break
308-
}
309-
}
310-
311286
for _, entry := range plan.Plan {
312287
if entry.Action == deployplan.Skip {
313288
entry.NewState = nil

libs/testserver/fake_workspace.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ type FakeWorkspace struct {
152152
RegisteredModels map[string]catalog.RegisteredModelInfo
153153
ServingEndpoints map[string]serving.ServingEndpointDetailed
154154
VectorSearchEndpoints map[string]vectorsearch.EndpointInfo
155-
VectorSearchIndexes map[string]vectorsearch.VectorIndex
155+
VectorSearchIndexes map[string]fakeVectorSearchIndex
156156

157157
SecretScopes map[string]workspace.SecretScope
158158
Secrets map[string]map[string]string // scope -> key -> value
@@ -288,7 +288,7 @@ func NewFakeWorkspace(url, token string) *FakeWorkspace {
288288
},
289289
ServingEndpoints: map[string]serving.ServingEndpointDetailed{},
290290
VectorSearchEndpoints: map[string]vectorsearch.EndpointInfo{},
291-
VectorSearchIndexes: map[string]vectorsearch.VectorIndex{},
291+
VectorSearchIndexes: map[string]fakeVectorSearchIndex{},
292292
Repos: map[string]workspace.RepoInfo{},
293293
SecretScopes: map[string]workspace.SecretScope{},
294294
Secrets: map[string]map[string]string{},

libs/testserver/vector_search_indexes.go

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,18 @@ import (
88
"github.com/databricks/databricks-sdk-go/service/vectorsearch"
99
)
1010

11+
// fakeVectorSearchIndex captures the endpoint's UUID at index creation time.
12+
// On the real backend an index is bound to a specific endpoint instance, not
13+
// just the name: deleting and recreating an endpoint with the same name yields
14+
// a different UUID, and the existing index keeps pointing at the OLD UUID
15+
// (i.e. is orphaned). Tracking this here lets tests reason about that drift.
16+
// The field is omitted from JSON responses since the real API doesn't return
17+
// it on the index path; the CLI looks it up via GetEndpointByEndpointName.
18+
type fakeVectorSearchIndex struct {
19+
vectorsearch.VectorIndex
20+
EndpointUuid string `json:"-"`
21+
}
22+
1123
func (s *FakeWorkspace) VectorSearchIndexCreate(req Request) Response {
1224
defer s.LockUnlock()()
1325

@@ -25,7 +37,8 @@ func (s *FakeWorkspace) VectorSearchIndexCreate(req Request) Response {
2537
Body: map[string]string{"error_code": "RESOURCE_ALREADY_EXISTS", "message": fmt.Sprintf("Vector search index with name %s already exists", createReq.Name)},
2638
}
2739
}
28-
if _, exists := s.VectorSearchEndpoints[createReq.EndpointName]; !exists {
40+
endpoint, exists := s.VectorSearchEndpoints[createReq.EndpointName]
41+
if !exists {
2942
return Response{
3043
StatusCode: http.StatusNotFound,
3144
Body: map[string]string{
@@ -35,17 +48,20 @@ func (s *FakeWorkspace) VectorSearchIndexCreate(req Request) Response {
3548
}
3649
}
3750

38-
index := vectorsearch.VectorIndex{
39-
Creator: s.CurrentUser().UserName,
40-
EndpointName: createReq.EndpointName,
41-
IndexType: createReq.IndexType,
42-
Name: createReq.Name,
43-
PrimaryKey: createReq.PrimaryKey,
44-
DeltaSyncIndexSpec: remapDeltaSyncSpec(createReq.DeltaSyncIndexSpec),
45-
DirectAccessIndexSpec: createReq.DirectAccessIndexSpec,
46-
Status: &vectorsearch.VectorIndexStatus{
47-
Ready: true,
51+
index := fakeVectorSearchIndex{
52+
VectorIndex: vectorsearch.VectorIndex{
53+
Creator: s.CurrentUser().UserName,
54+
EndpointName: createReq.EndpointName,
55+
IndexType: createReq.IndexType,
56+
Name: createReq.Name,
57+
PrimaryKey: createReq.PrimaryKey,
58+
DeltaSyncIndexSpec: remapDeltaSyncSpec(createReq.DeltaSyncIndexSpec),
59+
DirectAccessIndexSpec: createReq.DirectAccessIndexSpec,
60+
Status: &vectorsearch.VectorIndexStatus{
61+
Ready: true,
62+
},
4863
},
64+
EndpointUuid: endpoint.Id,
4965
}
5066

5167
s.VectorSearchIndexes[createReq.Name] = index

0 commit comments

Comments
 (0)