This guide provides step-by-step debugging procedures for diagnosing and resolving issues with Duckling. Use this guide when troubleshooting sync failures, data inconsistencies, or performance problems.
# Test MySQL connectiondocker exec duckling-server node scripts/mysql.js "SELECT 1"# Test DuckDB querycurl -X POST http://localhost:3001/api/query \ -H "Content-Type: application/json" \ -d '{"sql": "SELECT 1"}'# Test specific table synccurl -X POST http://localhost:3001/api/sync/table/users
# List all MySQL tablesdocker exec duckling-server node scripts/mysql.js "SHOW TABLES"# Check specific tabledocker exec duckling-server node scripts/mysql.js "SELECT COUNT(*) FROM table_name"
Expected: Table exists and has data.If table missing: Create the table in MySQL first.
# MySQL countdocker exec duckling-server node scripts/mysql.js \ "SELECT COUNT(*) FROM table_name"# DuckDB countcurl -X POST http://localhost:3001/api/query \ -d '{"sql": "SELECT COUNT(*) FROM table_name"}'# Or use validate endpointcurl http://localhost:3001/api/sync/validate
Required for incremental sync: Table must have one of these columns:
updated_at / updatedAt / modified_at / modifiedAt
created_at / createdAt
timestamp
If missing: Add watermark column:
ALTER TABLE table_name ADD COLUMN updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;UPDATE table_name SET updated_at = CURRENT_TIMESTAMP;
Then reset sync:
curl -X POST http://localhost:3001/api/query \ -d '{"sql": "DELETE FROM sync_log WHERE table_name = 'table_name'"}'curl -X POST http://localhost:3001/api/sync/table/table_name
# Check current watermarkcurl -X POST http://localhost:3001/api/query \ -d '{"sql": "SELECT * FROM sync_log WHERE table_name = 'table_name' ORDER BY created_at DESC LIMIT 1"}'
Look for: watermark_after value in JSON.Compare with MySQL:
# Latest MySQL recorddocker exec duckling-server node scripts/mysql.js \ "SELECT MAX(updated_at) FROM table_name"
If watermark is ahead of MySQL: Watermark may be corrupted. Reset it:
curl -X POST http://localhost:3001/api/query \ -d '{"sql": "DELETE FROM sync_log WHERE table_name = 'table_name'"}'
# Trigger full sync to re-baselinecurl -X POST http://localhost:3001/api/sync/table/table_name# Verify countcurl http://localhost:3001/api/tables/table_name/count
# Check for duplicate primary keyscurl -X POST http://localhost:3001/api/query \ -d '{"sql": "SELECT id, COUNT(*) as cnt FROM table_name GROUP BY id HAVING cnt > 1"}'
Expected: Zero results.If duplicates found: Check if table uses composite primary key:
# For composite keyscurl -X POST http://localhost:3001/api/query \ -d '{"sql": "SELECT col1, col2, COUNT(*) FROM table_name GROUP BY col1, col2 HAVING COUNT(*) > 1"}'
For append-only tables (fact tables), duplicates are expected and handled by views:
# Check total vs uniquecurl -X POST http://localhost:3001/api/query \ -d '{"sql": "SELECT COUNT(*) as total, COUNT(DISTINCT id) as unique FROM table_name"}'
If total > unique: This is normal for fact tables. Use deduplication view:
-- Query deduplicated viewSELECT * FROM table_name_dedup;
# DuckDB explain plancurl -X POST http://localhost:3001/api/query \ -d '{"sql": "EXPLAIN SELECT * FROM large_table WHERE status = 'active'"}'# DuckDB analyze querycurl -X POST http://localhost:3001/api/query \ -d '{"sql": "EXPLAIN ANALYZE SELECT * FROM large_table WHERE status = 'active'"}'
# Small tables (< 100K rows)BATCH_SIZE=5000# Medium tables (100K - 1M rows)BATCH_SIZE=10000 # Default# Large tables (> 1M rows)BATCH_SIZE=20000# Very large tables (> 10M rows)BATCH_SIZE=50000
# Test connectioncurl -X POST http://localhost:3001/api/databases/lms/test# Health checkcurl 'http://localhost:3001/health?db=lms'# Sync specific databasecurl -X POST 'http://localhost:3001/api/sync/full?db=lms'# Query specific databasecurl -X POST 'http://localhost:3001/api/query?db=lms' \ -d '{"sql": "SELECT COUNT(*) FROM users"}'
# View sync_log tablecurl -X POST http://localhost:3001/api/query \ -d '{"sql": "SELECT * FROM sync_log ORDER BY created_at DESC LIMIT 10"}'# Check watermarkscurl -X POST http://localhost:3001/api/query \ -d '{"sql": "SELECT table_name, watermark_after FROM sync_log WHERE status = 'success' ORDER BY created_at DESC"}'