Datasources.
Last updated
Datasources
What it is. A governed connection to one of your databases. Every query a user runs against it passes through AccessFlow's review, masking, and row-security guards instead of hitting the database directly — so a datasource is where you decide who may run what against which data. PostgreSQL, MySQL, MariaDB, Oracle, and MS SQL Server are built in; MongoDB, Couchbase, Redis, Cassandra / ScyllaDB, Elasticsearch / OpenSearch, DynamoDB, Neo4j, Snowflake, BigQuery, and Databricks install from the connector catalog; any other JDBC engine works by uploading its driver and choosing Custom.
What is a datasource in AccessFlow?
A datasource is a governed connection to one of your databases. Users never receive its credentials — they submit queries to AccessFlow, which reviews them and then executes them over the pooled connection on their behalf. Masking, row-level security, schema allow-lists, and row caps are all configured per datasource.
Configure it. Create one with the four-step wizard at
/datasources/new:
/datasources/new — four-step wizard: Database type → Connection details → Connection test → Configuration.- Database type. Pick a bundled driver tile (PostgreSQL ships built-in; other drivers download on first use and are verified against a pinned SHA-256 checksum). Pick Custom to use a JDBC driver you uploaded under Admin → Custom JDBC drivers.
- Connection details. Name the datasource, then enter host, port, database name, service-account username, and password. SSL mode is pre-filled from the engine's own default rather than one global value — PostgreSQL starts at
VERIFY_FULL, several NoSQL engines start atDISABLE, and the rest atREQUIRE. Check it rather than assuming it, and preferVERIFY_FULLin production. For Cassandra and ScyllaDB the wizard also requires a local datacenter (the driver's load-balancing datacenter); this field is unused for every other engine. For Elasticsearch and OpenSearch the wizard offers an Authentication toggle — basic (username + password) or an API key — and the database-name field is optional. For Amazon DynamoDB the connection is cloud credentials, not host/port: the wizard hides host/port and instead asks for the AWS region (the database-name field), the access key ID and secret access key (the username/password fields), and an optional custom endpoint (DynamoDB Local / VPC; blank for AWS). For Neo4j the wizard takes the standard host/port/database/username/password (the SSL mode is encoded in the Bolt scheme) plus an optional Bolt connection URI (advanced) — a fullbolt:///neo4j+s://URI for Neo4j Aura or clustered routing that, when set, overrides host/port. For Snowflake the wizard asks for the account host (<account>.snowflakecomputing.com; the port field is hidden — always 443), the database, the user, a credential that is either a password or a PKCS#8 private key (PEM) for key-pair authentication, an optional private key passphrase (only for a passphrase-protected key, which is what Snowflake's ownopensslinstructions produce), and an optional JDBC URL override — a fulljdbc:snowflake://URL carrying warehouse / role / schema parameters. For Google BigQuery the connection is cloud credentials: the wizard hides host/port/username and asks for the GCP project (optionallyproject.datasetto pin a default dataset) and the service-account key JSON, plus an optional custom endpoint (BigQuery emulator). For Databricks SQL the wizard asks for the workspace host, the required warehouse HTTP path (/sql/1.0/warehouses/<id>from the warehouse's connection details), an optional Unity Catalog catalog, and a personal access token. The password (and API key / secret access key) are AES-256-GCM encrypted on write, decrypted once into the connection pool, and never returned in any GET response. When an external secrets manager is enabled (see Run & deploy), any credential field also accepts a secret reference —vault:<mount>/<path>#<field>,aws:<name-or-arn>[#jsonField], orazure:<secret-name>— stored as-is and resolved through the store at connection time; the form shows the syntax hints for whichever providers are enabled. - Connection test. AccessFlow opens a real JDBC connection, runs a heartbeat query, and surfaces any SSL / authentication errors before you save.
- Configuration. Pick the Review plan that gates this datasource, toggle Require review on reads / writes, and (optionally) enable AI analysis and/or text-to-query + pick an AI configuration. The AI configuration is shared by both features, so it is required whenever either toggle is on. With text-to-query on, users can draft a query from a natural-language prompt in the editor — in the engine's native query language (SQL or a NoSQL query) — and the draft still flows through the normal review pipeline. Pool size, max rows, and statement timeout default sensibly but can be tightened per datasource. An optional Environment (Development, Test, Staging or Production) picks which SQL review ruleset applies to queries on this datasource — leave it unset to use the organization default.
Read replicas & load balancing (optional). On the datasource
settings page, the Read replicas card takes any number of replica endpoints
(JDBC URL plus optional username and password per endpoint — blank credentials reuse
the primary's). AccessFlow opens one connection pool per endpoint and load-balances
every query classified as SELECT round-robin across the healthy replicas;
INSERT / UPDATE / DELETE / DDL and transactional BEGIN … COMMIT batches
always hit the primary. Replicas must use the same database engine as the primary
(they reuse the primary's JDBC driver), and credentials are AES-256-GCM encrypted with
the same ENCRYPTION_KEY. Per-node health checks (a background prober plus
a circuit breaker) take a failed endpoint out of rotation for a cooldown
(ACCESSFLOW_PROXY_REPLICA_COOLDOWN, default 30s) and its health shows on
the Datasource health dashboard; only when every replica is down does the
read fall back to the primary, with one DATASOURCE_REPLICA_FALLBACK audit
row visible at /admin/audit-log. Click Test replica on any row
to validate its URL + credentials live without persisting; leaving the password blank
reuses that endpoint's saved password. Remove every endpoint to disable replica
routing. Replica pools reuse the same ACCESSFLOW_PROXY_* connection-pool
tuning as the primary; the health checks are tuned by the
ACCESSFLOW_PROXY_REPLICA_* variables.
SELECT result caching (optional). The settings page's
Performance card opts a datasource into a Redis-backed result cache for
repeated identical SELECTs, with a per-datasource TTL (1–86,400 seconds;
blank uses ACCESSFLOW_PROXY_CACHE_DEFAULT_TTL, default 60s). Caching is
security-safe by construction — entries are keyed over the row-security-rewritten
query and the caller's masking scope, so masking and row-level security always apply —
and any write executed through AccessFlow to a referenced table (including GDPR
erasure and retention deletes) immediately invalidates the affected entries. Note that
writes made outside AccessFlow are invisible to the cache and are served
stale until the TTL expires, so pick a TTL that matches how the datasource is written.
ACCESSFLOW_PROXY_CACHE_ENABLED=false switches the feature off
deployment-wide.
Grant a user access. Open the datasource → Permissions tab and add a row per user — can read / can write / can DDL, allowed schemas, allowed tables, restricted columns (masked as *** in SELECT results), denied schemas and tables, denied columns, and denied query shapes. Without a permission row, a user can't see or query the datasource at all. The allowed schemas / allowed tables lists are enforced when a query is submitted: every table it references — across joins, subqueries, CTEs, and BEGIN; …; COMMIT; batches — must appear in allowed tables or live in an allowed schema, or the query is rejected before it runs. Matching is case-insensitive, and an unqualified table name (FROM users) only matches an unqualified entry in allowed tables. Leave both fields empty to allow every table.
Denied schemas and tables — everything except. Sometimes it is easier to say what a user may not touch. Allow the schema crm and deny the table crm.salary, and the user can query every table in crm — including tables created later — except crm.salary. A query that touches a denied table is refused before it runs:
- A denial always wins. It is checked after the allowed schemas and tables, and it works on its own too, with no allowed list at all.
- Name tables with their schema. An entry written as just
salarydenies a table calledsalaryin every schema.crm.salarydenies that table, and also a query that writes plainsalary, because AccessFlow cannot tell which schema the database would pick. - Denying a schema. Every table in a denied schema is refused. While any schema is denied, the user must write table names with their schema (
crm.customer, notcustomer); an unqualified name is refused for the same reason as above. - Hidden, not just refused. Denied tables and schemas disappear from the schema tree, autocomplete, the table preview, AI query drafting and the AI agent tools.
- Several grants add up. If a user holds their own grant and group grants, a table denied by any one of them stays denied — a wider group grant cannot undo it, and a group's denial applies to every member. Denied columns work the same way (below).
- Who it does not bind. Administrators with query-admin rights skip per-datasource permission checks.
- Just-in-time access keeps denials. A just-in-time access request cannot add a denied schema or table, and approving one never removes a denial: denials from every grant add up, and when the approval replaces the user's own expiring grant, that grant's denials carry over to the new one.
- How to write entries. A denied schema is a single name, such as
hr— notanalytics.hr. A denied table istable,schema.table, orschema.*for a whole schema. Other wildcards and empty parts are refused when you save the grant. - Tricky names are refused, not guessed. SQL Server's
db..salary(default schema) is treated as matching any schema, an Oracle database link (hr.salary@remote) does not get around a denial, and a name pattern such as the Elasticsearch index patternsal*is refused whenever the grant denies anything. - Works on every datasource type. Denied schemas and tables apply to relational, NoSQL and warehouse datasources alike. On datasources whose objects have no schema — MongoDB collections, DynamoDB tables, Redis keys — a denied schema refuses every query, so use denied tables there. A denial can only catch the tables AccessFlow sees in the query: MongoDB
$lookup,$unionWithand$graphLookupstages, a Neo4jMATCH (n)with no label, and a RedisKEYSpattern are not caught. The allowed lists share this limit. - Removing a grant can widen access. A denial belongs to the grant that carries it. Revoke that grant, let it expire, or revoke it in an access review, and its denial goes with it — another grant the user still holds may then let them reach the table. Check the user's other grants first.
Denied columns — block instead of mask. A restricted column can still be queried; only its value is hidden. For a column that must never be read at all, list it under Denied columns as table.column or schema.table.column. A query that uses it is refused before it runs:
- What counts as using it. Selecting it, filtering, joining, grouping or sorting on it, or reading its whole table through
SELECT *,TABLE tor a whole-row value such asrow_to_json(t). Spell out the columns you need instead of*. The table preview on the Schema tab reads every column, so it is refused on a table with a denied column. - Joins. A column written without its table in a query that joins several tables is refused if any of those tables denies a column of that name. Prefix it with the table to avoid this.
- Deny beats mask. A query that uses a column that is both restricted and denied is refused.
- Who it does not bind. Administrators (any role with query-admin rights) skip per-datasource permission checks, so a denied column does not stop them. If a user holds several grants on the datasource — their own and their groups' — a column denied by any one of them stays denied. A wider grant that denies nothing cannot undo it, and a group's denied columns apply to every member. A temporary just-in-time grant that replaces a user's own expiring grant keeps that grant's denied columns.
- Supported datasources. PostgreSQL, MySQL, MariaDB, Oracle, SQL Server and custom JDBC. The field is not offered for NoSQL or cloud data-warehouse datasources.
Denied query shapes — limit how a query is written. Some grants should allow a table but not every way of querying it — for example, simple lookups but no joins or totals. Pick the query shapes to refuse under Denied query shapes: joins, set operations (UNION, INTERSECT, EXCEPT), subqueries, WITH clauses, GROUP BY, HAVING, aggregate functions and window functions. Denying all of them leaves plain single-table queries such as SELECT … FROM … WHERE … ORDER BY; shapes don't change whether the grant can write, so keep can write off for a read-only grant. A query that uses a denied shape is refused before it runs:
- Anywhere in the query. A join inside a subquery or a
WITHclause counts, and in aBEGIN; …; COMMIT;batch every statement is checked. - Aggregate functions. The built-in ones —
COUNT,SUM,AVG,MIN,MAX,JSON_ARRAYAGG, the statistics functions and similar. A custom aggregate function defined in your database, or a rarely used built-in, may not be recognised, so pair this setting with database permissions where it must hold. - When in doubt, refuse. If AccessFlow cannot work out a query's shape, it treats the query as using every shape the grant denies.
- Who it does not bind. Administrators with query-admin rights skip per-datasource permission checks when they submit a query. Emergency (break-glass) queries are still checked, for everyone.
- Several grants add up. A shape denied by any of a user's grants stays denied, and a just-in-time grant that replaces a user's own expiring grant keeps its denied shapes.
- Supported datasources. PostgreSQL, MySQL, MariaDB, Oracle, SQL Server and custom JDBC. The field is not offered for NoSQL or cloud data-warehouse datasources.
- Rather review than refuse? Use the Query shape condition in a routing policy instead, for example to send every joined query to a second reviewer.
Users only see the tables they are granted. The same lists decide what a user can browse. The schema tree in the query editor, autocomplete, AI query drafting and the AI agent tools show a user only the tables their allowed schemas and tables cover, leave out their denied schemas and tables, and leave out their denied columns. Administrators still see every table. If the same table name exists in more than one schema, write the entry as schema.table; an entry with just the name then shows neither table. Two places still list every table name on purpose: the just-in-time access request form, because asking for access to a table you cannot see yet is its whole purpose, and the automatic AI review of a submitted query, which reads the whole schema, so its comments may mention other tables.
Schema explorer & ER diagram. Each datasource also carries
Schema and ER diagram tabs alongside Configuration /
Permissions. The schema view introspects the live database (cached and
refreshable from the UI) and renders a searchable object tree — one
filter matches across schema, table, and column names. Click any table to open a
sample-data preview: a small, read-only set of rows fetched through the
same governance path as a real query, so row-level security filters the rows and column
masking redacts sensitive values (masked columns show ***, never the raw
value). The same searchable tree and preview are available in the query editor sidebar.
The ER tab lays those tables out as a node-and-edge graph with PK/FK badges and column
types so reviewers and operators can sanity-check what a query is touching without
leaving AccessFlow.
/datasources/<id>/settings → ER diagram. Auto-laid-out via dagre; node positions persist after manual edits.
Masking policies. The datasource Masking tab adds per-column
dynamic data masking on top of the static restricted-columns masking above. Each
policy targets a schema.table.column and picks a strategy —
full (***), partial (keep the last N characters),
hash (stable SHA-256), email (j***@domain),
format-preserving, keep first N (0912******),
constant (a fixed value such as REDACTED), nullify
(NULL), regex replace, numeric bucket (a salary
rounded down to its 10,000 band, an age to a band) or date generalization (a birth date to its year,
quarter or month) — with an optional reveal-to condition. Strategy settings are
checked when you save the policy, so a broken regular expression is rejected there rather
than during someone's query, and a value a strategy cannot handle — text in a numeric
bucket, a value the regex does not match — is fully masked instead of shown. A query
submitter whose role, group, or user id is listed in reveal to sees the unmasked
value; everyone else sees the strategy output. A live preview shows how a sample value will
render. Masking is applied at result-read time before results are serialized or stored, so
unmasked values never persist, and the ids of the policies that applied are recorded in the
execution's audit metadata. Reveal is explicit — there is no implicit admin bypass.
/datasources/<id>/settings → Masking. Per-column dynamic masking with role / group / user reveal conditions.
Row security policies. The datasource Row security tab adds
row-level security: per-table predicates the proxy injects into the parsed SQL so a
scoped user only sees (SELECT) or affects (UPDATE/DELETE) the rows they are authorised for.
Each policy is a structured column operator value predicate where the value is a
fixed literal or a :user.* variable — the built-in
:user.id / :user.email / :user.role /
:user.groups, or an admin-set per-user attribute (the Attributes
key/value editor on Admin → Users, or a service account's Attributes
tab). The applies to roles / groups / users
scope it (empty = everyone, no implicit admin bypass — the inverse of masking's
reveal to). Values are bound as parameters, never concatenated; an unresolved
variable filters out every row (fail-closed); and a query the engine can't safely rewrite
(a policied table inside a UNION, CTE, sub-select, or join-onto-another-policied-table) is
rejected rather than run unfiltered. Applied policy ids are recorded in the execution's audit
metadata, and the query's detail page keeps the effective SQL — the statement as it
actually ran, with the policy's filter in place and its values shown as ? — so
an auditor sees what executed even after the policy is later changed or deleted.
/datasources/<id>/settings → Row security. Per-table predicates injected into the parsed SQL; values bound as parameters.Simulate a policy before you save it. Both the Masking and Row security forms have a Simulate button that dry-runs the draft against this datasource's own past queries, so you see the blast radius first. Pick a date range (up to 90 days) and AccessFlow replays that traffic twice — once against the policies in place today, once with the draft added or replacing the one you are editing — then reports the difference: for masking, which columns would start (or stop) being hidden, in how many past queries, and for whom; for row security, which queries would newly come back filtered, come back empty, or be rejected outright because the engine cannot safely apply the predicate to that shape. Redis is the clearest case — a row rule has no meaning over a key-value store, so the simulation lists exactly the commands the policy would start refusing. The same button sits on the routing policy form.
Row limits. The datasource Row limits tab caps how many rows a query may return when it reads a particular table, so two tables on the same database can have different limits and one team can be held tighter than another on the same table. Each policy names a table (and optionally its schema), a maximum number of rows, and the applies to roles / groups / users it covers (empty = everyone, admins included). A row limit can only ever lower the cap: the datasource's Max rows per query and any per-user limit on the access grant still apply, and the smallest number wins. A query that joins several limited tables gets the lowest of their limits. A policy with a schema also catches queries that name the table without one or with a database name in front, so neither gets anyone more rows. Results that hit the limit are marked as truncated, the table preview obeys the same limit, and when a policy's limit is the one that applied it is recorded in the query's audit entry.
Bytes-scanned cap — limit what a warehouse query can cost. On BigQuery, Snowflake and Databricks you pay for the data a query reads, not the rows it returns, so a query that returns ten rows can still read a terabyte. Before every query runs, AccessFlow asks the warehouse how much data it expects to read. Set Max bytes scanned per query in the datasource's Limits & policies section (entered in MB, GB, TB or PB) and a query expected to read more is refused, with a message that names the estimate and the cap:
- Checked twice. Once when the query is submitted, before anyone reviews it, and again just before it runs. That covers scheduled, recurring and grouped queries, emergency (break-glass) runs, and a cap you lowered after the query was approved.
- When there is no estimate. Sometimes the warehouse cannot give one, for
example for a
CREATE TABLE. Choose what happens under When no bytes estimate is available: Require human review (the default) sends the query to a reviewer even if it would otherwise be approved automatically, and Reject the query refuses it. With Require human review, the check just before a query runs lets it through once it has been approved, and an emergency (break-glass) run, which has no reviewer, also goes ahead and is reviewed afterwards. - Tighter for some people. An access grant can set its own, lower Bytes-scanned cap. The smallest cap that applies wins, across the datasource and all of a user's own and group grants, and administrators are bound by it too.
- An estimate, not a bill. The warehouse's estimate can be higher or lower than what a query really reads. Leave the field empty for no cap.
- Supported datasources. BigQuery, Snowflake and Databricks — the warehouses that report how much a query will read. The fields are not offered for other datasources.
- Rather review than refuse? Use the Estimated bytes scanned condition in a routing policy to send expensive queries to an extra reviewer instead.
Data budgets — limit how much one person reads over time. Row limits and the bytes-scanned cap stop one big query. They cannot stop someone copying a table out a few thousand rows at a time over a week. A data budget can: on the datasource Data budgets tab, set how many rows, how much result data (entered in MB, GB, TB or PB), or both, a person may read from this datasource over a rolling window, from one hour up to 31 days:
- Everyone gets their own allowance. Choose the roles, groups or people the budget applies to, or leave all three empty for everyone who uses the datasource. A budget for a group gives each member the full allowance; it is never shared between them.
- What counts. Everything a person actually receives from a read: rows after row-level rules and every other cap (masked rows still count), and the size of the result. That includes their own queries, scheduled and recurring runs, grouped requests, table previews and emergency (break-glass) runs. Writes never count. Usage is counted from the moment a budget exists.
- A rolling window. The budget looks back over the last hour, day or week you chose, measured from now, so older reads drop out gradually. There is no midnight reset.
- While allowance is left, a query's result is cut short at the rows and data the person has left, and the result says the data budget was the reason.
- Once it is used up, the budget does what you chose under When the
budget is used up. Send to human review (the default) sends every new
SELECTto a reviewer, even one that would otherwise be approved automatically, and runs it once a reviewer approves. An approval given earlier, while allowance was left, does not count. Reject refuses new reads outright. Table previews and grouped requests are refused either way. Emergency (break-glass) runs are counted but never cut short or refused by a budget; they are reviewed afterwards as usual. - Several budgets. When more than one applies to a person, the one with the least left wins, and Reject wins over review.
- Warnings. Set Warn at (% used) and the person is notified once when they pass it. When a budget is used up, they and the administrators who manage data budgets are notified. People see what they have left beside the query editor, and admins can open Data usage on any user's row on the Users page.
- Approximate, by design. Result size is AccessFlow's estimate of the result in memory, not network bytes, and two queries that run at the same moment can go over the limit by at most one query's worth.
- Rather review than refuse early? Use the Data budget used (%) condition in a routing policy to send a heavy reader's queries to an extra reviewer before they run out.
Export policies. Masking and row security govern what a user
sees; the datasource Export policy tab governs what leaves.
Each policy sets a mode — allow, watermark, row cap, or
deny when classified (optionally scoped to specific classifications) — and an
applies to roles / groups / users target (empty = every exporter, no implicit
admin bypass). When several policies apply, the most restrictive wins. The policies gate
the signed CSV/PDF result download on the query detail page and the results attachment
on recurring-run emails: a denied exporter sees a disabled export button with the
reason, a watermarked download carries the exporter, timestamp, and query id baked into
the signed bytes (the modal previews the exact stamp), and every export lands in the
audit log as RESULT_EXPORTED — with an admin notification whenever a
classified result leaves.
Data classification
The datasource Classification tab tags
tables and columns with one or more data classifications — PII, PCI,
PHI, GDPR, FINANCIAL, or SENSITIVE — and
derives stricter handling automatically. Tagging a column
auto-applies a masking policy from the classification's default strategy
(PII / GDPR / FINANCIAL → partial, PCI / PHI → full, SENSITIVE → hash), so you don't
hand-configure masking for every sensitive field; a table-level tag (no column) is
informational. A query that references a tagged table gets an automatic AI risk-score
bump, and a derivation preview suggests a stricter review posture (AI review,
human approval, minimum approvals) aggregated across the datasource's tags — a suggestion
you apply on the datasource's review plan, never auto-changed. Tags are immutable
(create / delete) and audited; deleting a tag keeps the masking policy it derived. The
classifications appear as badges in the schema explorer, and Admin → Data
classifications (/admin/data-classifications) lists every tag across all
datasources as the evidence base for compliance reporting.
Automated discovery. Instead of tagging hundreds of tables by hand, the
datasource Discovery tab opts a datasource into a scheduled scanner that samples
column data through the same governed sampling path, detects sensitive values with local
regex + checksum detectors (emails, credit-card numbers with Luhn, US SSNs, IBANs, phone
numbers) and — optionally — your bound AI analyzer, then proposes the
classification tags in a review worklist. Confirming a finding applies the tag (deriving
masking exactly like a manual tag); dismissing suppresses the proposal permanently. Raw
sampled values never persist (findings store a redacted sample only), and the AI pass
only ever sees column names, types, and redacted samples. Configure the per-datasource
sample size (10–1000 rows, never more than the datasource's row cap) and cadence (1–720 hours), or hit Scan now for an
immediate run; scans and decisions land in the audit log
(DISCOVERY_SCAN_COMPLETED, DISCOVERY_FINDING_CONFIRMED /
_DISMISSED). Operator knobs:
ACCESSFLOW_DISCOVERY_SCAN_POLL_INTERVAL (PT15M),
ACCESSFLOW_DISCOVERY_SCAN_TIME_BUDGET (PT10M),
ACCESSFLOW_DISCOVERY_SAMPLE_STATEMENT_TIMEOUT (PT10S),
ACCESSFLOW_DISCOVERY_MAX_TABLES_PER_SCAN (200),
ACCESSFLOW_DISCOVERY_MAX_AI_TABLES_PER_SCAN (25),
ACCESSFLOW_DISCOVERY_MAX_NESTED_DEPTH (5),
ACCESSFLOW_DISCOVERY_MAX_NESTED_LEAVES_PER_ROW (100),
ACCESSFLOW_DISCOVERY_STALE_SCANS_BEFORE_EXPIRY (3),
ACCESSFLOW_DISCOVERY_SCAN_LOCK_AT_MOST_FOR (PT30M).
One scan per datasource at a time. A scan claims its datasource for as
long as it runs, so the same tables are never sampled twice at once — including when
you run AccessFlow on several servers, where a Scan now and a scheduled scan
could otherwise start on different ones. Scan now is refused outright while a
scan is under way, so you always know which run you are looking at; a scheduled scan
that finds the datasource busy simply leaves it due and picks it up on the next round.
ACCESSFLOW_DISCOVERY_SCAN_LOCK_AT_MOST_FOR above is only the safety net for
a server that dies mid-scan: it caps how long the claim can outlive the machine holding
it. Raising ACCESSFLOW_DISCOVERY_SCAN_TIME_BUDGET raises the claim with it,
keeping a wide margin over the time a scan is expected to take — so leaving this one
alone is normally right. To hand a datasource back sooner than the cap after a server
has died, delete the Redis key
job-lock:accessflow:shedlock:discoveryScan:<datasource id>.
Proposals that go quiet clean themselves up. When a column is dropped,
its data is cleared, or you mask it by hand, the scan simply stops proposing it — and
the old suggestion would otherwise sit in the worklist forever. Instead, a proposal the
scanner keeps sampling but no longer finds is marked Stale after a few
consecutive scans and drops out of the default Pending view; switch the status
filter to Stale to see those proposals and dismiss the batch in one go.
Nothing is thrown away: a stale proposal is still yours to confirm or dismiss, and if
the data comes back the next scan returns it to Pending. Only tables a scan
actually sampled can age this way, so a run cut short by its table cap or time budget
never retires proposals it did not look at. Retirements are audited as
DISCOVERY_FINDING_EXPIRED, up to 100 rows per scan — past that the scan's
own audit entry carries the full count and flags the trail as truncated. How many
consecutive misses it takes is
ACCESSFLOW_DISCOVERY_STALE_SCANS_BEFORE_EXPIRY above.
Tune it. Per-datasource fields above set row caps and review behaviour; these environment variables set the engine-level connection and execution ceilings (defaults shown):
- Connection pools (all JDBC engines):
ACCESSFLOW_PROXY_CONNECTION_TIMEOUT(30s),ACCESSFLOW_PROXY_IDLE_TIMEOUT(10m),ACCESSFLOW_PROXY_MAX_LIFETIME(30m),ACCESSFLOW_PROXY_LEAK_DETECTION_THRESHOLD(0s= off). - Statement execution (all engines):
ACCESSFLOW_PROXY_EXECUTION_MAX_ROWS(10000),ACCESSFLOW_PROXY_EXECUTION_STATEMENT_TIMEOUT(30s),ACCESSFLOW_PROXY_EXECUTION_DEFAULT_FETCH_SIZE(1000),ACCESSFLOW_PROXY_EXECUTION_INSERT_BATCH_CHUNK_SIZE(1000). - Heap protection (relational engines):
ACCESSFLOW_PROXY_EXECUTION_MAX_RESULT_BYTES(52428800— per-result byte cap; larger SELECT results are truncated),ACCESSFLOW_PROXY_EXECUTION_MAX_CONCURRENT(32— global in-flight execution budget across all datasources),ACCESSFLOW_PROXY_EXECUTION_ACQUIRE_TIMEOUT(5s— wait before overflow executions are rejected with HTTP 503). - SELECT result cache:
ACCESSFLOW_PROXY_CACHE_ENABLED(true),ACCESSFLOW_PROXY_CACHE_DEFAULT_TTL(PT60S),ACCESSFLOW_PROXY_CACHE_MAX_ENTRY_BYTES(1000000). - Data-budget usage history:
ACCESSFLOW_CORE_DATA_BUDGET_USAGE_RETENTION(P32D— how long read usage is kept; never less than 31 days and an hour, so the longest window is always covered),ACCESSFLOW_CORE_DATA_BUDGET_PRUNE_INTERVAL(PT1H— how often older usage is cleaned up). - Read-replica health checks:
ACCESSFLOW_PROXY_REPLICA_PROBE_INTERVAL(PT30S),ACCESSFLOW_PROXY_REPLICA_PROBE_TIMEOUT(PT5S),ACCESSFLOW_PROXY_REPLICA_COOLDOWN(PT30S). - MongoDB:
ACCESSFLOW_PROXY_MONGO_CONNECT_TIMEOUT(PT10S),…_SERVER_SELECTION_TIMEOUT(PT10S),…_MAX_POOL_SIZE(10). - Couchbase:
ACCESSFLOW_PROXY_ENGINES_COUCHBASE_CONNECT_TIMEOUT(PT10S),…_WAIT_UNTIL_READY_TIMEOUT(PT10S),…_SCAN_CONSISTENCY(request-plus). - Redis:
ACCESSFLOW_PROXY_ENGINES_REDIS_CONNECT_TIMEOUT(PT5S),…_SOCKET_TIMEOUT(PT5S),…_MAX_POOL_SIZE(10). - Cassandra / ScyllaDB:
ACCESSFLOW_PROXY_ENGINES_CASSANDRA_CONNECT_TIMEOUT/…_SCYLLADB_CONNECT_TIMEOUT(PT10S) and the matching…_REQUEST_TIMEOUT(PT10S). - Elasticsearch / OpenSearch:
ACCESSFLOW_PROXY_ENGINES_ELASTICSEARCH_CONNECT_TIMEOUT/…_OPENSEARCH_CONNECT_TIMEOUT(PT10S) and…_SOCKET_TIMEOUT(PT30S). - DynamoDB:
ACCESSFLOW_PROXY_ENGINES_DYNAMODB_CONNECT_TIMEOUT(PT10S),…_API_CALL_TIMEOUT(PT30S). - Neo4j:
ACCESSFLOW_PROXY_ENGINES_NEO4J_CONNECT_TIMEOUT(PT10S),…_MAX_CONNECTION_POOL_SIZE(100). - Snowflake:
ACCESSFLOW_PROXY_ENGINES_SNOWFLAKE_LOGIN_TIMEOUT(PT30S),…_NETWORK_TIMEOUT(PT60S). - BigQuery:
ACCESSFLOW_PROXY_ENGINES_BIGQUERY_CONNECT_TIMEOUT(PT10S),…_READ_TIMEOUT(PT30S). - Databricks:
ACCESSFLOW_PROXY_ENGINES_DATABRICKS_CONNECT_TIMEOUT(PT10S),…_WAIT_TIMEOUT(PT10S),…_POLL_INTERVAL(PT1S),…_RESULT_DISPOSITION(auto),…_MAX_RESULT_BYTES(52428800).
Custom JDBC drivers
What it is. The escape hatch for any JDBC database not in the connector catalog. Upload its driver JAR and datasources can bind to it by choosing Database type → Custom in the create wizard.
Configure it. Manage uploads at /admin/drivers:
/admin/drivers — manages the per-org JDBC driver registry.- Upload driver. Click Upload driver, attach the JAR, and supply the vendor name, target database type, the driver class identifier shown in the vendor's documentation, and the expected SHA-256 checksum.
- Verification. The checksum is re-verified every time a pool initializes; uploaded drivers are loaded in isolated runtimes to keep providers from interfering with each other.
- On-disk cache. JARs cache to
ACCESSFLOW_DRIVER_CACHE(default~/.accessflow/drivers). Mount it as a persistent volume in Kubernetes so pods don't re-download on restart, and setACCESSFLOW_DRIVERS_OFFLINE=truefor air-gapped installs.
Datasource health
What it is. An operational dashboard for spotting a datasource in trouble — pool exhaustion, a database that has turned slow, or a sudden volume spike. Use it as the first place to look when queries start failing or hanging.
Configure it. Nothing to set up. /admin/datasource-health
(admin-only, read-only) auto-refreshes every 30 seconds; each card shows live
connection-pool utilisation (active / idle / free against the configured maximum) plus a
trailing 24-hour summary of query volume, p50 / p95 execution latency, and error count.
Pool gauges read "pool not initialised" until the datasource's first query runs (pools are
created on demand).
Tune it. ACCESSFLOW_PROXY_HEALTH_CACHE_TTL caches each
snapshot so the auto-refresh stays cheap (default PT30S).
/admin/datasource-health — one card per datasource, auto-refreshing every 30 s.