Today’s YugabyteDB Tip is a follow-up to Making yb-admin list_snapshots Output Valid JSON tip!
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:
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, anddatabase_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!
