OBLETHdocs

Postgres schema

Every Postgres table that obleth and its dashboard use, with each column's type and default and the migration that added it.

18 min readUpdated for v2.4.0Edit on GitHub

Postgres holds configuration and the audit trail: tenants, keys, models, endpoints, MCP servers, knowledge collections, Slurm specs, settings, and the dashboard's login tables. Usage history lives in ClickHouse and hot state in Redis; see ClickHouse usage and Redis layout.

Migrations

The gateway compiles schema/postgres/0001 to 0035 into its binary and applies all of them in order on every boot, under a Postgres advisory lock so replicas don't race. Every statement is idempotent (if not exists, guarded constraints), so there is no version table. A configuration backup covers fairshare_groups, tenants, api_keys, models, model_endpoints, mcp_servers, model_families, and app_settings.

FileAdds
0001_init.sqlfairshare_groups, tenants, api_keys, audit_log, models, model_health_checks, model_endpoints, managed_models, model_replicas, mcp_servers, app_settings
0002_tracing_flag.sqltracing_enabled on tenants and api_keys
0003_guardrails_policy.sqltenants.guardrails_policy
0004_saved_recipes.sqlsaved_recipes, dropped again by 0006
0005_managed_launcher_spec.sqlmanaged_models.launcher_spec
0006_recipes.sqlrecipes; drops saved_recipes
0007_replica_port_and_min_replicas.sqlmodel_replicas.port_base, managed_models.min_replicas
0008_managed_provision_error.sqllast_provision_error, last_provision_error_at on managed_models
0009_replica_cancel_requested.sqlmodel_replicas.cancel_requested
0010_drop_replica_model_cascade.sqlDrops the model_replicas → models foreign key
0011_endpoint_selection_session_hash.sqlAllows session_hash in endpoint_selection_mode
0012_model_debug_diagnostics.sqlmodels.debug_diagnostics
0013_compression_policy.sqltenants.compression_policy
0014_model_energy_slots.sqlmodels.energy_slots_per_node
0015_tenant_synthetic.sqltenants.synthetic
0016_api_keys_identity.sqlIdentity-key columns on api_keys and their unique index
0017_model_route_bias.sqlmodels.route_bias
0018_knowledge_base.sqlknowledge_collections, knowledge_documents, knowledge_chunks, model_knowledge_collections
0019_model_auto_eligible.sqlmodels.auto_eligible
0020_knowledge_chunk_unique.sqlUnique (document_id, generation, ordinal) on knowledge_chunks
0021_knowledge_reindex_requested.sqlknowledge_documents.reindex_requested
0022_model_verifier_for.sqlmodels.verifier_for, unread since 0023
0023_model_speculation_fields.sqldraft_model, verify_api_base, verify_upstream_model on models
0024_model_aliases_and_quantization.sqlaliases (GIN-indexed) and quantization on models
0025_api_key_fairshare.sqlweight and max_in_flight on api_keys
0026_model_upstream_headers.sqlmodels.upstream_headers
0027_model_cost_per_video.sqlmodels.cost_per_video
0028_video_jobs.sqlvideo_jobs and two indexes
0029_model_capacity_discovery.sqlCapacity discovery columns on models, model_endpoints.max_in_flight, discovered capacity mode
0030_video_jobs_key_index.sqlIndex on video_jobs (tenant_id, key_id, created_at desc)
0031_deployment_launches.sqldeployment_launches, the launch history of Slurm-provisioned replicas, and two indexes
0032_api_key_end_user_fairshare.sqlapi_keys.end_user_fairshare
0033_model_variants.sqlmodels.variants and its GIN index
0034_model_lifecycle.sqlmodels.lifecycle
0035_reasoning_effort.sqlreasoning and listed on models; model_families
0036_model_rejected_request_fields.sqlmodels.rejected_request_fields
0037_job_kind.sqlvideo_jobs.kind
0038_model_session_affinity.sqlmodels.session_affinity

Tenants and keys

fairshare_groups

Seeded with a default group of weight 100.

ColumnTypeDefault and notes
nametext primary key
weightbigint not null100; at least 1
created_at, updated_attimestamptz not nullnow()

tenants

Indexed by fairshare_group, and by status where it is not active.

