Skip to content

feat(cli): add sync observability with per-table progress and timing#207

Merged
MatLBS merged 4 commits intogetnao:mainfrom
ealexisaraujo:feat/sync-observability-206
Feb 16, 2026
Merged

feat(cli): add sync observability with per-table progress and timing#207
MatLBS merged 4 commits intogetnao:mainfrom
ealexisaraujo:feat/sync-observability-206

Conversation

@ealexisaraujo
Copy link
Contributor

@ealexisaraujo ealexisaraujo commented Feb 14, 2026

Summary

Improve nao sync observability so users always know what the CLI is doing during long-running database syncs, and pre-filter schemas to avoid unnecessary Snowflake queries.

Changes in provider.py

  • Per-table progress — the progress bar description now shows SCHEMA → TABLE_NAME so users always see which table is being synced
  • MofNCompleteColumn — progress bar shows 329/416 (completed/total) instead of just a percentage
  • TimeElapsedColumn — elapsed time ticks in real-time as a built-in heartbeat, so users know the sync is alive even during slow queries
  • Connection timing — logs how long db_config.connect() takes (e.g., Connected to my-db (1.9s))
  • Schema discovery timing — logs schema count and duration (e.g., Found 3 schemas (297ms))
  • Per-schema entry log — when entering a schema, shows matched vs total table count and list_tables() duration (e.g., ▸ TRANSFORM — 416 tables (of 416 total, listed in 1.2s))
  • Slow query warnings — any accessor (columns/description/preview) taking >5 seconds is logged with its name and duration (e.g., ⏱ TRANSFORM.LARGE_TABLE description took 1m16s)
  • Error context — errors now show which accessor failed, how long it ran before failing, and the error message (e.g., ✗ TRANSFORM.BAD_TABLE preview failed after 3.5s: Numeric value...)
  • Per-schema completion summary — when a schema finishes, logs table count, duration, and error count (e.g., ✓ TRANSFORM — 416 tables synced in 38m0s (1 errors))
  • Total error summary — if any errors occurred, shows the total count at the end
  • Total sync duration — final summary includes overall time (e.g., 654 tables across 3 datasets in 50m11s)

Changes in snowflake.py

  • Schema pre-filtering — new _schema_matches() method extracts schema prefixes from include/exclude patterns (e.g., ANALYTICS from ANALYTICS.*) and filters schemas before entering the per-table sync loop
  • This avoids calling list_tables() to Snowflake for schemas that will have zero matching tables, reducing unnecessary API calls and warehouse cost

Motivation

When running nao sync on large databases (e.g., Snowflake with 100B+ row tables), several problems make the CLI difficult to use:

  1. Progress bar appears stuck — it only updates per-schema, so within a schema with 400+ tables, users see no movement for 30+ minutes
  2. No heartbeat — impossible to tell if the CLI is working on a slow COUNT(*) or has crashed
  3. No timing data — users can't identify which tables/accessors cause slowdowns to make informed decisions about their config
  4. Generic errors — when a template fails, the error doesn't say which accessor (columns/description/preview) failed or how long it ran
  5. Wasted queriesget_schemas() returns all schemas from the database even when include patterns limit to a subset, causing unnecessary list_tables() calls for non-matching schemas

Why these accessors are slow

The three default templates have very different performance characteristics:

Accessor Snowflake Query Typical Duration
columns INFORMATION_SCHEMA.COLUMNS metadata Sub-second
description SELECT COUNT(*) FROM table Minutes on 100B+ row tables
preview SELECT * FROM table LIMIT 10 Seconds, but can fail on type casting issues

Without timing, users had no visibility into which accessor was causing the sync to appear hung.

Example output (before vs after)

Before

🗄️  Syncing Databases
snowflake-prod: columns, description, preview

✗ Error generating preview.md for TRANSFORM.TABLE_WITH_BAD_DATA: Numeric value '11,380' is not recognized
⠦ snowflake-prod ━━━━━━━━━━━━━━━━━━━━━━━━━━━━╺━  94%
    SALES        ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100%
⠦   TRANSFORM   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━   1%

