Native HTTP/2 + Protobuf on port 9090 — no external gRPC library. Three services cover unary SQL execution, streaming large result sets, and bidirectional live query subscriptions.
Absolute DB ships a fully native gRPC server with zero external dependencies — no grpc-c, no libgrpc. The HTTP/2 framing, HPACK header compression, stream multiplexing, flow control, and Protobuf varint encoding are all implemented in pure C11.
| Property | Value |
|---|---|
| Default port | 9090 |
| Protocol | HTTP/2 + Protobuf binary |
| TLS | Native TLS 1.3 (no OpenSSL) |
| PQC hybrid | X25519 + ML-KEM-768 key exchange |
| Services | Execute (unary), Query (server-streaming), Subscribe (bidirectional) |
| Prometheus | Remote write/read at 9090/prom |
| Compatible clients | grpc-go, grpc-python, grpcurl, any standard gRPC stub |
HTTP/2 stream multiplexing means hundreds of concurrent gRPC calls share a single TCP connection with no head-of-line blocking. HPACK dynamic table compression keeps header overhead minimal even for high-frequency short queries.
Start the server with gRPC enabled (it is on by default). The server listens on port 9090 for plaintext or TLS, depending on whether a certificate is configured.
# Plaintext gRPC (development only)
./bin/absdb-server --port 5433 --grpc-port 9090
# TLS gRPC (recommended for production)
./bin/absdb-server \
--grpc-port 9090 \
--tls-cert /etc/absdb/server.crt \
--tls-key /etc/absdb/server.key
# Verify gRPC is reachable (plaintext)
grpcurl -plaintext localhost:9090 list
The gRPC server advertises ALPN h2 so that TLS-aware load balancers and proxies (Envoy, nginx) can route correctly. When using mutual TLS, provide --tls-ca to require client certificates.
| Service | RPC Pattern | Use Case |
|---|---|---|
AbsoluteDB.Execute | Unary | Single SQL statement — INSERT, UPDATE, DELETE, DDL, or small SELECT |
AbsoluteDB.Query | Server-streaming | Large SELECT result sets — rows streamed in batches as they are produced |
AbsoluteDB.Subscribe | Bidirectional streaming | Live change stream — receive row-level change events in real time |
Execute sends a single SQL statement and receives a single response. It is the fastest path for writes and small reads. The response includes the row count affected, column metadata, and up to the first result page of rows.
message ExecuteRequest {
string sql = 1; // SQL statement to execute
string database = 2; // Target database (optional, uses default)
repeated Value params = 3; // Positional bind parameters ($1, $2, ...)
bool read_only = 4; // Hint: refuse writes on read-only replica
}
message ExecuteResponse {
int64 rows_affected = 1;
repeated ColumnMeta columns = 2;
repeated Row rows = 3;
string error = 4; // Empty on success
}
Bind parameters use positional placeholders ($1, $2, ...) matching the PostgreSQL convention. This prevents SQL injection and allows the server to cache the query plan.
Query is designed for large result sets. The server streams QueryResponse messages in batches as rows are produced by the executor — the client does not wait for the full result before processing begins. Each batch contains up to 1,000 rows by default (configurable via batch_size).
message QueryRequest {
string sql = 1;
string database = 2;
repeated Value params = 3;
int32 batch_size = 4; // Rows per response message (default 1000)
int64 timeout_ms = 5; // Query timeout in milliseconds
}
// Streamed — server sends N of these per query
message QueryResponse {
bool is_first = 1; // First message contains column metadata
repeated ColumnMeta columns = 2;
repeated Row rows = 3;
bool is_last = 4; // Final message (may have 0 rows)
int64 total_rows = 5; // Set only in last message
}
HTTP/2 flow control prevents the server from overwhelming a slow consumer. The server respects stream window sizes and pauses sending when the client's window is exhausted.
Subscribe opens a persistent bidirectional stream for live change events. The client sends subscription commands; the server pushes row-level change events (INSERT, UPDATE, DELETE) as they occur. This is built on the same WAL-tap CDC engine that feeds Debezium-compatible output.
// Client sends to open/close subscriptions
message SubscribeRequest {
enum Action { SUBSCRIBE = 0; UNSUBSCRIBE = 1; ACK = 2; }
Action action = 1;
string table = 2; // Table to subscribe to
string where = 3; // Optional server-side filter predicate
int64 start_lsn = 4; // Resume from LSN (0 = current)
int64 ack_lsn = 5; // Acknowledge processed up to this LSN
}
// Server sends for each row change event
message ChangeEvent {
enum Op { INSERT = 0; UPDATE = 1; DELETE = 2; }
Op op = 1;
string table = 2;
int64 lsn = 3;
int64 ts_us = 4; // Microseconds since epoch
Row before = 5; // Old row values (UPDATE/DELETE)
Row after = 6; // New row values (INSERT/UPDATE)
}
Clients send ACK messages to advance the server-side cursor. If the client disconnects, it can resume from its last acknowledged LSN using start_lsn. The server maintains a 100 MB ring buffer to absorb consumer lag.
The complete .proto file is shipped with the Absolute DB distribution at clients/proto/absolutedb.proto. You can generate stubs for any language supported by the standard protoc compiler.
# Python stubs
python -m grpc_tools.protoc \
-I clients/proto \
--python_out=. \
--grpc_python_out=. \
clients/proto/absolutedb.proto
# Go stubs
protoc \
-I clients/proto \
--go_out=. \
--go-grpc_out=. \
clients/proto/absolutedb.proto
# List services via reflection (no proto file needed)
grpcurl -plaintext localhost:9090 list AbsoluteDB
Absolute DB also supports gRPC server reflection, so tools like grpcurl and Postman can discover services and methods without the .proto file.
TLS 1.3 on the gRPC port is handled by Absolute DB's native TLS implementation — no OpenSSL or external library is involved. The server advertises ALPN h2 so that HTTP/2 is negotiated during the TLS handshake.
For authentication, pass credentials as gRPC metadata headers:
| Method | Metadata Key | Value |
|---|---|---|
| Password | authorization | Basic base64(user:password) |
| JWT / OIDC | authorization | Bearer <jwt_token> |
| mTLS | — | Client certificate in TLS handshake |
| SPIFFE/SVID | — | X.509 SVID as client cert |
# Using server certificate (TLS, no client cert)
grpcurl \
-cacert /etc/absdb/ca.crt \
-H 'authorization: Basic YWRtaW46c2VjcmV0' \
localhost:9090 \
AbsoluteDB/Execute
# Mutual TLS
grpcurl \
-cacert /etc/absdb/ca.crt \
-cert /etc/absdb/client.crt \
-key /etc/absdb/client.key \
localhost:9090 \
AbsoluteDB/Execute
import grpc
import absolutedb_pb2 as pb
import absolutedb_pb2_grpc as pbg
# Plaintext (development)
channel = grpc.insecure_channel('localhost:9090')
# TLS (production)
# creds = grpc.ssl_channel_credentials(open('ca.crt','rb').read())
# channel = grpc.secure_channel('db.example.com:9090', creds)
stub = pbg.AbsoluteDBStub(channel)
# Unary execute
resp = stub.Execute(pb.ExecuteRequest(
sql = 'SELECT id, name FROM users WHERE id = $1',
params = [pb.Value(int_val=42)],
database = 'myapp',
))
for row in resp.rows:
print(row.values)
# Server-streaming query
req = pb.QueryRequest(
sql = 'SELECT * FROM events ORDER BY ts DESC',
batch_size = 500,
)
for batch in stub.Query(req):
for row in batch.rows:
print(row.values)
package main
import (
"context"
"log"
pb "github.com/supportcall/absdb-go/proto"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
func main() {
conn, err := grpc.Dial(
"localhost:9090",
grpc.WithTransportCredentials(insecure.NewCredentials()),
)
if err != nil {
log.Fatal(err)
}
defer conn.Close()
client := pb.NewAbsoluteDBClient(conn)
// Unary execute
resp, err := client.Execute(context.Background(), &pb.ExecuteRequest{
Sql: "INSERT INTO orders (user_id, total) VALUES ($1, $2)",
Params: []*pb.Value{{IntVal: 7}, {FloatVal: 99.95}},
Database: "myapp",
})
if err != nil {
log.Fatal(err)
}
log.Printf("rows affected: %d", resp.RowsAffected)
}
# List all services
grpcurl -plaintext localhost:9090 list
# Describe Execute method
grpcurl -plaintext localhost:9090 describe AbsoluteDB.Execute
# Run a query (JSON → Protobuf conversion)
grpcurl -plaintext \
-d '{"sql": "SELECT version()", "database": "myapp"}' \
localhost:9090 AbsoluteDB/Execute
# Subscribe to a table (streaming — Ctrl+C to stop)
grpcurl -plaintext \
-d '{"action": 0, "table": "orders"}' \
localhost:9090 AbsoluteDB/Subscribe
# Resume from a known LSN
grpcurl -plaintext \
-d '{"action": 0, "table": "orders", "start_lsn": 1048576}' \
localhost:9090 AbsoluteDB/Subscribe
The gRPC port doubles as a Prometheus remote write and remote read endpoint at /prom. This makes Absolute DB a drop-in replacement for Thanos, Cortex, or VictoriaMetrics as a Prometheus long-term storage backend.
remote_write:
- url: http://localhost:9090/prom/api/v1/write
# For TLS:
# url: https://db.example.com:9090/prom/api/v1/write
# tls_config:
# ca_file: /etc/absdb/ca.crt
remote_read:
- url: http://localhost:9090/prom/api/v1/read
read_recent: true
Metrics are stored in the Absolute DB time-series engine (hypertables) and are queryable via standard PromQL as well as SQL. This allows joining application metrics with business data in a single query.
-- All metrics for a specific job in the last hour
SELECT time_bucket('1 minute', ts) AS minute,
avg(value) AS avg_value
FROM prom_metrics
WHERE name = 'http_requests_total'
AND labels->>'job' = 'absdb-server'
AND ts > now() - INTERVAL '1 hour'
GROUP BY minute
ORDER BY minute;
Absolute DB maps internal errors to standard gRPC status codes:
| gRPC Status | Code | Cause |
|---|---|---|
| OK | 0 | Success |
| INVALID_ARGUMENT | 3 | SQL parse error, invalid parameter type |
| NOT_FOUND | 5 | Table or database does not exist |
| ALREADY_EXISTS | 6 | Unique constraint violation |
| PERMISSION_DENIED | 7 | Insufficient role privileges |
| RESOURCE_EXHAUSTED | 8 | Connection limit, buffer pool full |
| FAILED_PRECONDITION | 9 | Transaction conflict, serialisation failure |
| ABORTED | 10 | Transaction rolled back |
| DEADLINE_EXCEEDED | 4 | Query timeout exceeded |
| INTERNAL | 13 | Unexpected server error — check server log |
| UNAVAILABLE | 14 | Server shutting down or Raft leader not elected |
Detailed error messages are included in the Status.message field and also written to the server log at ERROR level. Enable --log-level debug for full query traces.
~154 KB binary · zero external dependencies · 2,737 tests passing · SQL:2023 100%