ColumnTypeDefault and notes
iduuid primary key
nametext not null unique
weightbigint not null100; fairshare weight, at least 1
tokens_per_minutebigint not null0 = no per-minute cap
max_in_flightbigintCap within each model's pool; null = none
fairshare_grouptext not null'default'; references fairshare_groups.name
description, organization, contact_emailtext not null''
statustext not null'active'; or suspended, archived. Only active admits traffic
tracing_enabledboolean not nullfalse; record spans for every key (0002)
timezonetext not null'UTC'; IANA zone for the access windows
active_from, active_untiltimestamptzActivation window; null = unbounded
weekly_windowsjsonb[{day, start_min, end_min}], day 0 = Sunday; null or empty = any time
budget_tokensbigintTerm token cap
budget_cost_usddouble precisionTerm USD cap
budget_periodtextlifetime, monthly, or term
budget_started_attimestamptzStart of the current term
allowed_modelsjsonbModel allowlist; null or empty = all
guardrails_policyjsonbGuardrails policy (0003)
compression_policyjsonbCompression policy; null = global defaults (0013)
syntheticboolean not nullfalse; traffic recorded as request_type = 'benchmark' (0015)
created_at, updated_attimestamptz not nullnow()

api_keys

Only the SHA-256 of a secret is stored, peppered when OBLETH_API_KEY_PEPPER is set. An identity key has no secret: its key_hash is jwt: plus the SHA-256 of issuer, a NUL byte, and subject. Indexed by tenant_id, with one identity key per (identity_issuer, identity_subject). See Tenants and keys.

ColumnTypeDefault and notes
iduuid primary key
tenant_iduuid not nullReferences tenants.id, on delete cascade
nametext not null
descriptiontext not null''
key_prefixtext not nullFirst 18 characters of the secret; jwt:<subject> for identity keys
key_hashtext not null uniqueLookup handle, also the Redis key
kindtext not null'secret' or identity (0016)
identity_issuer, identity_subjecttextIdentity keys only (0016)
identity_claimsjsonbClaims captured at first use (0016)
budget_tokens, budget_cost_usd, budget_period, budget_started_atas on tenantsKey-level term budget
weightbigint not null100; share among the tenant's keys, at least 1 (0025)
max_in_flightbigintKey cap within each model's pool; null = none (0025)
disabledboolean not nullfalse
tracing_enabledboolean not nullfalse (0002)
end_user_fairshareboolean not nullfalse; each end user the key names queues on their own (0032)
created_at, updated_attimestamptz not nullnow()

Models

models

Indexed by enabled (partial), by health_next_check_at for enabled models with checks on, and by aliases and variants (GIN). Field meanings are in Model registry.

