Mapping YugabyteDB Snapshots Back to Their Databases

Today’s YugabyteDB Tip is a follow-up to Making yb-admin list_snapshots Output Valid JSON tip!

A few days ago we looked at how to turn the output of command yb-admin list_snapshots:
				
					yb-admin -master_addresses 127.0.0.1:7100 list_snapshots
				
			

… into clean, parseable JSON.

But there’s a natural next question:

“Can we determine which database each snapshot belongs to?”

Yes… and YugabyteDB already gives us everything we need! The trick is buried inside the details array of each snapshot.

Let’s walk through how to extract this information and optionally enhance the JSON to make snapshots fully self-describing.

πŸ” Every snapshot includes its database… here’s where to find it

Whenever you call:

				
					yb-admin ... list_snapshots show_details
				
			

each snapshot lists all its captured objects, including a NAMESPACE entry:

				
					{
  "type": "NAMESPACE",
  "id": "000034cb000030008000000000000000",
  "data": {
    "name": "yugabyte",
    "database_type": "YQL_DATABASE_PGSQL"
  }
}

				
			

For YSQL snapshots:

  • ● data.name β†’ database name

  • ● data.database_type β†’ YQL_DATABASE_PGSQL

  • ● id β†’ namespace ID

For YCQL snapshots, database_type will be YQL_DATABASE_CQL.

Any tables included in the snapshot point back to the same namespace via:

				
					"namespace_id": "000034cb000030008000000000000000",
"namespace_name": "yugabyte"
				
			

So the relationship looks like this:

				
					snapshot
  └── details[]
        └── NAMESPACE β†’ database name + database type + namespace ID
				
			
🐍 Updated Python script: now with database metadata!

Below is the full updated script that:

  • ● Parses snapshot summaries

  • ● Parses detailed snapshot metadata

  • ● Attaches the database name, database type, and namespace UUID directly into each snapshot object

Save asΒ yb_snapshots_to_json_updated.py:

				
					#!/usr/bin/env python3
import json
import os
import re
import subprocess
import sys


def yb_admin(args):
    """
    Run yb-admin with the configured master address.

    Environment:
      YB_MASTER = "host:port" or comma-separated list (default 127.0.0.1)
    """
    master_addrs = os.environ.get("YB_MASTER", "127.0.0.1")
    cmd = ["yb-admin", "--init_master_addrs", master_addrs] + args
    return subprocess.check_output(cmd, text=True)


def parse_summary(text):
    """
    Parse `yb-admin list_snapshots` output into a dict keyed by snapshot UUID.

    Expected format (simplified):

      Snapshot UUID                       State      Creation Time                Previous Snapshot Time
      e3cd52fb-...                        COMPLETE   2025-11-23 03:52:56.308819   2112-09-17 23:53:47.370495
    """
    snaps = {}

    # id state snapshot_time [previous_snapshot_time]
    pattern = re.compile(r"^(\S+)\s+(\S+)\s+(\S+\s+\S+)(?:\s+(\S+\s+\S+))?")

    for line in text.splitlines():
        s = line.strip()
        if not s:
            continue

        # skip header/footer lines
        if s.startswith("Snapshot UUID") or s.startswith("No snapshot restorations"):
            continue

        m = pattern.match(s)
        if not m:
            continue

        sid, state, ts, prev_ts = m.groups()
        snaps[sid] = {
            "id": sid,
            "state": state,
            "snapshot_time": ts,
            "previous_snapshot_time": prev_ts or None,
            "details": [],
        }

    return snaps


def parse_details(text):
    """
    Parse `yb-admin list_snapshots show_details` output.

    Example (simplified):

      Snapshot UUID                     State     Creation Time
      e3cd52fb-...                      COMPLETE  2025-11-23 03:52:56.308819
        {"type":"NAMESPACE", ...}
        {"type":"TABLE", ...}
      4444301c-...                      COMPLETE  2025-11-23 03:55:46.552879
        {"type":"NAMESPACE", ...}
        {"type":"TABLE", ...}
      No snapshot restorations
    """
    snap_header = re.compile(r"^([0-9a-f-]+)\s+")
    grouped = {}
    current = None

    for line in text.splitlines():
        s = line.strip()
        if not s:
            continue

        if s.startswith("Snapshot UUID") or s.startswith("No snapshot restorations"):
            continue

        m = snap_header.match(s)
        if m:
            current = m.group(1)
            grouped.setdefault(current, [])
            continue

        if s.startswith("{") and current:
            grouped[current].append(json.loads(s))

    return grouped


