- Replace all occurrences of 'ss-tools' with 'superset-tools' in 104 files - Rename git bundle file ss-tools.bundle → superset-tools.bundle - Update .gitignore pattern accordingly - Preserve variable names (hasSsTools etc.) and code identifiers
8.2 KiB
DuckDB Rebuild Timeout — Investigation for Axiom Developer
Symptom
axiom_semantic_index rebuild rebuild_mode="full" use_duckdb=true consistently times out via MCP transport regardless of timeout value.
Environment
- Binary:
/home/busya/dev/axiom-mcp-rust-port/target/release/axiom-mcp-server-rs - Workspace:
/home/busya/dev/superset-tools(3185 contracts, 2149 edges, 690 files) - DuckDB path:
.axiom/semantic_index/graph.duckdb - Expected DuckDB size: ~225 MB (final, from earlier successful rebuild)
- OS: Linux, 64-bit
Investigation Log
Attempt 1: Timeout scaling test (old binary)
| use_duckdb | Timeout | Result | Notes |
|---|---|---|---|
| false | 120s | ✅ Success | 3185 contracts, 2149 edges |
| true | 120s | ❌ Timeout | |
| true | 300s | ❌ Timeout | |
| true | 600s | ❌ Timeout | |
| true | 900s | ❌ Timeout |
Attempt 2: After chunking fix + error logging (new binary)
| Step | Result | Notes |
|---|---|---|
rebuild use_duckdb=true |
❌ "Failed to populate DuckDB store" |
Timeout побеждён! Код не зависает |
| Очистка DuckDB + retry | ❌ "Failed to populate DuckDB store" |
Та же логическая ошибка |
Удалён index.duckdb (394MB legacy) + retry |
❌ "Failed to populate DuckDB store" |
Не помогло |
rebuild (no DuckDB) + сразу rebuild use_duckdb=true |
❌ "Failed to populate DuckDB store" |
Даже после успешного non-DuckDB rebuild |
rebuild use_duckdb=false (контрольный) |
✅ Always success | 3185 contracts, 2149 edges |
Итого: "Failed to populate DuckDB store" воспроизводится всегда, стабильно, на чистой DuckDB. Ошибка в Rust-коде популяции, не в данных.
Attempt 3: Clean database
- Moved old
graph.duckdb(225MB) →graph.duckdb.bak - Tried
rebuild use_duckdb=true— still ❌ timeout - Partial write observed: new
graph.duckdbgrew 0→38MB before timeout - After retry: grew 38→63MB — then stalled completely
- WAL file (
graph.duckdb.wal) appeared and disappeared, suggesting incomplete transactions
Attempt 4: Non-DuckDB works flawlessly
reindex → 120s ✅ (3185 contracts, 0 edges, instant)
rebuild (no DuckDB) → 300s ✅ (3185 contracts, 2149 edges)
audit_contracts after rebuild → responds instantly
Key observations
- DuckDB write starts but never finishes. First attempt wrote 38MB, second wrote 63MB, then stopped making progress. Expected final size is ~225MB.
- Non-DuckDB operations are fast. The contract parsing, relation resolution, and JSON index writing all complete within 180-300s.
- MCP transport times out before DuckDB finishes. The MCP protocol returns
-32001: Request timed outat the transport layer. The server process may still be running but the client disconnected. - DuckDB file is 63MB, WAL is empty. The partial file suggests the DuckDB transaction started but never committed.
- DuckDB WAL appeared then disappeared between attempts, suggesting a rollback occurred.
Root cause found! Duplicate @RELATION edges in source code
После логирования удалось выявить точную причину: PRIMARY KEY constraint violation при вставке в таблицу edges. DuckDB использует первичный ключ (source_id, relation_type, target_id), а в коде есть дублирующиеся @RELATION строки.
Примеры дубликатов (подтверждено)
DatasetReviewEntryUxTests:DEPENDS_ON:DatasetReviewWorkspaceEntry (2 файла, одинаковый contract_id)
BackupsPage:IMPLEMENTS:RoutePages (2 файла, одинаковый contract_id)
DeleteRunningTasksUtil:DEPENDS_ON:TaskRecord (2 строки в одном файле)
AuthJwtModule:DEPENDS_ON:auth_config (2 строки в одном файле)
Масштаб проблемы
Скрипт поиска (grep -r @RELATION | sort | uniq -d) показал десятки файлов с дубликатами: profile.py (7 копий), database.py (2), async_network.py (2), security.py (2), и много тестовых файлов.
Варианты фикса
Вариант A: Rust-код (рекомендуется)
Изменить вставку в DuckDB с INSERT на INSERT OR REPLACE или INSERT OR IGNORE:
// Текущий код (падает при дубликатах):
conn.execute("INSERT INTO edges (source, type, target) VALUES (?, ?, ?)", ...)?;
// Фикс 1: INSERT OR IGNORE — пропускает дубликаты
conn.execute("INSERT OR IGNORE INTO edges (source, type, target) VALUES (?, ?, ?)", ...)?;
// Фикс 2: INSERT OR REPLACE — перезаписывает
conn.execute("INSERT OR REPLACE INTO edges (source, type, target) VALUES (?, ?, ?)", ...)?;
Преимущество: не требует правки кода на проекте, устойчив к дубликатам в будущем.
Вариант B: Правка кода (трудоёмко)
Найти и удалить все дублирующиеся @RELATION строки в проекте. Оценка: ~50 файлов, ~200 дубликатов.
Текущий статус (после ручного фикса 4 дубликатов)
| Попытка | Результат |
|---|---|
rebuild use_duckdb=true |
❌ Всё ещё падает — есть ещё дубликаты (всего ~50 файлов) |
rebuild use_duckdb=false |
✅ Всегда работает (JSON index не имеет unique constraint) |
Рекомендация
Разработчику Axiom: реализовать Вариант A (INSERT OR IGNORE). Это:
- Решает проблему мгновенно
- Не требует правки проекта
- Устойчив к регрессиям
- Не теряет данные — дубликаты @RELATION это одна и та же связь, записанная дважды
Suggested fix approach
// Before (current — single large write blocks MCP):
pub async fn rebuild_with_duckdb() -> Result<()> {
let contracts = parse_all_files()?;
let conn = DuckDbConnection::open(path)?;
conn.execute("BEGIN TRANSACTION")?; // single giant tx
for c in &contracts {
conn.execute("INSERT INTO contracts ...", params!(c))?;
}
conn.execute("COMMIT")?; // may take minutes
Ok(())
}
// After — chunked with MCP yield:
pub async fn rebuild_with_duckdb(stream: &McpStream) -> Result<()> {
let contracts = parse_all_files()?;
let conn = DuckDbConnection::open(path)?;
// Smaller batches to prevent transport timeout
for chunk in contracts.chunks(500) {
conn.execute("BEGIN TRANSACTION")?;
for c in chunk {
conn.execute("INSERT INTO contracts ...", params!(c))?;
}
conn.execute("COMMIT")?;
// Signal progress to keep MCP connection alive
stream.send(Progress { done: chunk_end, total: contracts.len() }).await?;
// Yield to allow other MCP requests
tokio::task::yield_now().await;
}
Ok(())
}
Workaround (for current version)
Until the DuckDB rebuild is fixed, use the two-step process:
axiom_semantic_index reindex(in-memory, instant)axiom_semantic_index rebuild rebuild_mode="full" use_duckdb=false(JSON index, ~180-300s)
The JSON index at .axiom/semantic_index/index.json is fully functional for search_contracts, read_outline, and local_context. Only audit_contracts with DuckDB-dependent schema validation may show stale tag validation results.
Files referenced
- DuckDB binary path:
.axiom/semantic_index/graph.duckdb(63 MB partial) - JSON index path:
.axiom/semantic_index/index.json(fresh, 3185 contracts, 2149 edges) - Old DuckDB backup:
.axiom/semantic_index/graph.duckdb.bak(225 MB) - Axiom config:
.axiom/axiom_config.yaml(all tag/permission fixes applied)