ColumnTypeDefault and notes
iduuid primary key
model_nametext not null uniqueCanonical client-facing name
aliasesjsonb not null'[]'; other names that resolve to this model (0024)
variantsjsonb not null'[]'; [{name, description, boons}], names that resolve to this model with extra boons (0033). See Variants
descriptiontext not null''
model_typetext not null'chat'; or embedding, audio_transcription, audio_speech, image, video, search
quantizationtext not null'unknown'; or none, fp16, bf16, fp8, nvfp4, mxfp4, int8, int4, awq, gptq, gguf. Descriptive only (0024)
upstream_modeltext not nullName sent upstream
api_basetext not nullUpstream base URL
api_keytextEncrypted when OBLETH_ENCRYPTION_KEY is set
upstream_headersjsonb not null'{}'; lowercase name to value, values encrypted; the API returns names only (0026)
input_cost_per_token, output_cost_per_tokendouble precision not null0
cost_per_image, cost_per_audio_second, cost_per_characterdouble precision not null0
cost_per_videodouble precision not null0; charged once per created job (0027)
context_windowbigint not null8192; at least 0
admission_weightbigint not null100; multiplier on tenant weight in this pool, at least 1. See Effective weight
max_in_flightbigintPool size, at least 1; null = OBLETH_DEFAULT_MODEL_MAX_IN_FLIGHT. The fallback for discovered
capacity_modetext not null'static'; or tuned, discovered (0029)
capacity_tuned_attimestamptzLast auto-tune apply
capacity_sourcetext not null'endpoints' or kubernetes (0029). See Pool sizes
capacity_namespacetextkubernetes: the Service's namespace; null searches OBLETH_CAPACITY_DISCOVERY_NAMESPACES (0029)
capacity_servicetextkubernetes: the Service; null uses OBLETH_CAPACITY_DEFAULT_SERVICE (0029)
per_replica_max_in_flightbigintRequests one backend replica serves; at least 1 (0029)
capacity_headroomdouble precision not null1; multiplier on the discovered size, above 0 and at most 10 (0029)
supports_function_callingboolean not nullfalse
supports_system_messagesboolean not nulltrue
supports_response_schemaboolean not nullfalse
supports_tool_choiceboolean not nullfalse
supports_visionboolean not nullfalse
tagsjsonb not null'[]'; routing tags for auto
boonsjsonb not null'[]'; vision, structured_output, compression, knowledge, image_generation, speculation, web_search
tool_serversjsonb not null'[]'; MCP servers for the gateway tool loop
enabledboolean not nulltrue
lifecyclejsonb not null'{}' (active); {status, replacement, retire_at, changed_at, note, redirect} (0034). See Model lifecycle
reasoningjsonb not null'{}' (pass-through); {mode, default_level, levels}, how the model takes reasoning effort (0035). See Reasoning effort
listedboolean not nulltrue; false keeps the model out of /v1/models and /model/info while it is still served by name (0035)
rejected_request_fieldsjsonb not null'[]'; request body fields refused with 400 before dispatch (0036)
session_affinityjsonb not null'{}' (follows the gateway); {enabled, hold_ms, max_held_percent, max_bypass_secs}, each optional (0038). See Session affinity
cache_enabledboolean not nullfalse
cache_ttl_secsbigint not null300; 0 disables caching
request_timeout_secsbigintAt least 1; null = OBLETH_UPSTREAM_TIMEOUT_SECS
max_retriesbigint not null0
retry_backoff_msbigint not null200
endpoint_selection_modetext not null'failover'; or load_balance, session_hash
debug_diagnosticsboolean not nullfalse; DNS and TCP probe span on a final 502 or 504 (0012)
energy_slots_per_nodebigint not null0 = off; see Energy accounting (0014)
route_biasdouble precision not null1.0; multiplier on the auto score (0017)
auto_eligibleboolean not nulltrue; false keeps the model out of auto (0019)
verifier_fortext not null''; not read (0022)
draft_modeltext not null''; speculation drafter; '' = boon default (0023)
verify_api_basetext not null''; direct URL that scores drafts; '' = no speculation (0023)
verify_upstream_modeltext not null''; name the scorer serves; '' = same (0023)
health_checks_enabled, health_alerts_enabledboolean not nulltrue
health_check_interval_secsbigint not null900; at least 60
health_failure_thresholdbigint not null2; failures before alerting
health_maintenance_until, health_maintenance_notetimestamptz, textAlert suppression window
health_statustext not null'unknown'
health_consecutive_failuresbigint not null0
health_alert_statetext not null'ok'
health_next_check_attimestamptz not nullnow()
health_last_checked_attimestamptz
health_last_latency_ms, health_last_http_statusbigint
health_last_messagetext
created_at, updated_attimestamptz not nullnow()

model_families

One name for several chat models; the reasoning effort level picks which one answers (0035). Members are named, not linked: the API checks renames and deletes, and a family whose member is gone answers 404 for that level. See Reasoning effort.

ColumnTypeNotes
iduuidPrimary key
nametext not nullUnique; shares one namespace with model names, aliases and variants
descriptiontext not null''
default_leveltext not null'none'; the level a request that names none gets
membersjsonb not null'{}'; {none, low, medium, high}, each a models.model_name
created_at, updated_attimestamptz not nullnow()

model_endpoints

One row per upstream a model routes to; a model with none uses its own api_base and api_key. Unique (model_id, name). See Reliability.

ColumnTypeDefault and notes
iduuid primary key
model_iduuid not nullReferences models.id, on delete cascade
nametext not nullUnique per model
api_basetext not null
api_keytextEncrypted; null inherits the model's key
prioritybigint not null100; lower first in failover
weightbigint not null100; share in load_balance, at least 1
enabledboolean not nulltrue
max_in_flightbigintPer-endpoint concurrency for the endpoints source; null = model's per_replica_max_in_flight (0029)
health_statustext not null'unknown'
consecutive_failuresbigint not null0
alert_statetext not null'ok'
last_checked_at, last_latency_ms, last_http_status, last_messageLatest probe
created_at, updated_attimestamptz not nullnow()

model_health_checks

Probe history, pruned after OBLETH_MODEL_HEALTH_RETENTION_DAYS (30). Indexed by (model_id, checked_at desc) and checked_at desc.

