Frequently asked questions
Find answers to the most common questions about the PDQ data platform.
Platform architecture and core concepts
Q: What is the PDQ platform, and how do the core modules fit together?
A: PDQ by Simplitics is a unified data management platform that combines automation with end-to-end active metadata management. Seven modules cover the flow from source to delivery:
- AME (Active Metadata Engine): The central repository holding all system logic, historical operations and planned schedules. Every other module communicates through the AME APIs.
- INGEST (Data Importer): A containerised agent that extracts data from SQL databases, REST APIs and file systems on a schedule, writing raw output to the Landing zone.
- DLS (Data Lake Service): An event-driven worker that detects new files in Landing, archives them in Raw Archive, standardises them into Trusted and publishes them for SQL access.
- DWA (Data Warehouse Automation): A batch- and event-triggered engine that automates data modelling, SQL code generation and ELT push-down loading in the target database.
- QPI (Quality Performance Indicator): The framework's data quality engine, running SQL controls against data in DWA. It reports deviations; it does not block loading.
- DataOps Console: The operator-facing interface for monitoring daily execution, slicing runtime statistics and resolving failures.
- Config UI: The developer interface, backed by Git integration, for managing metadata definitions and CI/CD deployment.
See Architecture for the full picture, and the INGEST, DLS and DWA pages for the layer-by-layer detail.
Q: What does "Design as Documentation" mean, and how does it prevent code drift?
A: Design as Documentation is the founding principle that your design is the documentation — and that documentation is what generates the executable code. Because tables, transformation rules and loading scripts are generated from active metadata, the documentation cannot fall out of sync with what actually runs. There is no separate artefact to maintain, and therefore no code drift over time.
Q: What does PDQ actually create in my target database, besides tables and rows?
A: The generation run emits the governance along with the structure:
- Column and table comments, inside the
CREATE TABLEon Snowflake and Databricks, taken from the descriptions in the data contract and the model. - Primary keys on publish tables, unique constraints from business keys on core tables, and foreign keys from relationship metadata. All three default to on.
- Category tags from
fieldDomainand sensitivity tags from thesensitiveflag, using each platform's own tagging facility —SET TAGSon Databricks, which emits no mask. - Query tags on every statement, so each query in the target's history names the job, step and run that issued it.
Nothing on that list is a separate step you run afterwards. See What lands in the target environment.
Q: Do I have to classify sensitive columns again in the target?
A: No. Classification is declared once on the sourcefile field — sensitive and fieldDomain — and the generator applies it to the target columns as part of building the tables. On hierarchical sources, the domain tags of business keys inherited from ancestor levels are also applied to the normalised child tables that carry those keys, so a column classified once stays classified everywhere the platform copies it.
Two caveats worth knowing: Databricks emits no enforced UNIQUE, PK or FK, so uniqueness there has to be a QPI quality check; and the tag names come from TAG_NAME, SENSITIVE_TAG_NAME and SENSITIVE_TAG_VALUE, which are worth setting to your own scheme before the first generation run.
Q: Can I get PDQ's metadata and lineage into my own catalogue?
A: Yes, by two routes.
- The REST API. The AME v3 API is the same interface the console and the generators read — configuration, data contracts, schedules, logs, audit records and lineage.
/api/v3.2covers object-level generation, and the SQL Generator API returns the generated DDL itself, so definitions can be diffed in CI before deployment. - OpenLineage. Every execution emits start and end events; QPI quality checks emit parent-job events; attribute-level lineage is available as a detailed option. Lineage therefore lands in whatever catalogue or observability platform you already run, without a separate ingestion pipeline of its own.
Q: How does PDQ avoid vendor lock-in across clouds and databases?
A: All modules are packaged as standalone Docker containers (Python 3.12 on Debian 12), so the platform runs on Azure, AWS, Cleura or on-premises without change.
Data can also be added, modified or discontinued at any architectural layer — Landing, Raw Archive, Trusted, Published, Integrated or Business. That loose coupling means you can bypass a component or substitute a preferred storage or database technology without breaking the rest of the ecosystem.
Installation and configuration
Q: What are the minimum requirements to run the PDQ platform?
A: The platform runs as Docker containers on a Linux host — Ubuntu 22.04 is the reference platform — with network access to the source and target systems.
| Minimum | Recommended | |
|---|---|---|
| Docker Engine | 20.10+ | 24.0+ |
| RAM | 8 GB | 16 GB |
| CPU | 4 cores | 8 cores |
| Storage | 100 GB | 500 GB |
| Database | Shared | Dedicated instance (SQL Server, PostgreSQL or another supported engine) |
The sizing table is maintained in Installing and securing the platform — treat that page as the source of truth.
Q: How do I configure the FQDN for Caddy?
A: For Caddy configuration:
- Ensure your server has a Fully Qualified Domain Name (FQDN)
- Open port 443 in the firewall (internal only, not public)
- Create a new Docker network (e.g.,
xx_caddynet) - Remove all mapped ports from
ame-compose.ymlfor security - Configure Azure AD App Registration for authentication
Q: Why can't I access the developer interface?
A: Check the following:
- Docker containers running:
docker ps -a- all should be "Up" - Network configuration: Verify ports are correctly mapped
- Caddy status: Check the Caddy configuration
- Firewall settings: Ensure correct ports are open
- DNS resolution: Verify FQDN works correctly
Data ingestion (INGEST)
Q: How do I add a new data source?
A: An ingestion flow can be configured through the Config UI or programmatically through the API.
Via the Config UI:
- Open the Ingest page and click Add new system (a unique, single-word system name plus a description)
- Select the connection type — DB or API
- For DB: provide database name, alias, server address, port, user and password
- For API: provide the API URL, auth method (OAuth 2.0, TOKEN or NONE), token URL and any headers or connection properties
- Configure the Source Export: the
fromClause(DB) orapiPath(API), output format (JSONorCSV), output file name, suffix and schedule cycle - Test the connection and verify the first delivery
Via code:
- Connection — post a connection JSON to
/api/v3/ingest/connection/for/:sourceSystem. Database passwords must first be encrypted through the AME encryption API. - Export definition — post an export instruction JSON containing
fromClause/apiPath,alias,suffix,inclColumns/exclColumns,whereClause,scheduleCycleandenableCDCto/api/v3.1/ingest/definition/for/:sourceSystem/:alias - Agent — start an INGEST container with the target
-n :sourceSystem
Full parameter reference: Ingest.
Q: How does Change Data Capture (CDC) work in INGEST?
A: With enableCDC = 1, INGEST compares each exported row against the previously exported state and writes only new, changed or deleted records.
CDC compares each run against a baseline the agent keeps locally, in the file <alias>_latestversion.txt inside its own container. The baseline survives a restart, which is what the file is for. It does not survive the container being destroyed or removed: the next run then exports everything once and builds a new baseline.
Combining enableCDC = 1 with dynamic filtering in the whereClause will report rows the filter excluded as deletes. Use one or the other.
Q: Which dynamic timestamp placeholders can I use?
A: Placeholders can be used in the filename suffix and in query filters:
| Placeholder | Meaning |
|---|---|
[__SCHEDULEDTIME__] | The planned schedule timestamp. A late-running job still extracts the window it was meant to, not the window it happened to run in. |
[__EXECUTIONTIME__] | The actual timestamp when the export started. |
[__LASTEXECUTION__] | The timestamp of the previous export — the basis for incremental queries such as WHERE ModifyDate > '[__LASTEXECUTION__]'. |
Relative offsets such as [__SCHEDULEDTIME_MINUS_7_DAYS__] are supported for rolling-window extraction. In the output filename suffix the same values are written as [__SCHEDULEDTIME__], [__EXECUTIONTIME__] and [__LASTEXECUTION__].
Q: What does the red "missing expected files" banner mean?
A: A red banner indicates:
- Expected file types haven't been delivered according to schedule
- INGEST agents have failed or haven't run
- Source systems may be unavailable or have changed structure
- Check INGEST Tasks view for specific error messages
Q: How do I configure INGEST for Microsoft Teams and SharePoint?
A: Files stored in a Teams channel live in the underlying SharePoint document library, so both are ingested through the Microsoft Graph API:
- Azure AD setup — register an application in Azure AD/Entra, note the
Application (client) IDandDirectory (tenant) ID, and generate a client secret - API permissions — grant the Microsoft Graph application permissions (
Sites.Read.SelectedorSites.Read.All, plusFiles.Read.All) and have an Azure AD administrator grant admin consent - Connection — post an API connection to
/api/v3/ingest/connection/for/<SOURCESYSTEMNAME>withAPIUrl: "https://graph.microsoft.com/v1.0",APIAuthMethod: "OAuth 2.0",APITokenUrl: "https://login.microsoft.com/<TENANT_ID>/oauth2/v2.0/token"and the client credentials inAPIConnectionProperties - Export definition — point at the Graph item path, for example
/sites/{site-id}/drives/{drive-id}/items/{item-id}/children, with a filter such aslastModifiedDateTime=[__SCHEDULEDTIME_MINUS_3_DAYS__] - Container — create a
teams.yamlorsharepoint.yamlsettingsourcetypetoteamsorsharepoint, and run thesimplitics1/simpleingestioncontainer
Store client secrets encrypted through the AME encryption API — never in plain text in a configuration file.
Data Lake Service (DLS)
Q: What do the different zones in DLS Trace mean?
A: DLS zones represent:
- Landing: First landing for raw data from INGEST
- Raw Archive: Permanent archival for audit and replay
- Trusted: Standardised format. Deviations against the data contract are recorded here, not blocked
- Published: Table format optimised for SQL access
- Profile: Extended metadata and data profiling statistics
Q: Why are counts different between zones?
A: Nothing is filtered out between the zones, so a shortfall means something is stuck rather than discarded — see What the platform records, and what it stops.
The two exceptions are deduplication between Landing and Raw Archive, and the timing gap between Trusted and Published, where a delivery can simply not have been published yet when the window closes.
Landing, Raw Archive, Trusted and Profile should match. The Published count may sit slightly lower; a difference of up to 10–20 records is normal and not by itself a fault. Anything beyond that is worth investigating.
Q: How do I enable streaming for large XML/JSON files?
A: A large or deeply nested XML or JSON file that is loaded into memory in one piece can exhaust the server's RAM and crash the processing worker. Streaming solves this:
- Identify the hierarchical level at which records should be split — typically the repeating element, such as
<company>or<order> - Set
"useForSplittingRecords": 1on that level in thefileStructuremetadata - Use the API — this setting is not available in the Config UI and must be updated through the backend, for example
/api/v3.2/sourcefiles/:sourceFilename
DLS then streams the file chunk by chunk from that level downwards, omitting the parent elements above the split point and emitting multiple flatter records. Memory consumption drops sharply and throughput increases. The DWA SQL generator detects the flag automatically and simplifies the downstream staging queries accordingly.
Q: What are Hierarchical IDs (_HID) and granular checksums?
A: Both are generated by the Data Modifier for every element in a nested document, and both exist to make nested data traceable:
_HIDis a dot-delimited path such as1.2.4.1that records an element's exact position in the source document. It lets you trace any database row back to where it came from, and it acts as a stable internal join key when nested arrays are split into separate tables — so joins do not depend on business keys being present.- Granular checksums are calculated at every object level rather than only at the document root. This makes change detection precise: an edit to one child record does not mark the whole parent file as changed.
Data warehouse automation (DWA)
Q: What is the difference between the Ensemble model and the CORE model?
A: DWA uses a two-layered modelling architecture inside the Integrated layer:
| Model | Purpose | Shape |
|---|---|---|
| Ensemble / Intermediate | Agile, highly normalised integration based on Anchor Modelling or typed Data Vault patterns | Every attribute and relationship in its own physical table, fully historised, with direct traceability to __fileKey |
| CORE / Enterprise | A physical, consolidated copy of the ensemble views representing the integrated enterprise model | One row per business key — the latest version — for fast BI and downstream reporting |
The Ensemble model answers "what did we know, and when?". The CORE model answers "what is true now?" quickly. See Data Warehouse Automation (DWA).
Q: What CORE loading patterns are available?
A: Five patterns control how the CORE/Enterprise model is populated:
| Pattern | Behaviour |
|---|---|
none | Leaves the data as a consolidated view without materialising a physical table (default) |
full | Truncates and reloads all data on every run |
dayspan | Rolling window — captures changes from a configurable timeframe, for example the last 7 days |
incremental | Captures only the latest changes, based on the execution schedule interval |
transaction | Appends new records only, without performing updates |
Q: What's required for DWA to generate SQL?
A: Every mapping group — the unit of work — must contain at least one business key mapping and one attribute mapping. If the key mapping is missing, DWA generates no SQL and no data loads. The Mappings page flags an unmapped business key and offers to map it on the spot. This is one of several silent failure modes. Per load type:
- Object load: complete key mapping
- Attribute load: key mapping + attribute mapping
- Relationship load: two complete keys from the same source
- All mappings must sit within the same mapping group, which also shares source file, filter conditions and sort orders
Q: How are relationships created?
A: Relationships are defined in the target model, never in the individual mapping program. When one source file populates the complete business keys of two related model objects within the same mapping group, DWA generates and runs the relationship load by itself.
The condition is that the complete business key is populated for both objects. If it is not, the load still creates a relationship — but against a false business key, which means no related data. See Silent failure modes.
Q: How do I restart failed DWA tasks?
A: To restart tasks:
- Identify failed tasks (red in list/Gantt chart)
- Mark relevant tasks for specific schedule and source file
- Use "Restart Tasks" from DataOps console
- Monitor progress to ensure successful restart
Q: Why are unnecessary DWA schedules created?
A: This can happen when:
- On file arrival tasks are created without Core model dependencies
- Solution: Stop schedule via API:
POST /api/v3/master/schedule/{sourcefile} - Set ValidTo date to deactivate the schedule
DataOps and monitoring
Q: What are the daily monitoring routines?
A: Six checks, in this order: System Health, INGEST Tasks, DLS Trace, DWA Loading Tasks, DWA Additional Tasks, QPI Monitor.
The full routine — what each check is looking for, and what to do when one fails — is documented once, in The daily pass.
The thing worth knowing up front: each console page carries its own checklist comparing configured against executed. That is the fastest way to spot work that never started at all, which a "no failures" metric will never reveal.
Q: How do I use the DataOps Console effectively?
A: DataOps Console offers:
- Dashboard View: High-level status across all components
- Runtime Statistics: Detailed performance and trends
- Task Management: Restart of failed processes
- Alert Management: Centralised notification handling
A page reporting "no data for the selected time range" is usually answering correctly for a window that is too narrow — widen the global date range before assuming a failure.
Troubleshooting
Q: What do I do if the entire system is down?
A: First steps:
- Stop and start VM from cloud portal (most common solution)
- If problem persists: SSH to server
- Check disk:
df -h— a full/datadriveblocks everything - Check Docker:
docker ps -a(all should be "Up") - Manual restart: from
/datadrive/configs, rundocker compose downfollowed bydocker compose up -dfor each component in strict order: UI → AME → DLS → DWA → INGEST
The order matters: each service depends on the one before it, and AME must be up before DLS, DWA and INGEST can fetch their instructions.
Q: How do I handle a degraded System Health status?
A: When System Health reports System degraded:
- Identify specific failed tasks/files
- Restart the failed components
- Check logs for root cause
- Document incident for future reference
Q: How do I recover interrupted pipelines after a crash?
A: Work forward through the layers, restarting only the step that did not complete:
- Find what never finished — open the Job Log in System Health and filter on
status = INITto find processes that started but never reported completion. Check the target tables using the__fileKeyto see whether data was partially loaded. - Inspect DLS Trace — filter on
lastSeenOn:- Stuck in
raw: select the rows, open the admin console and click Load Trusted - Stuck in
landing: check the file size first. A very large file that caused an out-of-memory crash needs to be split into smaller subsets before you re-run Load Raw. - Missing Trusted load (
trustedNumberOfFiles = 0): filter on that count or on the specificfileKeyand click Load Trusted
- Stuck in
- Restart the containers if they are unresponsive — see the startup order above.
Q: How do I resolve a Published load failure caused by JSON line length in Azure Synapse?
A: Symptom: the Published load fails with an error referencing a 500,000-character limit in the Synapse OPENROWSET function.
Cause: large nested JSON extractions accumulate _hid and _checksum attributes across deep hierarchies, which can push a single JSON line past Synapse's hard line-length limit.
Resolution:
- Open Azure Storage Explorer and navigate to
trusted/temporary_files/<System>/<SourceFile> - Download the
.json.gzfiles created since the last successful load - Run the local cleanup script to split the over-length JSON lines
- Upload the cleaned files back to the storage root folder, overwriting the original filenames
- In the DataOps Console, click Restart on the failed Published job
Q: What does "Metadata Case Mismatch" mean?
A: Target database engines — Snowflake, Databricks, SQL Server, Azure Synapse, Fabric and PostgreSQL — evaluate fieldKey against the JSON/XML document path using SQL JSON operators that require an exact case match. If fieldKey or path differs from the source by a single character, the operator finds nothing and the column returns NULL.
- Keep
fieldKeyandpathmatched to the source exactly — they are case-sensitive - To apply a different naming convention in the target tables (uppercase,
f_prefixes and so on), usefieldAliasandlevelAliastogether with the global settingscolumnPrefixandtableNameCasing
Performance and scaling
Q: How do I scale the PDQ platform for larger data volumes?
A: For scaling:
- DLS: Minimum 6 containers, scale workers based on capacity
- DWA: Minimum 4 containers, place near database
- INGEST: Distribute agents close to data sources
- Consider separate regions for different components
Q: What are performance best practices?
A: Performance recommendations:
- Place AME and DLS in same region as repository database
- Use streaming for large XML/JSON files
- Optimise network latency between components
- Monitor resource usage regularly via DataOps Console