(No visibility into which table, which accessor, how long, or whether it's still alive)

After

🗄️  Syncing Databases
my-snowflake-db: columns, description, preview

  Connected to my-snowflake-db (1.9s)
  Found 3 schemas (297ms)
  ▸ ANALYTICS — 110 tables (of 110 total, listed in 621ms)
    ⏱ ANALYTICS.DIM_LARGE_TABLE description took 6.0s
  ✓ ANALYTICS — 110 tables synced in 4m57s
  ▸ SALES — 128 tables (of 128 total, listed in 597ms)
    ⏱ SALES.FACT_LARGE_TABLE description took 1m51s
  ✓ SALES — 128 tables synced in 7m14s
  ▸ TRANSFORM — 416 tables (of 416 total, listed in 1.2s)
⠋     TRANSFORM → STG_DIM_SOME_TABLE ━━━━━━━━━╸━━━━━ 329/416 79% 0:30:31
    ✗ TRANSFORM.BAD_TABLE preview failed after 3.5s: Numeric value '11,380' is not recognized
    ⏱ TRANSFORM.VERY_WIDE_TABLE description took 1m16s
    ⏱ TRANSFORM.VERY_WIDE_TABLE preview took 3m42s
  ✓ TRANSFORM — 416 tables synced in 38m0s (1 errors)
  ⚠ 1 total errors during sync

Files changed

File Change
cli/nao_core/commands/sync/providers/databases/provider.py Add timing, per-table progress, MofNCompleteColumn, TimeElapsedColumn, slow query warnings, error context, schema/total summaries
cli/nao_core/config/databases/snowflake.py Add _schema_matches() for schema-level pre-filtering based on include/exclude patterns

Test plan

  • Run nao sync -p databases against a Snowflake database with multiple schemas and hundreds of tables
  • Verify connection timing appears on startup
  • Verify schema count reflects only included schemas (not all database schemas)
  • Verify per-table name shows in progress bar during sync
  • Verify MofNCompleteColumn shows N/M count (e.g., 329/416)
  • Verify elapsed time column ticks in real-time
  • Verify slow queries (>5s) produce a warning with accessor name and duration
  • Verify errors show accessor name, duration, and error message
  • Verify per-schema completion summary appears with table count, duration, and error count
  • Verify total sync duration appears in final summary
  • Verify make lint passes (ty, ruff check, ruff format)
  • Verify empty include/exclude lists still return all schemas (no regression)

Fixes #206

🤖 Generated with Claude Code

aaraujodata and others added 4 commits February 13, 2026 19:42
…d slow query warnings

During `nao sync` on large databases, the progress bar appears stuck
because it only updates per-schema with no visibility into per-table
progress or query timing. This makes it impossible to tell if the sync
is working or hung.

Add timing instrumentation and granular progress reporting:
- Connection and schema discovery timing
- Per-schema summary (matched vs total tables, list duration)
- Live table name in progress bar showing current work
- Elapsed time column as a built-in heartbeat
- Slow query warnings (>5s) with accessor name and duration
- Error context showing which accessor failed and how long it ran
- Per-schema completion summary with table count, duration, error count
- Total sync duration in final summary

Fixes getnao#206

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…d slow query warnings

During `nao sync` on large databases, the progress bar updates per-schema
but appears stuck within a schema because there's no per-table feedback.
Users can't tell if the sync is working or hung.

Add timing instrumentation and granular progress reporting:
- Connection and schema discovery timing
- Per-schema summary (matched vs total tables, list duration)
- Live table name in progress bar showing current work
- Elapsed time column as a built-in heartbeat
- Table count column (329/416) showing completed vs total
- Slow query warnings (>5s) with accessor name and duration
- Error context showing which accessor failed and how long it ran
- Per-schema completion summary with table count, duration, error count
- Total sync duration in final summary

Fixes getnao#206

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Previously get_schemas() returned all schemas from the database, and
filtering only happened at the table level via matches_pattern(). This
meant nao would query list_tables() for schemas that would ultimately
have zero matching tables, wasting time on unnecessary Snowflake calls.

Add _schema_matches() to extract the schema prefix from include/exclude
patterns (e.g., ANALYTICS from ANALYTICS.*) and filter schemas before
entering the per-table sync loop.

Refs getnao#206

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The sync observability changes added multiple console.print calls
(connection timing, schema discovery, per-schema summary, etc.) so
tests that expected a single print call or checked only the last call
now need to search through all calls to find the error line.

Add find_print_call_containing() helper to search all console.print
calls for the expected error marker, replacing assertions on
call_args (last call) and assert_called_once.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@ealexisaraujo ealexisaraujo force-pushed the feat/sync-observability-206 branch from bc209c5 to b0b4f29 Compare February 14, 2026 15:24
@MatLBS
Copy link
Contributor

MatLBS commented Feb 16, 2026

Hi ealexisaraujo 👋, thank you so much for your PR,

I tried it and everything works well

@MatLBS MatLBS merged commit 8816727 into getnao:main Feb 16, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Improve nao sync observability: per-table progress, timing, and slow query warnings

3 participants