ColumnTypeDefault and notes
idbigserial primary key
model_iduuid not nullReferences models.id, on delete cascade
checked_attimestamptz not nullnow()
triggertext not nullscheduled, manual, or bulk
statustext not nullhealthy, degraded, unhealthy, or skipped
latency_ms, http_statusbigint
message, response_excerpttextSanitized

video_jobs

One row per job created through the gateway, video or 3D mesh (the table keeps its first name), so follow-ups reach the endpoint that holds the job and only its owner sees it (OBLETH_VIDEO_JOB_SCOPE, key or tenant). Pruned after OBLETH_VIDEO_JOB_RETENTION_HOURS. Indexed by (tenant_id, created_at desc), (tenant_id, key_id, created_at desc), and created_at. See Who can see a job.

ColumnTypeDefault and notes
job_idtext primary keyThe upstream's job id
kindtext not nullvideo (default) or mesh; every lookup matches it
model_nametext not nullModel the job was created through
tenant_iduuid not nullOwner; every lookup is scoped by it
key_iduuid not nullCreating key; also scopes lookups under key
upstream_basetext not nullEndpoint that accepted the create
created_attimestamptz not nullnow()

Slurm deployments

managed_models

The Slurm launch spec for a model the provisioner keeps running. See Slurm provisioning.

ColumnTypeDefault and notes
model_iduuid primary keyReferences models.id, on delete cascade
enabledboolean not nulltrue; false drains to zero
partitiontext not null
grestext not null''
nodesbigint not null1; at least 1
constraints, exclude, account, qos, time_limittextSlurm options
cpus_per_taskbigintNull = cluster default
memtextNull = cluster default
imagetext not nullApptainer image
preambletext not null''
log_output_dirtext not null'' = Slurm default
launch_commandtext not null
script_bodytext not null''; when set, submitted as is instead of image, preamble, and command
serving_portbigint not nullBase of the port window, 1–65535
health_pathtext not null'/health'
target_replicasbigint not null2; at least 1
min_replicasbigint not null1; healthy floor (0007)
max_job_failuresbigint not null0 = unlimited
launcher_specjsonbRecipe provenance; the provisioner ignores it (0005)
last_provision_error, last_provision_error_attext, timestamptzLast submit failure (0008)
created_at, updated_attimestamptz not nullnow()

model_replicas

One row per Slurm-backed replica, owned by the provisioner. Unique (model_id, slurm_job_id); indexed by model_id and state.

ColumnTypeDefault and notes
iduuid primary key
model_iduuid not nullNo foreign key, so jobs can be cancelled after the model is deleted (0010)
slurm_job_idtext not null
nodestextAllocated hostnames
endpoint_iduuidReferences model_endpoints.id, on delete set null
statetext not null'pending'; or starting, healthy, draining, lost
port_basebigintBase of this replica's port window (0007)
cancel_requestedboolean not nullfalse; operator restart (0009)
last_messagetext
created_at, updated_attimestamptz not nullnow()

recipes

Launch templates edited from the dashboard. Indexed by updated_at desc.

ColumnTypeDefault and notes
iduuid primary key
nametext not null
bodytext not null''; YAML header plus sbatch script
authortext not null''
created_at, updated_attimestamptz not nullnow()

deployment_launches

One row per Slurm replica launch, written as the replica moves through its states and kept after the replica row is gone (0031). It has no foreign keys, so it outlives the replica, the launch spec and the model. Indexed by (model_id, submitted_at desc) and (recipe_id, submitted_at desc). Read it with GET /api/v1/deployments/launches.

ColumnTypeDefault and notes
iduuid primary keyThe replica's id
model_id, model_nameuuid, text not null
recipe_idtextThe recipe it was launched from
slurm_job_idtext not null
partition, account, qos, time_limit, gres, mem, exclude, constraintstextSnapshot of the spec at submit time
nodes_requested, cpus_per_taskbigintSnapshot of the spec at submit time
launcher_specjsonbSnapshot of the spec's launcher metadata
nodestextNodes Slurm allocated
submitted_attimestamptz not nullnow()
started_at, healthy_at, ended_attimestamptz
end_statetextSlurm's end state (TIMEOUT, OUT_OF_MEMORY, NODE_FAIL, …), gone when the job vanished, or cancelled:<why> when obleth cancelled it
end_reasontext
updated_attimestamptz not nullnow()

MCP and knowledge

mcp_servers

