Skip to content

iceberg: a dotted database or spoke ID builds an unaddressable namespace — stop emitting one, and migrate the tables already published under one #1129

Description

@xe-nvdk

Summary

Arc builds one Iceberg namespace component per database — nsPrefix + "_" + sanitizeNamespaceDB(database) (internal/iceberg/exporter.go, tableIdent) — and sanitizeNamespaceDB maps / to .. So a database name or edge-sync spoke ID carrying a dot produces a single namespace component containing a dot, e.g. arc_rocket.01.

iceberg-go v0.7.0 addresses such a namespace by a JSON-encoded catalog key rather than the plain dotted string, and deliberately refuses the legacy key:

// catalog/sql/sql.go, namespaceStorageKeys
// Only marker collisions get a legacy fallback. Dotted components must
// never fall back because their legacy key belongs to a different namespace.

Measured on v0.7.0:

tableIdent("rocket.01", "cpu") = []string{"arc_rocket.01", "cpu"}
catalog row, v0.6.0            = arc_rocket.01
catalog row, v0.7.0            = __iceberg_namespace_v1__:["arc_rocket.01"]

Three things then break at once, none of them loudly:

  1. History orphaned. A table published under v0.6.0 is no longer found, so EnsureTable correctly creates a new one. The original table and its whole snapshot history stay on disk, unreferenced, and any external reader pointed at the old path sees a frozen table.
  2. The exporter eats its own metadata. The new directory is __iceberg_namespace_v1__:["arc_rocket.01"].db, which isWarehouseDir (internal/iceberg/source.go) does not recognise — it matches nsPrefix + "_" … .db. So Measurements() walks it as a user database, one nesting level deeper per pass. That is exactly the harm the /-to-. mapping was added to prevent (high(iceberg): edge-sync hub spoke namespaces are exported as garbage tables — source discovery predates #619 #634).
  3. No reader entry point. MetadataLocation() returns a percent-encoded URI while the directory on disk is not encoded, so writeVersionHint publishes nowhere. DuckDB and Spark cannot resolve the table, and the discovery-file failure declines the scheduler's fingerprint cache, so the measurement is re-reconciled on every tick forever.

Current state (mitigated, not fixed)

The iceberg-go v0.7.0 bump ships two refusals so nothing is silently published broken:

  • iceberg.namespace_prefix containing a dot is refused at config load — it would poison every database on the node.
  • A database whose namespace component would contain a dot is refused per measurement (checkNamespaceAddressable, called from EnsureTable), logged and skipped, so the rest of the node keeps exporting. This covers the dynamic case a config check cannot: a spoke registering after startup.

So new broken tables are no longer created. What remains unfixed is any table already published under a dotted namespace: it is now refused rather than served, and there is no migration.

How reachable is a dotted database?

Arc's own database names are dot-free by the create-time rule. The one source that permits a dot is an edge-sync spoke ID: validateSpokeID (internal/edgesync/receive.go) rejects /, \, : and .., but a single dot passes — rocket.01, site.a, plant.2 are all valid today. The Iceberg source gets spoke namespaces unconditionally when both features are on (cmd/arc/main.go, icebergSource.SetNamespaceExpander(receivedNamespaces(spokeRegistry))).

What this issue wants

Two stages, and they have to ship together — the first alone would orphan tables the same way the bump does.

Stage 1 — stop building a dotted component. Options, in rough order of preference:

  • Emit a two-component namespace for a spoke, {nsPrefix + "_" + spoke, db}, instead of folding the separator into a dot. EnsureTable's ns := icetable.Identifier{ident[0]} assumes a one-component namespace and would need widening.
  • Or map . to _ alongside /, accepting that a.b and a_b then collide — which is the collision class More lossy encodings that become paths, keys or authorization decisions #750 is about, so it needs a disambiguator rather than a plain replacement.

Stage 2 — migrate tables already published under a dotted namespace. Rewrite the catalog row and move the warehouse directory to whatever Stage 1 chooses, preserving the metadata chain and republishing version-hint.text. Needs to be idempotent, safe to interrupt, and to fail closed: a half-migrated table must stay readable at its old location until the new one is complete. Dry-run first, like reconciliation.manifest_only_dry_run.

Worth validating the spoke-ID rule at the same time: if Iceberg export cannot serve a dotted spoke ID, validateSpokeID arguably should reject a dot at registration, which is cheaper than migrating later. That is a compatibility decision for existing spokes, not an obvious yes.

Note

Internal work item. Arc's Iceberg exporter is not taking community contributions, so this is filed for tracking rather than as an invitation to submit a patch.

Refs #634, #750. Found while bumping iceberg-go to v0.7.0.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workinginternalMaintained internally; filed for tracking, not open to community PRspriority: highData loss, wrong results or availability; fix first

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions