From 3bd69d4a91a0be76ff1ad7f7ea26064b0970d9fd Mon Sep 17 00:00:00 2001 From: Andrew Hundt Date: Thu, 2 Jul 2026 13:42:22 -0400 Subject: [PATCH] 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 --- src/graph_buffer/graph_buffer.c | 36 +++++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/src/graph_buffer/graph_buffer.c b/src/graph_buffer/graph_buffer.c index 4f894257b..27bb35dae 100644 --- a/src/graph_buffer/graph_buffer.c +++ b/src/graph_buffer/graph_buffer.c @@ -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;