ColumnTypeDefault and notes
iduuid primary key
nametext not null uniqueReached at /mcp/<name>
upstream_urltext not null
auth_headertextEncrypted
enabledboolean not nulltrue; partial index
created_at, updated_attimestamptz not nullnow()

knowledge_collections

ColumnTypeDefault and notes
iduuid primary key
nametext not null unique
descriptiontext not null''
embedding_modeltext not nullEmbedder the operator chose
indexed_embedding_modeltext not null''; embedder the index was built with
embedding_diminteger not null0
chunk_tokensinteger not null400; above 0
chunk_overlap_tokensinteger not null50; at least 0, below chunk_tokens
versionbigint not null0; bumped on any content change
created_at, updated_attimestamptz not nullnow()

knowledge_documents

Unique (collection_id, content_hash); indexed by status.

ColumnTypeDefault and notes
iduuid primary key
collection_iduuid not nullReferences knowledge_collections.id, on delete cascade
titletext not null
filenametext not null''
content_typetext not null'text/plain'
contenttext not nullDocument text
content_hashtext not nullIdentical re-uploads reuse the row
byte_sizebigint not null0
statustext not null'pending'; or indexing, ready, failed
errortextFailure reason
chunk_countinteger not null0
active_generationinteger not null0; chunk generation served
indexed_embedding_modeltext not null''
indexing_started_attimestamptzReclaimed after index_stale_after_secs
reindex_requestedboolean not nullfalse; reindex asked for mid-indexing (0021)
created_attimestamptz not nullnow()
indexed_attimestamptz

knowledge_chunks

Unique (document_id, generation, ordinal); indexed by (collection_id, generation).

ColumnTypeDefault and notes
iduuid primary key
document_iduuid not nullReferences knowledge_documents.id, on delete cascade
collection_iduuid not nullReferences knowledge_collections.id, on delete cascade
generationinteger not null0
ordinalinteger not nullPosition in the document
texttext not null
token_countinteger not null0
embeddingbytea not nullLittle-endian f32, embedding_dim × 4 bytes, stored uncompressed
embedding_diminteger not null

model_knowledge_collections

Which collections each model retrieves from. Primary key (model_id, collection_id); both columns cascade on delete. See Knowledge base.

ColumnTypeDefault and notes
model_iduuid not nullReferences models.id
collection_iduuid not nullReferences knowledge_collections.id

Audit and settings

audit_log

One row per Management API change. Indexed by ts desc, (actor, ts desc), and (entity_type, entity_id, ts desc). See Audit log.

ColumnTypeDefault and notes
idbigserial primary key
tstimestamptz not nullnow()
actortext not nullX-Obleth-Audit-Actor, else admin
actiontext not nullFor example create_tenant
entity_typetext not nullFor example api_key
entity_idtext not null
detailjsonb not null'{}'

app_settings

One JSON value per key: key text primary key, value jsonb not null, updated_at timestamptz not null default now().

KeyHoldsManagement API
alertsSlack and email alert channels/api/v1/settings/alerts
auto_routerauto routing and classifier/api/v1/settings/auto-router
boonsBoon settings, including knowledge retrieval/api/v1/settings/boons, /api/v1/settings/knowledge
energyEnergy accounting/api/v1/settings/energy
slurmSlurm connection, JWT encrypted/api/v1/settings/slurm
usage_retentionRequest history window/api/v1/settings/usage-retention
charo_settingsPlayground assistant model, tools, benchmark limits/api/v1/settings/charo
charo_enabledThe assistant's on switch/api/v1/settings/charo
control_plane_keyThe control plane's reserved key, encrypted/api/v1/system/control-plane-key

Dashboard auth tables

The control plane creates these from control-plane/db/auth-schema.sql when it starts and reads them through DATABASE_URL. user is a reserved word, so quote it: select email, role, status from "user";.

TableHolds
"user"Accounts: id, name, email (unique), emailVerified, image, role (admin or user, default user), status (pending or active, default pending), tenantId (references tenants.id, on delete set null), banned, banReason, banExpires, createdAt, updatedAt
"session"Sessions: token (unique), expiresAt, ipAddress, userAgent, userId (cascade), impersonatedBy
"account"Credentials per provider: providerId, accountId, userId (cascade), OIDC tokens and expiries, scope, and the hashed password of the break-glass admin
"verification"Short-lived values: identifier, value, expiresAt

Inspecting the schema

\dt          -- list tables
\d+ models   -- describe one