Zaroz
Operar servicios

Bases de datos PostgreSQL

Hay dos tipos de base de datos en Zaroz y la misma API lee ambos.

Base de datos incluidaPostgreSQL dedicado
Qué esUna base de datos en un servidor compartido, incluida con una suscripciónUn servidor PostgreSQL propio, vendido como producto con su plan y volumen
CosteIncluido: diez bases de 64 MiB por suscripciónSe factura por su plan (CPU, memoria, almacenamiento), como cualquier servicio
AislamientoServidor compartido, tu propia base y usuarioTu propia instancia y credenciales
Límite de almacenamientoUna cuota (64 MiB); bloquea la escritura al superarlaLo marca su propio volumen; sin cuota
Se crea conPOST /subscriptions/{id}/shared-databasesComprando el producto PostgreSQL (Suscripciones y facturación)

Un PostgreSQL dedicado ejecuta una versión mayor de PostgreSQL fija y anclada, que la consola muestra en el servicio. Zaroz no promete ninguna extensión concreta: usa lo que trae esa versión y consulta con soporte antes de depender de otra cosa. El volumen de una base dedicada se puede copiar y restaurar como el de cualquier servicio; consulta Copias de seguridad y restauración.

Los ejemplos usan el ayudante de Usar la API de Zaroz.

Un despliegue puede ofrecer menos funciones de base de datos que otro. Pregunta qué ofrece y espera 501 capability-not-available para lo que falte.

Crear una base de datos incluida

Comprueba lo que te queda y qué motores aloja este despliegue, y crea una.

curl

TerminalCode
curl -sS "$ZAROZ_API/subscriptions/$SUBSCRIPTION/shared-databases/entitlement" -H "$AUTH" curl -sS "$ZAROZ_API/subscriptions/$SUBSCRIPTION/shared-databases/engines" -H "$AUTH" # {"engines": ["postgres", ...]} curl -sS -X POST "$ZAROZ_API/subscriptions/$SUBSCRIPTION/shared-databases" -H "$AUTH" \ -H "Content-Type: application/json" -d '{"engine": "postgres", "display_name": "app-db"}'
JSONCode
{ "data": { "id": "7c0f…", "display_name": "app-db", "engine": "postgres", "database": "db_3f9a…", "username": "u_3f9a…", "password": "…", "external_host": "db.example.zaroz.cloud", "external_port": 5432, "internal_host": "…", "internal_port": 5432, "quota_bytes": 67108864, "included": true } }

TypeScript

TypeScriptCode
import { call } from "./zaroz"; const ent = await call("GET", `/subscriptions/${SUB}/shared-databases/entitlement`); console.log(ent.body); const db = await call("POST", `/subscriptions/${SUB}/shared-databases`, { engine: "postgres", display_name: "app-db", }); const d = db.body.data; console.log(`postgresql://${d.username}:${d.password}@${d.external_host}:${d.external_port}/${d.database}`); const DATABASE_ID = d.id;

Python

Code
from zaroz import call _, ent = call("GET", f"/subscriptions/{SUB}/shared-databases/entitlement") print(ent) _, db = call("POST", f"/subscriptions/{SUB}/shared-databases", {"engine": "postgres", "display_name": "app-db"}) d = db["data"] print(f"postgresql://{d['username']}:{d['password']}@{d['external_host']}:{d['external_port']}/{d['database']}") DATABASE_ID = d["id"]
  • Los campos de la respuesta son todo lo que necesita un cliente: external_host y external_port desde internet, internal_host e internal_port desde dentro de Zaroz (úsalos desde un Workload de la misma región cuando internal_connection_available es verdadero).
  • No puedes tener más de diez: la undécima responde 429 quota-exceeded. region_id es opcional; si lo das y ningún host sirve esa región y motor, la respuesta es 503 en lugar de colocarla en otro sitio.
  • La suscripción debe estar activa (422 si no), y crear requiere la autoridad para comprar por la organización (Owner o Admin).
  • GET /subscriptions/{id}/shared-databases las lista y muestra la contraseña de cada una, así que trata esa respuesta como secreta.
  • Rota la contraseña con POST .../shared-databases/{database_id}/rotate-credentials (solo cambia la contraseña) y borra con DELETE .../shared-databases/{database_id}: el hueco se libera al instante y los datos permanecen un breve periodo de recuperación en el host antes de eliminarse.

Conectar con una base dedicada

Un PostgreSQL dedicado es una instancia de servicio. Su usuario y contraseña se eligen en su configuración: lee las respuestas con GET /service-instances/{id}/setup y, para una contraseña que necesites volver a ver, la ruta de revelado (POST /service-instances/{id}/setup/steps/server_passwords/{password_id}/reveal), que queda registrada en el registro de actividad. Puedes abrirle un puerto público como a cualquier servicio (Puertos públicos y endpoints); mantenlo cerrado salvo que lo necesites desde fuera.