def main():
    try:
        summary_txt = yb_admin(["list_snapshots"])
        details_txt = yb_admin(["list_snapshots", "show_details"])
    except subprocess.CalledProcessError as e:
        sys.stderr.write(f"yb-admin failed:\n{e.output}\n")
        sys.exit(1)

    summary = parse_summary(summary_txt)
    details = parse_details(details_txt)

    # stitch details + derive database metadata
    for sid, snap in summary.items():
        dlist = details.get(sid, [])
        snap["details"] = dlist

        # find NAMESPACE entries (YSQL DB or YCQL keyspace)
        namespaces = [d for d in dlist if d.get("type") == "NAMESPACE"]
        if namespaces:
            ns_entry = namespaces[0]
            ns_data = ns_entry.get("data", {})

            snap["database_name"] = ns_data.get("name")
            snap["database_type"] = ns_data.get("database_type")
            snap["database_namespace_id"] = ns_entry.get("id")

    snapshots = list(summary.values())

    print(json.dumps({"snapshots": snapshots}, indent=2))


if __name__ == "__main__":
    main()
				
			

Example usage:

Here’s the JSON out from the list_snapshots command:
				
					[root@localhost ~]# yb-admin list_snapshots show_details JSON
 	{"type":"NAMESPACE","id":"00004000000030008000000000000000","data":{"name":"testdb","database_type":"YQL_DATABASE_PGSQL","next_normal_pg_oid":16640,"colocated":false,"state":"RUNNING","ysql_next_major_version_state":"NEXT_VER_RUNNING"}}
 	{"type":"TABLE","id":"00004000000030008000000000004000","data":{"name":"t1","version":0,"state":"RUNNING","next_column_id":1,"table_type":"PGSQL_TABLE_TYPE","namespace_id":"00004000000030008000000000000000","namespace_name":"testdb","pg_table_id":"00004000000030008000000000004000"}}
 	{"type":"TABLE","id":"00004000000030008000000000004005","data":{"name":"t2","version":0,"state":"RUNNING","next_column_id":2,"table_type":"PGSQL_TABLE_TYPE","namespace_id":"00004000000030008000000000000000","namespace_name":"testdb","pg_table_id":"00004000000030008000000000004005"}}
 	{"type":"NAMESPACE","id":"00004001000030008000000000000000","data":{"name":"another_testdb","database_type":"YQL_DATABASE_PGSQL","next_normal_pg_oid":16640,"colocated":false,"state":"RUNNING","ysql_next_major_version_state":"NEXT_VER_RUNNING"}}
 	{"type":"TABLE","id":"00004001000030008000000000004000","data":{"name":"t1","version":0,"state":"RUNNING","next_column_id":1,"table_type":"PGSQL_TABLE_TYPE","namespace_id":"00004001000030008000000000000000","namespace_name":"another_testdb","pg_table_id":"00004001000030008000000000004000"}}
{
    "snapshots": [
        {
            "id": "381ac5f2-ef73-43f1-9c7d-2962b99c25bc",
            "state": "COMPLETE",
            "snapshot_time": "2025-11-24 17:25:26.220491",
            "previous_snapshot_time": "2112-09-17 23:53:47.370495"
        },
        {
            "id": "0f30da8b-e1ec-478e-9ce9-c5f476a9ed5f",
            "state": "COMPLETE",
            "snapshot_time": "2025-11-24 18:47:41.429324",
            "previous_snapshot_time": "2112-09-17 23:53:47.370495"
        }
    ]
}
				
			

Let’s try the new Python script:

				
					[root@localhost ~]# export YB_MASTER=127.0.0.1

[root@localhost ~]# ./yb_snapshots_to_json_updated.py > snapshots.json

