Service Runtime¶
Overview¶
The service/ module is the authoritative runtime for Floecat. It hosts the Quarkus gRPC server,
implements every public API from proto/, manages multi-account security contexts,
translates requests into pointer/blob mutations, assembles execution scan bundles, and operates background
tasks such as idempotency/CAS/pointer/transaction/reconcile-job GC and repository seeding.
For durable reconcile jobs, the service now owns a native domain-split queue model rather than one generic pointer-prefix queue abstraction: - canonical job-index domain - ready-queue domain - lease-coordination domain - canonical payload-artifact references on job rows - projection/root-summary observability domain
It is structured for testability: each gRPC service delegates to repository abstractions, which in
turn encapsulate storage backends. Tests such as
service/src/test/java/ai/floedb/floecat/service/repo/impl/TableRepositoryTest.java and
service/src/test/java/ai/floedb/floecat/service/it/QueryServiceIT.java probe repository semantics and
query lifecycle / scan bundle logic.
Architecture & Responsibilities¶
┌────────────────────────────────────────────────────────────────────────────┐
│ Quarkus runtime │
│ ├─ Interceptors (context, localization, metering) │
│ ├─ Security (PrincipalProvider, Authorizer) │
│ ├─ Services (Catalog, Namespace, Table, View, Snapshot, Account, │
│ │ Directory, Statistics, Integrations, Overlays, Connectors, Query) │
│ ├─ QueryService (QueryContextStore, QueryServiceImpl) │
│ ├─ Repositories (CatalogRepository, NamespaceRepository, TableRepository, │
│ │ ViewRepository, SnapshotRepository, StatsRepository, │
│ │ ConnectorRepository, AccountRepository, │
│ │ IdempotencyRepositoryImpl) │
│ └─ GC & Bootstrap (IdempotencyGc, CasBlobGc, PointerGc, TransactionGc, │
│ ReconcileJobGc, SeedRunner) │
└────────────────────────────────────────────────────────────────────────────┘
Key packages¶
service/common– Shared helpers (BaseServiceImpl,IdempotencyGuard,Canonicalizer, pagination utilities, structured logging).service/context– gRPC interceptors injectingPrincipalContext, correlation IDs, query IDs, engine versions, and bridging outbound headers.service/security– MinimalPrincipalProviderandAuthorizerscaffolding; pluggable for production identity providers.service/repo– Resource repositories layering pointer/blob stores and parsing protobuf payloads; includes key generation utilities (Keys,ResourceKey) and value normalizers.service/catalog/directory/account/statistics/integration/connector– gRPC service implementations.catalog/builtin– Shared builtin catalog data model, validator, and loader helpers.service/query– Query lifecycle management (QueryContext,QueryContextStore,QueryServiceImpl).service/metagraph– MetadataGraph runtime (façade, loader, resolvers, and per-engine hint persistence); the immutable node models live incore/metagraph, assembled metadata and decoded hints are held byObjectCacheandHintCache, and pointer/disk-blob adapters live inservice/repo/cache/(seedocs/metadata-graph.mdanddocs/caching.md).service/gc– Scheduled cleanup for idempotency records, orphan pointers/blobs, stale transaction artifacts, and durable reconcile jobs.service/bootstrap– Optional seeding of demo accounts and catalog data.service/metrics–ServiceTelemetryInterceptor+StorageUsageMetricsfor Micrometer integration.
Public API / Surface Area¶
Each gRPC implementation derives from BaseServiceImpl, gaining retry semantics, error mapping, and
helpers like randomResourceId (UUIDv4). Highlights:
- CatalogServiceImpl – Enforces
catalog.read/catalog.writepermissions, canonicalises names, usesIdempotencyGuardfor Create, and ensures namespace cascading checks during Delete. - NamespaceServiceImpl – Handles hierarchical selectors, supports recursive listing, refuses restructuring that would strand descendants, and refuses deleting a namespace that still holds child namespaces, tables, or views.
- TableServiceImpl – Validates
UpstreamRef, enforces unique names before writing, supports partial updates viaFieldMask, and coordinates snapshot/statistics purging. - ViewServiceImpl – Stores SQL definitions and references to base tables.
- SnapshotServiceImpl – Binds snapshots to tables, ensuring parent-child relationships remain intact.
- TableStatisticsServiceImpl – Persists per-snapshot target/file stats; validates
NDV/histogram payloads; paginates target listings (optionally filtered by target kind); uses
client-streaming
PutTargetStatsto batch writes per stream. - DirectoryServiceImpl – Provides fast name↔ID lookup via
MetadataGraph(Resolve/Lookup) and reuses the graph’s ResolveFQ helpers for list/prefix pagination. - AccountServiceImpl – Administers accounts and enforces conventional permissions. Account deletion installs a durable write fence, then uses strongly consistent enumeration to remove catalog overlays before their integrations, followed by legacy connectors and local catalog descendants. Retrying a committed account delete resumes cleanup using the deletion record's original mutation metadata.
- CatalogIntegrationsImpl – Provides CRUD for external catalog identity records. The contract is
intentionally independent of legacy Connectors. It persists typed non-secret authentication
configuration for OAuth client credentials, bearer tokens, AWS assume-role/access-key, and AWS
SigV4. Secret values are accepted only in write-only credential messages, stored through the
SecretsManager under a deterministic integration-ID/credential-generation key, and never included
in resource responses. A typed credential-store resolver is available to follow-on catalog clients. A dedicated
authentication update RPC replaces configuration and rotates credentials while advancing a
visible credential generation. Unity integrations accept OAuth or bearer authentication; Iceberg
REST integrations accept every structurally valid authentication form the API supports.
Vendor/endpoint-specific compatibility is intentionally deferred to adapter selection and
validation in a follow-on change; this service does not yet perform connectivity validation or
reconciliation. Catalog endpoint URIs must
be hierarchical HTTP(S) URLs and cannot contain user-info, secret-bearing query parameters, or
fragments. Type is immutable through update; endpoint updates preserve the Integration identity
while advancing its etag. The API exposes
CM_REPLACEandCM_RETURN_EXISTINGas create-conflict primitives. Replacement publishes a new resource identity and atomically swaps the name pointer before the old identity is removed. Integration replacement is rejected while overlays depend on the old identity. Cascading delete uses a durable fence so retries resume dependent-overlay cleanup before removing the integration, dependency marker, and fence together. Idempotent creates publish the immutable success receipt in the same pointer transaction as the resource, so a retry never rebuilds its response from subsequently mutable state. Creates carrying credentials support idempotency. Secret values are excluded from durable request fingerprints and receipts, so the first successfully published credential value wins for retries of the same key. Credential writes happen before resource publication; definite publication failures remove the unpublished generation, and replaced credentials are deleted only after the resource CAS commits. If the publication acknowledgement is uncertain, the new secret is retained so a visible integration can never reference a deleted secret. Durable cleanup records are drained by pointer GC once a generation is provably superseded. Get accepts either resource ID or exact display name. Reads requirecatalog-integration.read; writes requirecatalog-integration.write, and cascading deletion of dependent overlays also requirescatalog-overlay.delete. - CatalogOverlaysImpl – Binds an integration and optional upstream namespace filters to an
existing Catalog. The Catalog remains independently named, writable, and managed, and multiple
overlays may target it. Creating an overlay atomically publishes Integration and Catalog
dependency pointers while requiring both parents to remain at their validated versions, and
advances both dependency markers in the same transaction.
CM_REPLACEatomically swaps in a new overlay identity and may bind it to another Integration or Catalog while advancing all affected dependency markers;CM_RETURN_EXISTINGreturns the existing object. Get accepts either resource ID or exact display name. An empty include list selects all namespaces; paths select subtrees, exclusions take precedence, and matching is case-sensitive. The Integration and Catalog bindings are immutable through update; updates only replace the selected include/exclude lists or rename the overlay. Reads requirecatalog-overlay.read; create and update requirecatalog-overlay.write, and creation also requirescatalog-integration.useandcatalog.write. Reconciliation requirescatalog-overlay.reconcilepluscatalog-integration.use; deletion requirescatalog-overlay.delete. These dedicated permissions are not inferred fromcatalog-overlay.writeor namespace/table/view permissions. Connectivity is deferred. - ConnectorsImpl – Manages connector lifecycle, validates
ConnectorSpecvia SPI factories, wires reconciliation job submission, and exposesValidateConnector+StartCapture.CaptureNowmaps to reconciler capture modes: - metadata only ->
METADATA_ONLY - capture only ->
CAPTURE_ONLY - metadata plus capture ->
METADATA_AND_CAPTURE - QueryServiceImpl – Administers query leases (
BeginQuery,RenewQuery,EndQuery,GetQuery) and exposes the scan streaming helpers (InitScan,StreamDeleteFiles,StreamDataFiles,CloseScan) so planners can request connector metadata safely. - SystemObjectsServiceImpl – Loads immutable builtin catalogs from disk/classpath, caches them
per engine version, and serves them via
GetSystemObjects.
Important Internal Details¶
BaseServiceImpl & Idempotency¶
BaseServiceImpl centralises retry policies (BACKOFF_MIN/MAX, jitter, RETRIES), correlation-ID
propagation, and error translation (storage/repository exceptions → gRPC status codes per
errors_en.properties). IdempotencyGuard stores request fingerprints inside
IdempotencyRepository (backed by the pointer/blob store) so replays reuse prior results.
Repository Layer¶
Each repository extends BaseResourceRepository<T>:
- Reserves pointer keys via CAS before writing blobs.
- Writes blobs with checksum verification (sha256B64).
- Maintains MutationMeta (pointer key, blob URI, pointer version, ETag, timestamp).
- For immutable CAS blobs, the ETag is derived from the hash in the URI; mutable and legacy blobs
retain the HEAD fallback so their RPC and precondition semantics do not change.
- Provides convenience accessors such as getByName, getById, list, and metaForSafe.
- Deletes tolerate missing blobs when cleaning up pointers, so skewed pointer/blob states can still be removed safely.
BaseResourceRepository also exposes reserveAllOrRollback for multi-key updates, and
compareAndDelete semantics for CAS-based deletions. Tests ensure parity between in-memory and AWS
implementations.
Namespace Shape Fencing¶
A namespace's shape is its set of child namespaces plus its set of tables and views. Two secondary index keys are derived from a different row's mutable fields:
| Derived key | Derived from |
|---|---|
namespaces/by-path/{path...} |
catalog id + ancestors' display names + own display name |
{tables,views,relations}/by-name/{name} |
catalog id + namespace id + relation name |
A repository recomputes secondary keys only for the row it writes, so nothing re-derives the keys of
rows underneath. Renaming or re-parenting a namespace therefore moves its own by-path pointer
while its descendants' pointers stay under the old path, and moving a namespace to another catalog
re-keys its relations without moving their pointers. The service refuses these operations rather
than performing a partial restructure:
| Operation | Refused when | Asserts |
|---|---|---|
| Rename namespace | it has any descendant namespace | markers/children, and its parent's when it has one |
| Re-parent namespace | it has any descendant namespace | markers/children, and the destination parent's when it has one |
| Move namespace to another catalog | it has any descendant namespace, table, or view | both markers, the destination catalog's child set, and the destination parent's when it has one |
| Delete namespace | it has any descendant namespace, table, or view | both markers, removed with the row |
| Move a namespace beneath itself | always | — |
The check is for a descendant at any depth, not an immediate child. A depth-one check is only
sufficient by induction: a descendant can only be added by a writer that asserts its parent's child
marker, so the parent's marker is enough provided every namespace's parent exists. Every writer
materialises its ancestors, so that holds going forward — but it did not always, so a row at a/b/c
with no a/b row can exist from before, and an immediate-child check cannot see it. A prefix count
also costs one read where a depth-one check costs a paged scan.
Renaming a leaf that holds only relations is allowed, because a relation's key carries the namespace id rather than the path. Moving a namespace beneath itself is refused outright: the destination parent resolves to the namespace itself, so every emptiness check passes and the write then vacates the path it just claimed to live under.
A rename joins its parent's child set, so it serializes against concurrent creates of its siblings for the same reason relation creates serialize with one another. A top-level namespace has no parent to join, so it asserts no parent condition; what covers that case is the catalog's own child set, below.
The refusal is atomic with the write. MarkerStore.childNamespacesFence and
MarkerStore.relationsFence return the marker versions as PointerConditions.markerVersions, which
the repository compiles into its own CAS batch as "require this version, then advance it".
Each call site samples them before the emptiness checks. That ordering is the call site's responsibility, not something these methods can enforce: a version sampled after the check is the version a concurrent writer already moved, so the assertion would confirm that writer instead of losing to it.
Exclusion holds only because every writer that changes a shape participates in the same assertion. Two batches that share no key cannot lose to each other, so a single non-participating writer would void the guard rather than weaken it:
| Writer | Asserts |
|---|---|
NamespaceServiceImpl create, update, delete |
the parent's or its own markers, in its own batch |
TableServiceImpl, ViewServiceImpl create and update |
the destination relation marker and namespace row when a relation enters a container where it was not already counted; leaving the source needs no fence |
TableServiceImpl, ViewServiceImpl delete |
nothing. A namespace delete racing a relation delete can only find the namespace emptier than it counted, which orphans nothing, so asserting here would cost a write to a hot key for no exclusion |
CatalogOverlayReconciler create, update, retire |
the shape assertion joined onto its overlay fence via PointerConditions.and |
TransactionIntentApplierSupport table intents |
the relation marker and the namespace's row, as CAS ops in the same pointer transaction -- on the same terms as a request, so only when the relation enters a container it was not already counted in |
A writer that loses its assertion retries on the shared FenceRetry policy, the same budget and
backoff as any other contended write, re-asserting on each attempt what the lost fence
may have invalidated -- the namespace it is joining may have been deleted by whoever won. One that
keeps losing raises a retryable abort.
Because relation writes assert a marker per namespace, concurrent creates into one namespace
serialize on it -- including the transaction commit path, which retries the whole batch when it
loses. "Serialize" here means retry, not queue: without a local retry the loser surfaces to the
client as TS_APPLY_FAILED_RETRYABLE, so the commit path carries the same bounded retry the request
paths do. The marker is one key per namespace, so sustained concurrent writes into a single namespace
still contend; sharding it is the remaining work.
That cost is inherent to proving a namespace empty atomically. It is not enough for both writers merely to read a common key: the delete's emptiness check happens before its batch, so a create landing in between is only excluded if the create itself writes something the delete asserts. A read-dependency cannot do that, which is why the marker is advanced rather than checked.
The two are used for different jobs. The marker excludes a namespace delete racing a relation write. A read-dependency on the namespace's own pointer -- which its delete removes -- excludes a relation write against a namespace that is already deleted, which the marker cannot catch because a write arriving afterwards samples the post-delete version and matches it. Relation writes carry both.
Bootstrap seeding asserts these fences too, and materialises every ancestor on a path it seeds rather than writing a nested row directly. It is check-then-create, but it runs on an executor while traffic is served and, on a restart, seeds into a catalog and namespaces it did not create in that run -- so it is a concurrent writer against a user's delete or rename like any other. Writing a nested row without its ancestors would make it the one writer that could strand one: it would assert the catalog's child set but no ancestor's, so a user deleting that ancestor could sample its marker, scan before the seed row committed, and commit alongside it.
Catalog Deletion¶
A catalog holds two kinds of child, counted separately: namespaces and catalog overlays. A namespace's
by-path key embeds the catalog id, so a catalog deleted from under one leaves that namespace
addressable under a catalog that does not exist.
Only delete can do this. A catalog's own by-name key derives from its own display name, which the
repository recomputes when it writes that row, and its children reference it by id — so renaming or
moving a catalog re-keys nothing beneath it.
| Operation | Refused when | Asserts |
|---|---|---|
| Delete catalog | it holds any namespace, or any overlay | catalogs/{id}/markers/children and catalogs/overlays-marker/{id}, both in the delete's own batch |
Each marker is asserted at the version read before the emptiness checks, and what is asserted depends on whether it exists. One that has been written is required at that version and deleted with the catalog rather than advanced — the resource it counts for is going, so advancing would leave a row behind counting nothing. One that has never been written is required absent, because the writer that adds the first child of that kind is the one that creates it, and that write then loses the batch carrying this assertion. Advancing a marker that was never written would not merely leave a stale row behind: an absent marker reads as version zero, so the advance would create a row for a resource being deleted in the same batch. The namespace count is read strongly, for the same reason the namespace guard's is: a marker is sampled with a consistent point read, so a namespace committed just before that sample is already in the marker version and an eventually-consistent count would read zero while the CAS matched.
Every writer that adds a namespace to a catalog asserts that catalog's children marker in its own
batch — CreateNamespace, the path-chain create behind it, CatalogOverlayReconciler's
materialization, a catalog move (which gains the destination catalog a child exactly as a create
does), and bootstrap seeding. Seeding is fenced here even though it is check-then-create, because on
a restart it seeds into a catalog it did not create in that run, which makes it a genuine concurrent
writer against a user's DeleteCatalog.
The catalog half is not a refinement of the parent-namespace fence. A namespace at the root of a catalog has no parent namespace, so its parent fence is empty and the catalog fence is the only thing standing between it and a concurrent delete.
Namespace deletes assert nothing about their catalog. Removal is the direction that orphans nothing.
Security and Context¶
InboundContextInterceptor reads x-query-id, x-engine-version, and x-correlation-id headers,
plus optional OIDC session/authorization headers, validates account membership, hydrates
MDC/OpenTelemetry attributes, and enforces the configured floecat.auth.mode.
OutboundContextClientInterceptor mirrors the same headers for internal gRPC calls
(service-to-service).
Authorizer currently performs simple list membership checks on PrincipalContext.permissions; it
can be replaced by injecting a custom implementation.
External session header authentication is documented in
docs/external-authentication.md.
Query Lifecycle Service¶
QueryContextStore is a process-local cache keyed by query ID. Each QueryContext tracks state,
expiration, PrincipalContext, encoded RelationPinSet, and ExpansionMap. Scan handles serve only
while their query is active.
QueryServiceImpl.beginQuery resolves name or ID references via Directory/Snapshot/Table services,
captures the selected snapshots, and stores the lease. Planners request connector file lists with InitScan (which
returns table metadata and a scan handle), then consume StreamDeleteFiles followed by
StreamDataFiles, and finally call CloseScan when done. Ordering is strict: StreamDeleteFiles
must be fully consumed before StreamDataFiles begins, otherwise the server rejects the data stream
with FAILED_PRECONDITION. The server auto-releases the scan session as soon as both streams finish,
so CloseScan is best-effort but still recommended to tidy server resources sooner. Each DataFile
currently reports DeleteRef.all_deletes=true; finer-grain delete references will come later once the
applicability logic is defined. The lease data (snapshots, expansion map, obligations) is returned to
the caller inside the QueryDescriptor.
Account Lifecycle¶
AccountScope controls snapshot-resolution and mutation admission. Pointer mutations use the
PlanningPointerIndex.Ownership seam, which is the single write-admission path. The default policy
is process-local and serves every account.
Managed Floe deployments get account exclusivity from routing plus the StatefulSet lifecycle contract: a given account is served by one Floecat owner. Floecat therefore does not write ownership records, recover assignments from KV, or fence every durable write. Process-local query contexts and resolved snapshot selections are optimizations only; retention is the snapshot-GC safety mechanism.
The protocol still calls these selections TablePin and RelationPinSet for compatibility. In
the current model, “pin” means “resolved snapshot selection”; it does not mean a GC root or a
durable lifetime reservation.
The extension points remain inside OSS Floecat. Deployments that need a different admission policy
can bind their own AccountScope or PlanningPointerIndex.Ownership without
changing query, cache, mutation or GC call sites.
One retention policy covers every versioned catalog object: snapshots of a table, and table
definitions, stats generations and constraints bundles. A version is kept while it is live or
published within retention plus grace. Snapshot publication time is Floecat's ingested_at, never
upstream event time; entries written before it existed fall back to the snapshot pointer, then to
the snapshot blob's write time. Other artifacts use their blob's write time.
The current snapshot stays selectable and readable as long as it remains live, however the
selection is spelled (CURRENT, AS OF, or an explicit id). Any other new selection fails with
MC_SNAPSHOT_TOO_OLD once it is past the visibility horizon. A query that
already selected such a snapshot receives MC_SNAPSHOT_EXPIRED once it is past retention plus grace,
or when its selected data is explicitly deleted or collected. Retrying may succeed if the selection
is still current and a missing derived artifact is repaired, but callers must not assume that an
explicit historical selection can be recreated. Both errors use FAILED_PRECONDITION; Core may discard and replan only when the
original query semantics allow a new selection (for example, CURRENT), and must not silently
replace an explicit snapshot or AS OF request. QueryContext and its resolved selections are
in-process read optimizations, never GC roots.
Retention is enforced in one place. Each pointer GC pass runs SnapshotExpiry first: a snapshot the
policy no longer keeps is dropped the way DeleteSnapshot drops one, its pointers and those of its
stats, index and constraints artifacts in one pointer transaction, then its root manifest entry.
Once pruned, a request for it by id or AS OF finds no such snapshot, as in Iceberg. Pruning needs
pointer GC; reclaiming the storage also needs CAS GC, which is plain reachability from the current
roots and reclaims whatever the table no longer references. At most 500 snapshots per table are
pruned per pass.
Managed deployments must keep upstream data available for at least the configured snapshot retention
period. Standalone OSS Floecat defaults to 0s retention, which keeps every snapshot; with CAS GC
enabled, unreferenced definitions, stats generations and constraints become collectable after grace.
Managed deployments must override retention with their authoritative-cache policy.
Builtin Catalog Service¶
SystemObjectsLoader reads immutable builtin catalogs (<engine_kind>.pb[pbtxt]) from the
configured location, caches them by engine kind, and exposes them through
SystemObjectsService.GetSystemObjects. Clients must send both x-engine-kind and
x-engine-version; the RPC always returns the filtered builtin bundle for the requested engine.
GC and Bootstrap¶
IdempotencyGc runs on a configurable cadence (see floecat.gc.* config) and sweeps expired
idempotency records in slices to avoid starvation. CasBlobGc performs a reachability-based sweep
per account from durable pointers and current table-root chains. Deletes are fenced by
floecat.gc.cas.min-age-ms and by retention plus grace, and any failed root-chain walk poisons the
account's delete phase so nothing is deleted that pass. CAS GC is disabled by default
(FLOECAT_GC_CAS_ENABLED). Every GC collector lists accounts through OwnedAccounts, so it
collects only accounts the local replica owns (PlanningPointerIndex.Ownership) and a multi-replica
deployment keeps each account's pointer index authoritative. A retained account continuation is
abandoned after floecat.gc.cas.max-consecutive-continuation-ticks so one large account cannot starve every other
account; raise that bound if the oldest-sweep-age metric shows a large account repeatedly restarting.
Snapshot compatibility artifacts under snapshots/<id>/compat/ are gateway-managed mutable
artifacts, not CAS objects, and remain owned by explicit snapshot/table lifecycle cleanup rather
than this CAS sweep. PointerGc removes
orphan/stale pointers. TransactionGc reaps expired/aborted transaction artifacts and dangling
intent indices. ReconcileJobGc enforces durable reconcile retention and queue/dedupe cleanup for
terminal jobs. The default finished reconcile-job retention window is 24 h
(floecat.gc.reconcile-jobs.retention-ms=86400000); older terminal jobs are removed from job-list
and job-detail views after that window unless operators configure a longer retention period or update
connector settings with connector settings update --finished-job-retention-sec. It is
retention-oriented GC, not a queue repair or index rebuild loop. SeedRunner populates demo data
when floecat.seed.enabled=true.
For connector-backed fixture tables, seeding runs a combined reconcile pass per fixture scope
using METADATA_AND_CAPTURE.
This ingests metadata/snapshots and runs capture through the reconcile job tree for stats.
Query scan bundles remain available immediately; stats availability follows queued capture completion.
Follow-up payloads use reconcile scoped stats requests
(table_id, snapshot_id, target_spec, column_selectors) so background capture stays targeted
without depending on separate unresolved snapshot-id and target lists.
When a CAPTURE_ONLY batch captures only a subset of requested items, the reconcile result
is degraded; when none of the requested items are captured, the reconcile result fails instead of
silently reporting zero processed stats.
Statistics streaming semantics¶
TableStatisticsServiceImpl enforces a single table_id + snapshot_id per streamed call to
PutTargetStats, rejects mixed idempotency keys within a stream, and applies
idempotent writes when a key is present. Each stream returns one response summarising the total
rows upserted after all batches have been consumed.
Data Flow & Lifecycle¶
Typical request path¶
client → Quarkus Server
→ InboundContextInterceptor (principal/query/correlation)
→ LocalizeErrorsInterceptor (message catalog)
→ ServiceTelemetryInterceptor (metrics/latency)
→ ServiceImpl (authz + validation)
→ Repository (CAS pointer/blob operations)
← response + MutationMeta
Configuration & Extensibility¶
Notable application.properties keys:
| Property | Purpose |
|---|---|
quarkus.grpc.server.* |
Port, HTTP2, plaintext/reflection toggles. |
quarkus.management.* |
Private management listener for health and operational endpoints. |
quarkus.grpc.clients.floecat.* |
Loopback client config for internal RPC calls. |
floecat.seed.enabled |
Enable demo data seeding. |
floecat.kv / floecat.blob |
Select pointer/blob store implementation (memory, dynamodb, s3). |
floecat.query.* |
Default TTL, grace period, max cache size, safety expiry for query contexts. |
floecat.query.resolver.max_parallel_inputs |
Per-request query-input snapshot-selection fan-out. Defaults to 8; values are clamped to 1–16. |
floecat.query.metadata-io.max-concurrency |
Process-wide admission bound for blocking metadata I/O shared by all requests. Missing values use 64; present malformed, blank, or out-of-range values fail startup. |
floecat.snapshot.retention / floecat.snapshot.retention-grace |
The retention policy for every versioned catalog object (see above). OSS defaults are 0s retention and 7d grace; managed deployments should override retention explicitly. 0s keeps every snapshot. |
floecat.catalog.bundle.max_parallel_relations |
Per-chunk relation-build fan-out for GetUserObjects. Defaults to 8. |
floecat.catalog.bundle.max_parallel_stats_warms |
Per-chunk stats-warm fan-out and shared process-wide stats-warm ceiling. Defaults to 16; clamped to >= 1. |
floecat.owner-publication.registration-batch-* |
Bounds one Owner Complete call by committed external-manifest bytes and, for registration chunks, objects and target-pointer writes. Defaults to 8 MiB, 10,000 objects, and 10,000 targets; tune downward if the caller's 120-second RPC deadline requires it. |
floecat.owner-publication.reuse-lease-ttl-ms |
Renewable GC lease for an in-flight Owner publication. Defaults to 24 hours. |
floecat.gc.idempotency.* |
Cadence, page size, batch limit, slice duration for idempotency GC. |
floecat.gc.cas.* |
Cadence, page size, min-age, tick slice settings for CAS blob GC. Unreferenced superseded blobs wait at least retention plus grace from their write time. |
floecat.gc.pointer.* |
Cadence, page size, min-age, tick slice settings for pointer GC. |
floecat.gc.reconcile-jobs.* |
Cadence, retention, and slice settings for durable reconcile-job GC. Finished terminal jobs default to 24 h retention. |
floecat.reconciler.job-queue.enabled |
Startup-scoped reconcile admission and execution kill switch (default true; env FLOECAT_RECONCILER_JOB_QUEUE_ENABLED). When disabled, persisted jobs and read/settings/cancellation APIs remain available, while new captures and executor work return FAILED_PRECONDITION. Restart after changing it. |
floecat.reconciler.worker-affinity |
Exact job-tree contract cohort shared by a control plane and its executor fleet (default reconciler-v1). |
floecat.reconciler.job-store.* |
Durable reconcile queue selection and retry/lease tuning. |
quarkus.log.* |
JSON logging, file rotation, audit handlers per RPC package. |
quarkus.otel.* / quarkus.micrometer.* |
Observability exporters (see docs/operations.md). |
floecat.auth.mode |
Auth enforcement mode (oidc, dev). |
floecat.auth.platform-admin.role |
IdP role name granted permission to manage accounts (default platform-admin). |
floecat.secrets.aws.role-arn |
Optional role to assume per account when using AWS Secrets Manager. |
Extension points:
- Storage – Provide custom PointerStore/BlobStore (see docs/storage-spi.md).
- Durable reconcile storage – The queue-facing reconciler store is split into native
job-index, ready, lease, and projection domains. Those are the extension seams to target if the
durable reconcile backend evolves further; they are no longer intended to share one generic
pointer-prefix queue abstraction.
- Security – Replace Authorizer or interceptors with CDI alternatives.
- Connectors – Register new SPI implementations and expose them via ConnectorRepository.
- QueryService – Extend query metadata by enriching QueryContext creation or injecting
additional connector metadata via the QueryScanService streaming RPCs / ScanBundleService on the query
path. Reconcile planning/execution does not use ScanBundleService; it goes through
FloecatConnector directly. BeginQuery optionally accepts a client-specified query_id plus
common.QueryInput records so lifecycle can resolve snapshots/expansions up front for deterministic
replay.
Secrets Manager integration (tags + optional per-account assume-role) is documented in
docs/secrets-manager.md.
Examples & Scenarios¶
- Create Catalog –
CatalogServiceImpl.createCatalogcanonicalisesdisplay_name, allocates a UUIDv4 identifier, reserves/accounts/{account}/catalogs/by-name/{name}and/by-id/{uuid}pointer keys, writes thecatalog.pbblob, and returnsMutationMeta. If the caller supplies anIdempotencyKey, the repository short-circuits duplicates. - Delete Namespace – The service refuses a namespace that still holds child namespaces, tables,
or views, raising
MC_CONFLICT.namespace.not_empty. The emptiness checks and the delete commit together under the namespace's shape markers, so a child or relation created concurrently either loses its assertion or makes the delete lose one. - Query lease renewal – Clients call
QueryService.RenewQuerybeforeexpires_at; the store extends the TTL if the query remainsACTIVE. A stale or ended query returnsMC_NOT_FOUND.query.not_found.
Cross-References¶
- RPC contracts:
docs/proto.md - Connector SPI & implementations:
docs/connectors-spi.md,docs/connectors-iceberg.md,docs/connectors-delta.md - Storage implementations consumed by repositories:
docs/storage-spi.md,docs/storage-memory.md,docs/storage-aws.md - Reconciler orchestrating connectors:
docs/reconciler.md