El id de la base de datos

Todo lo siguiente se direcciona con un id:

  • para una base incluida, el id de la base (el id de la respuesta de creación);
  • para una base dedicada, el id de instancia de servicio del servicio PostgreSQL.

Las rutas son /databases/{database_id}/.... Leer requiere acceso a la suscripción; cambiar cosas requiere el rol Owner o Admin (un miembro normal puede explorar pero no cambiar). Un id que no puedes usar responde 404, igual que uno que no existe.

Listar tablas

curl

TerminalCode
curl -sS "$ZAROZ_API/databases/$DATABASE_ID/tables" -H "$AUTH" curl -sS "$ZAROZ_API/databases/$DATABASE_ID/tables/users" -H "$AUTH"

TypeScript

TypeScriptCode
const tables = await call("GET", `/databases/${DATABASE_ID}/tables`); console.log(tables.body.data.map((t: any) => t.name));

Python

Code
_, tables = call("GET", f"/databases/{DATABASE_ID}/tables") print([t["name"] for t in tables["data"]])

Cada tabla lista sus columnas, su clave primaria y estimaciones de tamaño. Los tamaños (row_estimate, data_length, index_length) son estimaciones del motor, no recuentos; muéstralos como aproximaciones.

Leer filas

Las páginas usan cursor. limit es 50 por defecto y se recorta a 500, así que mira el limit de la respuesta. Devuelve next_cursor como cursor; nunca construyas un cursor tú. sort es column, column:asc o column:desc. filters es un array JSON de {column, operator, value}, donde operator es equals, not_equals, contains, starts_with, ends_with, greater_than, less_than, greater_than_or_equal, less_than_or_equal, is_null o is_not_null (los dos últimos no llevan value). Un sort o filters que no se pueda interpretar o nombre una columna desconocida se rechaza, nunca se ignora en silencio.

curl

TerminalCode
curl -sS -G "$ZAROZ_API/databases/$DATABASE_ID/tables/users/rows" -H "$AUTH" \ --data-urlencode 'limit=100' --data-urlencode 'sort=created_at:desc' \ --data-urlencode 'filters=[{"column":"email","operator":"ends_with","value":"@example.com"}]'

TypeScript

TypeScriptCode
const filters = JSON.stringify([{ column: "email", operator: "ends_with", value: "@example.com" }]); const q = new URLSearchParams({ limit: "100", sort: "created_at:desc", filters }); const page = await call("GET", `/databases/${DATABASE_ID}/tables/users/rows?${q}`); console.log(page.body.data);

Python

Code
import json from urllib.parse import urlencode filters = json.dumps([{"column": "email", "operator": "ends_with", "value": "@example.com"}]) q = urlencode({"limit": 100, "sort": "created_at:desc", "filters": filters}) _, page = call("GET", f"/databases/{DATABASE_ID}/tables/users/rows?{q}") print(page["data"])

Cambiar datos y estructura

POST inserta una fila (values asigna valor a cada columna), PATCH actualiza una y DELETE borra una, localizada por el valor de la clave primaria de una sola columna de la tabla: una tabla sin clave primaria, o con una compuesta, no se puede editar así (usa SQL). Crea una tabla con tipos de columna neutros (integer, decimal, float, text, boolean, date_time, date, time, json, binary, uuid); PATCH /tables/{name} añade, elimina, renombra o modifica columnas o renombra la tabla; DELETE /tables/{name} la elimina, para siempre. Cada una se valida contra las columnas reales de la tabla.

Ejecutar SQL

Una sentencia por llamada, ejecutada como el usuario propio de la base de datos, cancelada a los 15 segundos y registrada en el registro de actividad de la organización. Una sentencia que devuelve filas responde {"kind": "rows", "columns", "rows", "truncated"} (como máximo 200 filas); cualquier otra responde {"kind": "command", "affected_rows"}.

curl

TerminalCode
curl -sS -X POST "$ZAROZ_API/databases/$DATABASE_ID/sql" -H "$AUTH" -H "Content-Type: application/json" \ -d '{"sql": "SELECT id, email FROM users ORDER BY id LIMIT 10"}'

TypeScript

TypeScriptCode
const r = await call("POST", `/databases/${DATABASE_ID}/sql`, { sql: "SELECT count(*) AS n FROM users" }); if (r.body.data.kind === "rows") console.log(r.body.data.rows);

Python

Code
_, r = call("POST", f"/databases/{DATABASE_ID}/sql", {"sql": "SELECT count(*) AS n FROM users"}) if r["data"]["kind"] == "rows": print(r["data"]["rows"])

Para algo más grande que una consulta, conéctate con psql o con el controlador de tu aplicación: este endpoint es para inspeccionar y para arreglos pequeños.