[root@localhost ~]# cat snapshots.json
{
  "snapshots": [
    {
      "id": "381ac5f2-ef73-43f1-9c7d-2962b99c25bc",
      "state": "COMPLETE",
      "snapshot_time": "2025-11-24 17:25:26.220491",
      "previous_snapshot_time": null,
      "details": [
        {
          "type": "NAMESPACE",
          "id": "00004000000030008000000000000000",
          "data": {
            "name": "testdb",
            "database_type": "YQL_DATABASE_PGSQL",
            "next_normal_pg_oid": 16640,
            "colocated": false,
            "state": "RUNNING",
            "ysql_next_major_version_state": "NEXT_VER_RUNNING"
          }
        },
        {
          "type": "TABLE",
          "id": "00004000000030008000000000004000",
          "data": {
            "name": "t1",
            "version": 0,
            "state": "RUNNING",
            "next_column_id": 1,
            "table_type": "PGSQL_TABLE_TYPE",
            "namespace_id": "00004000000030008000000000000000",
            "namespace_name": "testdb",
            "pg_table_id": "00004000000030008000000000004000"
          }
        },
        {
          "type": "TABLE",
          "id": "00004000000030008000000000004005",
          "data": {
            "name": "t2",
            "version": 0,
            "state": "RUNNING",
            "next_column_id": 2,
            "table_type": "PGSQL_TABLE_TYPE",
            "namespace_id": "00004000000030008000000000000000",
            "namespace_name": "testdb",
            "pg_table_id": "00004000000030008000000000004005"
          }
        }
      ],
      "database_name": "testdb",
      "database_type": "YQL_DATABASE_PGSQL",
      "database_namespace_id": "00004000000030008000000000000000"
    },
    {
      "id": "0f30da8b-e1ec-478e-9ce9-c5f476a9ed5f",
      "state": "COMPLETE",
      "snapshot_time": "2025-11-24 18:47:41.429324",
      "previous_snapshot_time": null,
      "details": [
        {
          "type": "NAMESPACE",
          "id": "00004001000030008000000000000000",
          "data": {
            "name": "another_testdb",
            "database_type": "YQL_DATABASE_PGSQL",
            "next_normal_pg_oid": 16640,
            "colocated": false,
            "state": "RUNNING",
            "ysql_next_major_version_state": "NEXT_VER_RUNNING"
          }
        },
        {
          "type": "TABLE",
          "id": "00004001000030008000000000004000",
          "data": {
            "name": "t1",
            "version": 0,
            "state": "RUNNING",
            "next_column_id": 1,
            "table_type": "PGSQL_TABLE_TYPE",
            "namespace_id": "00004001000030008000000000000000",
            "namespace_name": "another_testdb",
            "pg_table_id": "00004001000030008000000000004000"
          }
        }
      ],
      "database_name": "another_testdb",
      "database_type": "YQL_DATABASE_PGSQL",
      "database_namespace_id": "00004001000030008000000000000000"
    }
  ]
}
				
			

Beautiful… each snapshot is now fully self-describing.Β  That is, we see that the database names as testdbΒ and anoher_testdb.

Show only snapshot ID, time, DB type, DB name
				
					[root@localhost ~]# jq -r '
  .snapshots[] |
  {
    id: .id,
    snapshot_time: .snapshot_time,
    database_type: .database_type,
    database_name: .database_name
  }
' snapshots.json
{
  "id": "381ac5f2-ef73-43f1-9c7d-2962b99c25bc",
  "snapshot_time": "2025-11-24 17:25:26.220491",
  "database_type": "YQL_DATABASE_PGSQL",
  "database_name": "testdb"
}
{
  "id": "0f30da8b-e1ec-478e-9ce9-c5f476a9ed5f",
  "snapshot_time": "2025-11-24 18:47:41.429324",
  "database_type": "YQL_DATABASE_PGSQL",
  "database_name": "another_testdb"
}
				
			
🏁 Summary

Every YugabyteDB snapshot already includes its database/keyspace info… you just have to pull it from the NAMESPACE detail entry.

This update:

  • ● Surfaces database_name, database_type, and database_namespace_id

  • ● Makes snapshot inventories vastly easier

  • ● Enables database-level filtering/reporting

  • ● Helps build tooling similar to YBA’s snapshot browser

  • ● Produces clean, fully self-contained JSON

Have Fun!