perf(graph-buffer): keep indexes for containment delete

Preserve graph indexes while deleting the target project row in cbm_gbuf_flush_to_store(), then drop/recreate them only around the replacement insert workload. This keeps SQLite's ON DELETE CASCADE from scanning large child tables without FK lookup indexes during incremental containment publishes.

Adds opt-in CBM_PROFILE phase timings for gbuf_flush using the existing structured profiling path; no MCP stdout output and no new heap ownership.

Validation: product build rc=0; ASan/UBSan graph_buffer 66/66; ASan/UBSan pipeline 301/301; source-safety rc=0. Profiled self-dogfood one_source_file improved from 1.51x to 3.52x speedup, with incremental.flush down from 13366 ms to 3914 ms and delete_project down from 10206 ms to 633 ms.
Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
This commit is contained in:
Andrew Hundt
2026-07-02 13:42:22 -04:00
parent de4b00c5d5
commit 3bd69d4a91
+30 -6
View File
@@ -2034,38 +2034,51 @@ int cbm_gbuf_flush_to_store(cbm_gbuf_t *gb, cbm_store_t *store) {
return CBM_NOT_FOUND;
}
CBM_PROF_START(t_flush_total);
int64_t *temp_to_real = NULL;
const char *phase = "begin_bulk";
CBM_PROF_START(t_begin_bulk);
int rc = cbm_store_begin_bulk(store);
CBM_PROF_END("gbuf_flush", "0_begin_bulk", t_begin_bulk);
if (rc != CBM_STORE_OK) {
return rc;
}
phase = "begin";
CBM_PROF_START(t_begin);
rc = cbm_store_begin(store);
CBM_PROF_END("gbuf_flush", "1_begin", t_begin);
if (rc != CBM_STORE_OK) {
(void)cbm_store_end_bulk(store);
return rc;
}
phase = "drop_indexes";
rc = cbm_store_drop_indexes(store);
if (rc != CBM_STORE_OK) {
goto fail;
}
/* Delete the project row so ON DELETE CASCADE clears graph and freshness
* tables owned by this project before the replacement graph is inserted.
* This preserves sibling projects, including dependency subprojects; caller
* code still owns contentless FTS rebuilds and user-authored project memory. */
phase = "delete_project";
CBM_PROF_START(t_delete_project);
rc = cbm_store_delete_project(store, gb->project);
CBM_PROF_END("gbuf_flush", "2_delete_project", t_delete_project);
if (rc != CBM_STORE_OK) {
goto fail;
}
/* Keep indexes during the cascade delete above: SQLite uses the child-table
* FK indexes to avoid scanning the whole graph. Drop them only for inserts. */
phase = "drop_indexes";
CBM_PROF_START(t_drop_indexes);
rc = cbm_store_drop_indexes(store);
CBM_PROF_END("gbuf_flush", "3_drop_indexes", t_drop_indexes);
if (rc != CBM_STORE_OK) {
goto fail;
}
phase = "upsert_project";
CBM_PROF_START(t_upsert_project);
rc = cbm_store_upsert_project(store, gb->project, gb->root_path);
CBM_PROF_END("gbuf_flush", "4_upsert_project", t_upsert_project);
if (rc != CBM_STORE_OK) {
goto fail;
}
@@ -2082,6 +2095,7 @@ int cbm_gbuf_flush_to_store(cbm_gbuf_t *gb, cbm_store_t *store) {
}
phase = "upsert_nodes";
CBM_PROF_START(t_upsert_nodes);
for (int i = 0; i < gb->nodes.count; i++) {
cbm_gbuf_node_t *n = gb->nodes.items[i];
@@ -2109,9 +2123,11 @@ int cbm_gbuf_flush_to_store(cbm_gbuf_t *gb, cbm_store_t *store) {
temp_to_real[n->id] = real_id;
}
}
CBM_PROF_END_N("gbuf_flush", "5_upsert_nodes", t_upsert_nodes, gb->nodes.count);
/* Insert all edges with remapped IDs */
phase = "insert_edges";
CBM_PROF_START(t_insert_edges);
for (int i = 0; i < gb->edges.count; i++) {
cbm_gbuf_edge_t *e = gb->edges.items[i];
int64_t real_src = (e->source_id < max_temp_id) ? temp_to_real[e->source_id] : 0;
@@ -2132,9 +2148,12 @@ int cbm_gbuf_flush_to_store(cbm_gbuf_t *gb, cbm_store_t *store) {
goto fail;
}
}
CBM_PROF_END_N("gbuf_flush", "6_insert_edges", t_insert_edges, gb->edges.count);
phase = "create_indexes";
CBM_PROF_START(t_create_indexes);
rc = cbm_store_create_indexes(store);
CBM_PROF_END("gbuf_flush", "7_create_indexes", t_create_indexes);
if (rc != CBM_STORE_OK) {
goto fail;
}
@@ -2144,11 +2163,16 @@ int cbm_gbuf_flush_to_store(cbm_gbuf_t *gb, cbm_store_t *store) {
goto fail;
}
phase = "commit";
CBM_PROF_START(t_commit);
rc = cbm_store_commit(store);
CBM_PROF_END("gbuf_flush", "8_commit", t_commit);
if (rc != CBM_STORE_OK) {
goto fail;
}
CBM_PROF_START(t_end_bulk);
int end_bulk_rc = cbm_store_end_bulk(store);
CBM_PROF_END("gbuf_flush", "9_end_bulk", t_end_bulk);
CBM_PROF_END_N("gbuf_flush", "TOTAL", t_flush_total, gb->nodes.count + gb->edges.count);
free(temp_to_real);
return end_bulk_rc == CBM_STORE_OK ? 0 : end_bulk_rc;