Session Instrumentation¶
GizmoSQL provides built-in session instrumentation for tracking server instances, client sessions, and SQL statement execution. This feature enables SOC2/audit compliance, performance analysis, and session management capabilities.
Overview¶
Session instrumentation automatically tracks: - Server instances: When the server starts and stops - Client sessions: When clients connect and disconnect, including authentication details - SQL statements: Prepared statements created during a session - SQL executions: Every execution of a statement, including bind parameters, execution time, and row counts
All instrumentation data is stored in a separate DuckDB database and exposed as read-only views accessible via SQL queries.
Configuration¶
Database Location (File-Based)¶
By default, the instrumentation database is created in the same directory as the main database file:
- If --database-filename is /path/to/mydb.db, instrumentation is stored at /path/to/gizmosql_instrumentation.db
- If using in-memory mode (:memory:), instrumentation is stored in the current working directory
You can override the location using the environment variable:
Choosing a backend¶
GizmoSQL stores instrumentation in one of three backends, auto-detected from the catalog --instrumentation-catalog points at (it reads duckdb_databases().type at startup):
| Backend | Use when | Constraints / indexes |
|---|---|---|
| File-based DuckDB (default) | Single instance | Primary keys, CHECK constraints, indexes |
| PostgreSQL (recommended for multiple instances) | Several instances share one catalog | Primary keys, foreign keys (ON DELETE CASCADE), CHECK constraints, indexes |
| DuckLake (⚠️ deprecated) | — | None — see the warning below |
Using PostgreSQL as Instrumentation Backend (recommended for multiple instances)¶
For multi-instance deployments, store instrumentation in a plain PostgreSQL database. Each instance writes its own (disjoint) rows — its own sessions, statements, and executions — and PostgreSQL's row-level MVCC lets those concurrent writes proceed without conflict. So, unlike DuckLake (whose table-level optimistic concurrency can drop a finalize/stop update on a commit conflict and strand a record), a finalize update is not lost to cross-instance contention. Attach the database in --init-sql-commands and point --instrumentation-catalog at it; GizmoSQL detects that it is PostgreSQL and creates the full relational schema (primary keys, foreign keys with ON DELETE CASCADE, CHECK constraints on status columns, and indexes on every foreign-key and timestamp column).
GIZMOSQL_PASSWORD="password" gizmosql_server \
--database-filename mydb.db \
--enable-instrumentation=true \
--instrumentation-catalog=instr_pg \
--instrumentation-schema=gizmosql_instr \
--init-sql-commands="
INSTALL postgres; LOAD postgres;
ATTACH 'host=localhost port=5432 dbname=instrumentation user=postgres password=password' AS instr_pg (TYPE postgres);
"
Notes:
- Choose a schema name that is not prefixed with pg_ (reserved by PostgreSQL). The default is main.
- Retention pruning is a single cascading delete on the parent — DELETE FROM instr_pg.gizmosql_instr.instances WHERE stop_time < now() - INTERVAL '30 days' removes that instance's sessions, statements, and executions via ON DELETE CASCADE. The timestamp columns are indexed so the delete is fast.
- JSON-valued columns (*_tag, query_profile) are stored as VARCHAR on PostgreSQL — they hold JSON strings; query them with a ::json cast (e.g. query_profile::json).
- Use a simple identifier for --instrumentation-schema (the default is main); exotic names with hyphens/spaces are not quoted in the generated DDL.
- Instrumentation writes are best-effort: under heavy concurrency a write can rarely hit a transient PostgreSQL serialization error (the postgres extension uses REPEATABLE READ); it is logged at WARNING and not retried, so a finalize update could in principle be dropped. To find any execution left mid-flight, query for executing rows belonging to an instance that has since stopped:
SELECT e.* FROM instr_pg.gizmosql_instr.sql_executions e
JOIN instr_pg.gizmosql_instr.sql_statements st ON e.statement_id = st.statement_id
JOIN instr_pg.gizmosql_instr.sessions s ON st.session_id = s.session_id
JOIN instr_pg.gizmosql_instr.instances i ON s.instance_id = i.instance_id
WHERE e.status = 'executing' AND i.status = 'stopped';
Using DuckLake as Instrumentation Backend (⚠️ Deprecated)¶
Deprecated. DuckLake-backed instrumentation is deprecated and not recommended. DuckLake uses table-level optimistic concurrency, so concurrent UPDATEs from multiple instances sharing one catalog can be lost — a finalize/stop update that loses a commit conflict is dropped, leaving records permanently stuck (e.g. an execution stuck at
status='executing', never reaped in shared-catalog mode). The server logs a startup WARNING when instrumentation resolves to a DuckLake catalog. Use file-based (single instance) or PostgreSQL (multiple instances) instead. DuckLake support remains until the upstream concurrent-UPDATE issue is resolved.
You can store instrumentation data in a DuckLake catalog. This enables: - Cloud-based storage (S3, Azure Blob, etc.) for instrumentation data - Transactional consistency with ACID guarantees from DuckLake
Configuration Parameters¶
| Parameter | CLI Flag | Env Var | Default | Description |
|---|---|---|---|---|
instrumentation_catalog |
--instrumentation-catalog |
GIZMOSQL_INSTRUMENTATION_CATALOG |
(empty) | Catalog name for instrumentation. If set, uses pre-attached catalog instead of file. |
instrumentation_schema |
--instrumentation-schema |
GIZMOSQL_INSTRUMENTATION_SCHEMA |
main |
Schema within the catalog |
instance_tag |
--instance-tag |
GIZMOSQL_INSTANCE_TAG |
(empty) | JSON-formatted tag attached to the instance record |
cluster_id |
--cluster-id |
GIZMOSQL_CLUSTER_ID |
(empty) | Cluster grouping UUID. Recorded on the instances row, injected into every log entry, and exposed via GIZMOSQL_CURRENT_CLUSTER(). Must be a UUID. |
When instrumentation_catalog is set:
- The catalog must be pre-attached via --init-sql-commands
- instrumentation_db_path is ignored
- GizmoSQL will create the instrumentation tables in the specified catalog/schema
Important: Dedicated Catalog Required
The instrumentation catalog is protected as read-only for clients (only administrators can read the data, no one can modify it). This protection applies to the entire catalog, not just the instrumentation schema.
Do NOT use a shared catalog that contains other application tables. If you do, you will not be able to modify any tables in that catalog. Always use a dedicated catalog for instrumentation.
✓ Good: instr_ducklake (dedicated catalog for instrumentation)
✗ Bad: my_app_ducklake with instrumentation in 'main' schema (entire catalog becomes read-only)
Example: Using Persistent Secrets (Recommended)¶
With persistent secrets, you only need to create the secrets once. DuckDB stores them in ~/.duckdb/stored_secrets and automatically loads them on subsequent startups.
Initial setup (first time only):
GIZMOSQL_PASSWORD="password" gizmosql_server \
--database-filename mydb.db \
--enable-instrumentation=true \
--instrumentation-catalog=instr_ducklake \
--init-sql-commands="
INSTALL ducklake; INSTALL postgres; LOAD ducklake; LOAD postgres;
CREATE PERSISTENT SECRET pg_secret (TYPE postgres, HOST 'localhost', PORT 5432, DATABASE 'ducklake_catalog', USER 'postgres', PASSWORD 'password');
CREATE PERSISTENT SECRET ducklake_secret (TYPE DUCKLAKE, METADATA_PATH '', DATA_PATH 's3://mybucket/instrumentation/', METADATA_PARAMETERS MAP {'TYPE': 'postgres', 'SECRET': 'pg_secret'});
ATTACH 'ducklake:ducklake_secret' AS instr_ducklake;
"
Subsequent startups (secrets already persisted):
GIZMOSQL_PASSWORD="password" gizmosql_server \
--database-filename mydb.db \
--enable-instrumentation=true \
--instrumentation-catalog=instr_ducklake \
--init-sql-commands="
LOAD ducklake; LOAD postgres;
ATTACH 'ducklake:ducklake_secret' AS instr_ducklake;
"
Example: Using Session Secrets¶
If you prefer not to persist secrets, create them each startup:
GIZMOSQL_PASSWORD="password" gizmosql_server \
--database-filename mydb.db \
--enable-instrumentation=true \
--instrumentation-catalog=instr_ducklake \
--init-sql-commands="
INSTALL ducklake; INSTALL postgres; LOAD ducklake; LOAD postgres;
CREATE OR REPLACE SECRET pg_secret (TYPE postgres, HOST 'localhost', PORT 5432, DATABASE 'ducklake_catalog', USER 'postgres', PASSWORD 'password');
CREATE OR REPLACE SECRET ducklake_secret (TYPE DUCKLAKE, METADATA_PATH '', DATA_PATH 's3://mybucket/instrumentation/', METADATA_PARAMETERS MAP {'TYPE': 'postgres', 'SECRET': 'pg_secret'});
ATTACH 'ducklake:ducklake_secret' AS instr_ducklake;
"
Read-Only Mode (--readonly) with an External Instrumentation Catalog¶
When you start the server with --readonly (or GIZMOSQL_READONLY=1), DuckDB
opens the main database in read-only mode and propagates that access mode
to every database that gets attached afterwards. For the built-in system
catalog and the file-based instrumentation database, GizmoSQL forces
(READ_WRITE) on the attach automatically. For an external instrumentation
catalog, GizmoSQL cannot do that for you — the ATTACH statement lives in
your --init-sql-commands, so you must add (READ_WRITE) yourself.
Without it, instrumentation manager startup will fail trying to CREATE TABLE
/ INSERT against a read-only attached catalog.
# WRONG under --readonly: inherits READ_ONLY from the parent DB
ATTACH 'ducklake:ducklake_secret' AS instr_ducklake;
# CORRECT under --readonly: explicit READ_WRITE override
ATTACH 'ducklake:ducklake_secret' AS instr_ducklake (READ_WRITE);
The same rule applies to any pre-attached catalog you want to write to (e.g. a Postgres scanner attach) when the server is in read-only mode.
Multiple GizmoSQL Instances¶
When running multiple GizmoSQL instances that share one instrumentation catalog:
- Use a PostgreSQL catalog (above), not DuckLake. PostgreSQL's row-level MVCC lets the instances write concurrently without losing updates; DuckLake can drop concurrent UPDATEs and is deprecated for this reason.
- All instances share the catalog for centralized monitoring; each gets a unique instance_id for correlation.
- Use the instances table to track all connected servers, and DELETE FROM …instances WHERE stop_time < … (cascading) to prune old data.
Note: Persistent secrets are stored in unencrypted binary format in ~/.duckdb/stored_secrets. You can customize this location with SET secret_directory = '/path/to/secrets'; if needed.
Enabling/Disabling Instrumentation¶
Session instrumentation is disabled by default. To enable it, use the --enable-instrumentation flag:
# Instrumentation disabled (default)
gizmosql_server --database-filename /path/to/mydb.db --password mypassword
# Explicitly enabled
gizmosql_server --database-filename /path/to/mydb.db --password mypassword --enable-instrumentation=true
# Explicitly disabled
gizmosql_server --database-filename /path/to/mydb.db --password mypassword --enable-instrumentation=false
When instrumentation is disabled:
- No instrumentation database is created
- The _gizmosql_instr schema is not attached to user sessions
- Session, statement, and execution tracking is disabled
- Queries to _gizmosql_instr.* tables/views will fail (schema doesn't exist)
Note that GIZMOSQL_CURRENT_SESSION(), GIZMOSQL_CURRENT_INSTANCE(), GIZMOSQL_CURRENT_CLUSTER(), GIZMOSQL_VERSION(), GIZMOSQL_USER(), GIZMOSQL_ROLE(), and KILL SESSION still work when instrumentation is disabled - they just won't be recorded in the instrumentation tables. GIZMOSQL_CURRENT_CLUSTER() returns the --cluster-id UUID, or NULL when no cluster id is configured.
Disabling instrumentation may be useful for: - Development/testing environments where audit trails are not needed - Performance-sensitive deployments where the overhead of instrumentation is undesirable - Environments where the additional database file is not desired
Schema¶
Table Location¶
The location of instrumentation tables depends on your configuration:
| Mode | Table Location | Example Query |
|---|---|---|
| File-based (default) | _gizmosql_instr.main.* |
SELECT * FROM _gizmosql_instr.sessions |
| PostgreSQL | {catalog}.{schema}.* |
SELECT * FROM instr_pg.gizmosql_instr.sessions |
| DuckLake/External catalog | {catalog}.{schema}.* |
SELECT * FROM instr_ducklake.main.sessions |
The four tables below have the same columns on every backend. The constraints and indexes differ by backend: file-based DuckDB and PostgreSQL add primary keys, CHECK constraints on status columns, and indexes on foreign-key and timestamp columns; PostgreSQL additionally adds foreign keys with ON DELETE CASCADE (DuckDB cannot, as it implements UPDATE as delete+insert and the lifecycle updates parent rows). DuckLake has none of these. On PostgreSQL, the JSON-valued columns (*_tag, query_profile) are VARCHAR (JSON strings — cast with ::json).
When using file-based instrumentation, GizmoSQL automatically attaches the instrumentation database as _gizmosql_instr and all examples in this documentation use that name.
When using DuckLake or another external catalog (via --instrumentation-catalog), set the schema context with USE before running queries. For example, if you configured --instrumentation-catalog=instr_ducklake --instrumentation-schema=main:
-- Set the schema context once
USE instr_ducklake.main;
-- Then query tables/views without prefixes
SELECT * FROM active_sessions;
SELECT * FROM sessions WHERE status = 'active';
Tables¶
The instrumentation database contains four core tables:
instances¶
Tracks server instance lifecycle.
| Column | Type | Description |
|---|---|---|
instance_id |
UUID | Primary key |
cluster_id |
UUID | Cluster grouping UUID from --cluster-id (nullable; NULL when unset) |
gizmosql_version |
VARCHAR | GizmoSQL version |
gizmosql_edition |
VARCHAR | GizmoSQL edition ('Core' or 'Enterprise') |
duckdb_version |
VARCHAR | DuckDB version |
arrow_version |
VARCHAR | Apache Arrow version |
hostname |
VARCHAR | Resolved server hostname |
hostname_arg |
VARCHAR | Hostname argument passed to server (NULL if not specified) |
server_ip |
VARCHAR | Server IP address |
port |
INTEGER | Server port |
database_path |
VARCHAR | Path to main database |
tls_enabled |
BOOLEAN | Whether TLS is enabled |
tls_cert_path |
VARCHAR | Path to TLS certificate file (NULL if TLS disabled) |
tls_key_path |
VARCHAR | Path to TLS key file (NULL if TLS disabled) |
mtls_required |
BOOLEAN | Whether mutual TLS (mTLS) is required |
mtls_ca_cert_path |
VARCHAR | Path to mTLS CA certificate file (NULL if mTLS disabled) |
readonly |
BOOLEAN | Whether the instance is started in read-only mode |
os_platform |
VARCHAR | Operating system platform (e.g., 'darwin', 'linux') |
os_name |
VARCHAR | Operating system name (e.g., 'macOS', 'Ubuntu') |
os_version |
VARCHAR | Operating system version |
cpu_arch |
VARCHAR | CPU architecture (e.g., 'arm64', 'x86_64') |
cpu_model |
VARCHAR | CPU model name |
cpu_count |
INTEGER | Number of CPU cores |
memory_total_bytes |
BIGINT | Total system memory in bytes |
start_time |
TIMESTAMPTZ | When server started (tz-aware; compare directly to now()) |
stop_time |
TIMESTAMPTZ | When server stopped (NULL if running) |
status |
VARCHAR | 'running' or 'stopped' |
stop_reason |
VARCHAR | Reason for shutdown |
instance_tag |
JSON | User-defined JSON tag set via --instance-tag CLI flag (NULL if not set) |
sessions¶
Tracks client session lifecycle.
| Column | Type | Description |
|---|---|---|
session_id |
UUID | Primary key |
instance_id |
UUID | Reference to instances |
username |
VARCHAR | Authenticated username |
role |
VARCHAR | User's role (e.g., 'admin', 'user') |
auth_method |
VARCHAR | Authentication method (e.g., 'Basic', 'BootstrapToken') |
peer |
VARCHAR | Client IP:port (network address) |
peer_identity |
VARCHAR | mTLS client certificate identity (NULL if not using mTLS) |
user_agent |
VARCHAR | Client user-agent header (e.g., 'ADBC Flight SQL Driver v1.10.0', 'grpc-java-netty/1.65.0') |
connection_protocol |
VARCHAR | 'plaintext', 'tls', or 'mtls' |
start_time |
TIMESTAMPTZ | When session started (tz-aware; compare directly to now()) |
stop_time |
TIMESTAMPTZ | When session ended (NULL if active) |
status |
VARCHAR | 'active', 'closed', 'killed', 'timeout', 'error' |
stop_reason |
VARCHAR | Reason session ended |
session_tag |
JSON | User-defined JSON tag set via SET gizmosql.session_tag (NULL if not set) |
sql_statements¶
Tracks prepared statement definitions (one per prepared statement).
| Column | Type | Description |
|---|---|---|
statement_id |
UUID | Primary key (same as used in logs) |
session_id |
UUID | Reference to sessions |
sql_text |
VARCHAR | The SQL query text |
flight_method |
VARCHAR | The Flight SQL method that created the statement (e.g., 'DoGetTables', 'CreatePreparedStatement') |
is_internal |
BOOLEAN | Whether this is an internal statement (metadata queries, etc.) |
prepare_success |
BOOLEAN | Whether statement preparation succeeded |
prepare_error |
VARCHAR | Error message if statement preparation failed (NULL if successful) |
created_time |
TIMESTAMPTZ | When statement was created (tz-aware) |
query_tag |
JSON | User-defined JSON tag set via SET gizmosql.query_tag (NULL if not set) |
Internal vs Client Statements:
- is_internal = true: Statements created by GizmoSQL for metadata queries (DoGetTables, DoGetCatalogs, DoGetPrimaryKeys, etc.)
- is_internal = false: Statements explicitly created by the client (CreatePreparedStatement, GetFlightInfoStatement, etc.)
The flight_method column tracks which Flight SQL method created the statement for tracing purposes.
Failed Statements:
All SQL statements are recorded, including those that fail. The prepare_success column indicates whether preparation succeeded, and prepare_error contains the error message for failures. This captures:
- Parse/syntax errors
- Unknown table/column references
- Permission violations (readonly user attempting writes, modifying instrumentation database)
- Administrative command failures (KILL SESSION permission denied, session not found)
- Attempts to DETACH the instrumentation database
sql_executions¶
Tracks individual executions of statements (one per execution).
| Column | Type | Description |
|---|---|---|
execution_id |
UUID | Primary key (same as used in logs) |
statement_id |
UUID | Reference to sql_statements |
bind_parameters |
VARCHAR | JSON array of bind parameters (NULL if none) |
execution_start_time |
TIMESTAMPTZ | When execution started (tz-aware) |
execution_end_time |
TIMESTAMPTZ | When execution completed |
enqueue_time |
TIMESTAMPTZ | When the statement entered the admission queue (NULL if never queued; see Statement Queuing) |
rows_fetched |
BIGINT | Number of rows returned |
status |
VARCHAR | 'queued', 'executing', 'success', 'error', 'timeout', 'cancelled' |
error_message |
VARCHAR | Error message if failed |
duration_ms |
BIGINT | Execution duration in milliseconds |
query_profile |
JSON | DuckDB native query profile JSON (NULL unless profile capture is enabled) |
Views¶
Several convenience views are provided:
active_sessions¶
Shows currently active sessions.
Returns: session_id, instance_id, username, role, auth_method, peer, peer_identity, user_agent, connection_protocol, start_time, status, hostname, hostname_arg, server_ip, port, database_path, session_duration_seconds
session_activity¶
Complete view joining instances, sessions, statements, and executions.
SELECT * FROM session_activity
WHERE username = 'alice'
ORDER BY execution_start_time DESC
LIMIT 100;
session_stats¶
Aggregated statistics per session.
SELECT
username,
total_statements,
total_executions,
successful_executions,
failed_executions,
total_rows_fetched,
avg_duration_ms
FROM session_stats;
execution_details¶
Detailed view of executions with statement and session info.
SELECT
execution_id,
statement_id,
sql_text,
bind_parameters,
execution_start_time,
duration_ms,
status,
username
FROM execution_details
WHERE status = 'error'
LIMIT 20;
Query profile capture¶
(Enterprise)
GizmoSQL can persist DuckDB's per-query profile (the operator tree with
per-operator timings, cardinalities, result-set sizes, and memory usage) into the
sql_executions.query_profile column. The value is DuckDB's native profiling
JSON (the same shape produced by SET enable_profiling = 'json'), stored
verbatim — so it can be consumed directly by tooling that renders DuckDB query
plans graphically.
DuckDB's profiler is per-connection and last-write-wins (each query resets the previous profile). GizmoSQL captures safely because it runs exactly one statement at a time per connection and never shares a session across users, so the profile is harvested synchronously immediately after execution — before the next statement on that connection can clobber it.
Capture is opt-in and off by default. It is governed by a single setting with three modes:
| Mode | Meaning |
|---|---|
off (default) |
No profile captured. Zero overhead. |
standard |
Per-operator profile. Overhead is negligible on real workloads. |
detailed |
Additionally times every expression (DuckDB profiling_mode = detailed). Adds ~15–20% on expression-heavy queries; use deliberately. |
Configuring capture¶
Server default — CLI flag or environment variable (the library applies the env
fallback, so the C API RunFlightSQLServer(capture_query_profile=...) behaves
identically):
# off (default) | standard | detailed
gizmosql_server ... --capture-query-profile=standard
# or
export GIZMOSQL_CAPTURE_QUERY_PROFILE=standard
Per session or live server-wide, via SET:
-- This session only (any user)
SET gizmosql.capture_query_profile = 'detailed';
-- Server-wide default for new statements (admin only)
SET GLOBAL gizmosql.capture_query_profile = 'standard';
-- Turn it back off
SET gizmosql.capture_query_profile = 'off';
The session setting overrides the server default; SET GLOBAL changes the server
default for subsequent statements (in-memory; reverts to the configured/env value
on restart). Requires a valid Enterprise license with the instrumentation
feature (the same gate as the rest of session instrumentation).
Querying captured profiles¶
query_profile is a JSON column, so DuckDB's JSON functions work directly:
-- Most recent profiled executions and their top-level latency
SELECT
e.execution_id,
st.sql_text,
e.duration_ms,
e.query_profile ->> '$.latency' AS profile_latency
FROM sql_executions e
JOIN sql_statements st ON e.statement_id = st.statement_id
WHERE e.query_profile IS NOT NULL
ORDER BY e.execution_start_time DESC
LIMIT 20;
query_profile is also exposed in the session_activity and execution_details
views.
SQL Functions¶
GizmoSQL provides several pseudo-functions that are replaced with actual values at query execution time. These functions work in any SQL context (SELECT, WHERE, etc.) and are case-insensitive.
GIZMOSQL_VERSION()¶
Returns the version string of the GizmoSQL server.
Useful for version checking in scripts or diagnostics:
GIZMOSQL_CURRENT_SESSION()¶
Returns the UUID of the current session.
Useful for filtering instrumentation data to the current session:
SELECT * FROM sql_statements
WHERE session_id = GIZMOSQL_CURRENT_SESSION()
ORDER BY created_time DESC;
GIZMOSQL_CURRENT_INSTANCE()¶
Returns the UUID of the current server instance.
Useful for filtering instrumentation data to the current server instance:
GIZMOSQL_USER()¶
Returns the username of the current authenticated user.
Useful for logging or auditing which user is executing queries:
GIZMOSQL_ROLE()¶
Returns the role of the current authenticated user (e.g., admin, user, readonly).
Useful for conditional logic based on user permissions:
-- Check if current user has admin privileges
SELECT CASE
WHEN GIZMOSQL_ROLE() = 'admin' THEN 'Full access'
WHEN GIZMOSQL_ROLE() = 'readonly' THEN 'Read-only access'
ELSE 'Standard access'
END AS access_level;
GIZMOSQL_EDITION()¶
Returns the edition of the GizmoSQL server (Core or Enterprise).
GIZMOSQL_INSTRUMENTATION_ENABLED()¶
Returns whether session instrumentation is enabled on this server. Returns the SQL boolean true or false (not a quoted string).
GIZMOSQL_INSTRUMENTATION_CATALOG()¶
Returns the catalog name where instrumentation tables are stored (e.g., _gizmosql_instr or a custom DuckLake catalog). Returns an empty string when instrumentation is disabled.
GIZMOSQL_INSTRUMENTATION_SCHEMA()¶
Returns the schema name within the instrumentation catalog (default: main). Returns an empty string when instrumentation is disabled.
Clients can use these three functions to dynamically discover instrumentation availability and query the correct catalog/schema:
SELECT
GIZMOSQL_INSTRUMENTATION_ENABLED() AS enabled,
GIZMOSQL_INSTRUMENTATION_CATALOG() AS catalog,
GIZMOSQL_INSTRUMENTATION_SCHEMA() AS schema;
Session Management¶
KILL SESSION¶
Administrators can terminate other sessions using the KILL SESSION command:
Requirements:
- Caller must have the admin role
- Cannot kill your own session
- The target session must exist and be active
Example workflow:
-- List active sessions
SELECT session_id, username, peer, session_duration_seconds
FROM active_sessions;
-- Kill a specific session
KILL SESSION '550e8400-e29b-41d4-a716-446655440000';
When a session is killed: 1. Any executing queries are cancelled 2. The session's status is set to 'killed' 3. The client receives an error on their next operation
Tagging¶
GizmoSQL supports attaching user-defined JSON metadata tags to instances, sessions, and queries. Tags are stored in the instrumentation schema and are useful for cost attribution, multi-tenant identification, environment tracking, and correlating instrumentation data with external systems.
All tag values must be valid JSON. Invalid JSON is rejected with an error.
Instance Tags¶
Set at server startup via CLI flag or environment variable. The tag is recorded once in the instances table and cannot be changed at runtime.
gizmosql_server \
--enable-instrumentation=true \
--instance-tag='{"env":"production","region":"us-east-1","cluster":"main"}'
Or via environment variable:
Session Tags¶
Set by a connected client using the SET gizmosql.session_tag command. The tag is stored in the sessions table and can be updated during the session. Each SET overwrites the previous value.
To clear a session tag:
Query Tags¶
Set by a connected client using the SET gizmosql.query_tag command. Once set, the tag is recorded with every subsequent SQL statement in the sql_statements table until changed or cleared.
-- Tag all subsequent queries
SET gizmosql.query_tag = '{"request_id":"req-abc-123","pipeline":"daily-etl"}';
-- These queries will be tagged with the above JSON
SELECT * FROM orders WHERE date = '2026-04-06';
SELECT count(*) FROM customers;
-- Change the tag for the next batch
SET gizmosql.query_tag = '{"request_id":"req-def-456","pipeline":"hourly-sync"}';
-- Clear the tag (subsequent queries will have NULL query_tag)
SET gizmosql.query_tag = '';
Querying Tags¶
Tags are stored as JSON columns and can be queried using DuckDB's JSON functions:
-- Find all sessions for a specific team
SELECT * FROM _gizmosql_instr.sessions
WHERE session_tag->>'team' = 'data-eng';
-- Find queries by request ID
SELECT sql_text, query_tag, created_time
FROM _gizmosql_instr.sql_statements
WHERE query_tag->>'request_id' = 'req-abc-123';
-- Aggregate execution stats by instance environment
SELECT i.instance_tag->>'env' AS environment,
COUNT(*) AS total_executions,
AVG(e.duration_ms) AS avg_duration_ms
FROM _gizmosql_instr.execution_details e
JOIN _gizmosql_instr.instances i ON e.instance_id = i.instance_id
GROUP BY 1;
Note: Tagging is an Enterprise feature. Attempting to use
SET gizmosql.session_tagorSET gizmosql.query_tagwithout a valid enterprise license will return an error.
Security¶
Read-Only Access¶
The instrumentation database is protected from client modifications. Users can query instrumentation data but cannot modify it. Any attempt to INSERT, UPDATE, DELETE, or otherwise modify data in _gizmosql_instr will be rejected:
DELETE FROM _gizmosql_instr.sql_executions;
-- Error: Cannot modify the instrumentation database (_gizmosql_instr).
-- It is read-only for client sessions.
DETACH Prevention¶
Users cannot detach the instrumentation database:
Role-Based Access¶
- Only users with the
adminrole can query instrumentation tables and views in the_gizmosql_instrschema - Only users with the
adminrole can executeKILL SESSION
Example Queries¶
These examples assume you have set the schema context with USE. For file-based instrumentation: USE _gizmosql_instr.main;. For DuckLake: USE {your_catalog}.{your_schema};.
Find slow executions¶
SELECT
sql_text,
bind_parameters,
duration_ms,
rows_fetched,
execution_start_time
FROM execution_details
WHERE status = 'success'
ORDER BY duration_ms DESC
LIMIT 10;
Session activity report¶
SELECT
s.username,
s.peer,
COUNT(DISTINCT st.statement_id) as statement_count,
COUNT(e.execution_id) as execution_count,
SUM(e.rows_fetched) as total_rows,
AVG(e.duration_ms) as avg_duration_ms
FROM sessions s
LEFT JOIN sql_statements st ON s.session_id = st.session_id
LEFT JOIN sql_executions e ON st.statement_id = e.statement_id
WHERE s.start_time > now() - INTERVAL '1 day'
GROUP BY s.username, s.peer
ORDER BY execution_count DESC;
Find failed executions¶
SELECT
sql_text,
bind_parameters,
error_message,
execution_start_time,
username
FROM execution_details
WHERE status = 'error'
ORDER BY execution_start_time DESC
LIMIT 20;
Find failed statement preparations (parse errors, permission errors)¶
SELECT
st.sql_text,
st.prepare_error,
st.created_time,
s.username,
s.peer
FROM sql_statements st
JOIN sessions s ON st.session_id = s.session_id
WHERE st.prepare_success = false
ORDER BY st.created_time DESC
LIMIT 20;
Monitor currently executing queries¶
SELECT
st.sql_text,
e.bind_parameters,
s.username,
s.peer,
e.execution_start_time,
EPOCH(now()) - EPOCH(e.execution_start_time) as running_seconds
FROM sql_executions e
JOIN sql_statements st ON e.statement_id = st.statement_id
JOIN sessions s ON st.session_id = s.session_id
WHERE e.status = 'executing'
ORDER BY running_seconds DESC;
Track prepared statement reuse¶
SELECT
st.statement_id,
st.sql_text,
COUNT(e.execution_id) as execution_count,
AVG(e.duration_ms) as avg_duration_ms,
SUM(e.rows_fetched) as total_rows
FROM sql_statements st
JOIN sql_executions e ON st.statement_id = e.statement_id
GROUP BY st.statement_id, st.sql_text
HAVING COUNT(e.execution_id) > 1
ORDER BY execution_count DESC
LIMIT 20;
Filter client-issued vs internal statements¶
Exclude internal metadata queries to focus on client-issued SQL:
-- Only client-issued statements
SELECT
st.sql_text,
st.flight_method,
e.duration_ms,
e.rows_fetched,
s.username
FROM sql_statements st
JOIN sql_executions e ON st.statement_id = e.statement_id
JOIN sessions s ON st.session_id = s.session_id
WHERE st.is_internal = false
ORDER BY e.execution_start_time DESC
LIMIT 20;
-- Only internal metadata queries
SELECT
st.flight_method,
COUNT(*) as query_count,
AVG(e.duration_ms) as avg_duration_ms
FROM sql_statements st
JOIN sql_executions e ON st.statement_id = e.statement_id
WHERE st.is_internal = true
GROUP BY st.flight_method
ORDER BY query_count DESC;
Tracing¶
The statement_id and execution_id fields in the instrumentation tables match the IDs used in GizmoSQL logs. This allows you to correlate log entries with instrumentation records for debugging:
-- Find all executions for a statement_id from the logs
SELECT * FROM execution_details
WHERE statement_id = '12345678-1234-1234-1234-123456789012';
Troubleshooting¶
Instrumentation data not appearing¶
The instrumentation system uses an asynchronous write queue. Data may take a few milliseconds to appear after query execution. If running automated tests, add a small delay before querying instrumentation tables.
Cannot find my session¶
Ensure you're querying from the same connection. Each connection has a unique session ID. Use GIZMOSQL_CURRENT_SESSION() to verify your session ID.
Performance impact¶
Instrumentation writes are asynchronous and have minimal impact on query performance. The instrumentation database is separate from your main database, so instrumentation I/O does not block your queries.
Unclean shutdown recovery¶
If the GizmoSQL server terminates unexpectedly (e.g., SIGKILL, crash, power loss), some records may remain in an incomplete state:
- Instances may show status = 'running'
- Sessions may show status = 'active'
- Executions may show status = 'executing'
On the next server startup, GizmoSQL automatically cleans up these stale records:
- Stale instances are marked as stopped with stop_reason = 'unclean_shutdown'
- Stale sessions are marked as closed with stop_reason = 'unclean_shutdown'
- Stale executions are marked as error with error_message = 'Server shutdown unexpectedly'
This cleanup happens before the server accepts new connections, ensuring instrumentation data remains consistent.
Note: When using an external DuckLake catalog for instrumentation (multi-instance deployments), stale record cleanup is disabled. This is because other instances may be legitimately running and should not be marked as stopped. In this scenario, administrators should implement their own cleanup strategy for detecting truly stale instances (e.g., based on heartbeat timeouts or health checks).