Almacenamiento y cuota

curl

TerminalCode
curl -sS "$ZAROZ_API/databases/$DATABASE_ID/usage" -H "$AUTH" # {"database": "...", "data": {"used_bytes": 1048576, "data_length": ..., "index_length": ..., "quota_bytes": 67108864, "over_quota_since": null}}

Una base incluida tiene cuota; una dedicada devuelve quota_bytes: null y su tamaño lo marca el volumen. Cuando una base incluida supera la cuota queda bloqueada en solo lectura (over_quota_since se rellena) y todo lo que la haría crecer (insertar, actualizar, crear tabla, SQL en bruto) responde 507. Borrar se permite siempre, incluso eliminar tablas: borra datos y el bloqueo se levanta. La cifra se guarda hasta un minuto y se refresca con cualquier escritura hecha mediante esta API.

Exportar, importar y subir (bases incluidas)

Mover datos dentro y fuera de una base incluida se ejecuta como una operación en cola: la llamada responde 202 con un id de operación, no el resultado. Consulta la operación hasta que salga de pending/processing (completed, o dead_letter cuando se agotan los reintentos) y entonces descarga.

  • Exportar (POST .../transfers/export) escribe un volcado JSON solo de los datos de las filas, nunca las sentencias CREATE TABLE. Está disponible para PostgreSQL.
  • Importar (POST .../transfers/import, el cuerpo indica el source_operation_id de una exportación de la misma suscripción) restaura una exportación anterior, posiblemente en otra base de la suscripción. Las tablas deben existir ya con los mismos nombres y columnas; una fila de una tabla que no existe hace fallar toda la operación.
  • Subir (POST .../transfers/upload, el archivo en bruto como cuerpo) importa un volcado SQL: SQL plano o comprimido (.sql.gz), pg_dump --format=plain (incluidos COPY ... FROM stdin), hasta 256 MiB. Un archivo pg_dump -Fc, vacío o binario da 422. Las sentencias se ejecutan como el usuario de la base en orden y se detienen en el primer error, así que una importación fallida puede dejar aplicadas las sentencias anteriores. Se omiten USE, CREATE|DROP|ALTER DATABASE y ALTER ... OWNER TO.

curl

TerminalCode
T=/subscriptions/$SUBSCRIPTION/shared-databases/$DATABASE_ID/transfers # un volcado SQL directamente curl -sS -X POST "$ZAROZ_API$T/upload" -H "$AUTH" -H "Content-Type: application/octet-stream" \ --data-binary @dump.sql.gz # 202 {"operation_id": "...", "status": "pending", ...} curl -sS "$ZAROZ_API$T/$OPERATION" -H "$AUTH" # consultar curl -sS -X POST "$ZAROZ_API$T/export" -H "$AUTH" # 202 curl -sS -o export.json "$ZAROZ_API$T/$EXPORT_OPERATION/download" -H "$AUTH"

TypeScript

TypeScriptCode
import { readFile } from "node:fs/promises"; const T = `/subscriptions/${SUB}/shared-databases/${DATABASE_ID}/transfers`; const up = await fetch(`${process.env.ZAROZ_API}${T}/upload`, { method: "POST", headers: { Authorization: `Bearer ${process.env.ZAROZ_TOKEN}`, "Content-Type": "application/octet-stream" }, body: await readFile("dump.sql.gz"), }); const { operation_id } = await up.json(); for (;;) { const op = await call("GET", `${T}/${operation_id}`); if (op.body.status === "completed" || op.body.status === "dead_letter") { console.log(op.body.status, op.body.last_error); break; } await new Promise((r) => setTimeout(r, 3000)); }

Python

Code
import os, time, requests from zaroz import call T = f"/subscriptions/{SUB}/shared-databases/{DATABASE_ID}/transfers" with open("dump.sql.gz", "rb") as f: up = requests.post(os.environ["ZAROZ_API"] + T + "/upload", data=f, timeout=600, headers={ "Authorization": "Bearer " + os.environ["ZAROZ_TOKEN"], "Content-Type": "application/octet-stream", }) operation_id = up.json()["operation_id"] while True: _, op = call("GET", f"{T}/{operation_id}") if op["status"] in ("completed", "dead_letter"): print(op["status"], op["last_error"]) break time.sleep(3)

Consultar es siempre seguro: nunca molesta al worker. La importación se detiene, como fallida, si llena la cuota mientras restaura.

Qué hace distinto una base dedicada

La cuota, los límites de usage y las rutas de transferencia anteriores pertenecen a las bases incluidas. En una base dedicada, mueve los datos con pg_dump y psql por un puerto público, y apóyate en las copias de seguridad del servicio para recuperar. Las rutas de explorador y SQL funcionan en ambas.

Last modified on