Data Quality and Observability: DQDL, Quarantine, Lineage, and SLOs
Turn the finished lakehouse into a system that can prove its own numbers: AWS Glue Data Quality end to end (DQDL's 32 rule types, expressions, where clauses, dynamic rules, labels and constants; rulesets bound to one table; the Data Catalog task versus EvaluateDataQuality inside a Glue job; results to S3, the service store and Iceberg tables in the Data Catalog; CloudWatch metrics and EventBridge events plus the checkbox they secretly depend on), Deequ/PyDeequ and DataBrew only on AWS-verified paths, the quarantine pattern with warn/quarantine/halt actions and Write-Audit-Publish on Iceberg branches, a three-clock freshness SLO with worked P95 arithmetic, per-layer CloudWatch logs metrics alarms and dashboards with freshness volume and schema-drift alarms, scoped CloudTrail data-event selectors with a cost warning, the DataZone business catalog and OpenLineage-compatible lineage against the Glue catalog and the CloudTrail audit plane, a fully worked EventBridge to SNS to Lambda remediation wiring, the Siemens and Vanguard cases, a sourced October 2026 update box, four interactive checks, five diagrams, six worked examples and eleven exam-style questions.
Lessons 01 to 14 built something that works: a bronze prefix, Iceberg tables, Glue jobs, a catalog, Athena and QuickSight, orchestration, Lake Formation grants, PII controls and a hardened perimeter. None of that answers the question your consumers will actually ask: can you prove today's gold numbers are right, and can you tell me what changed when they are not? A lakehouse without quality and observability is a very fast way to move wrong data.
This lesson builds the proof layer in the order AWS's own guidance lays it out: engines β rules β runtimes β results and signals β quarantine β SLOs β per-layer observability β audit β lineage β remediation. Two ideas carry the whole thing. First, quality is a pipeline step with an output, not a dashboard: it either gates a write, splits rows, or raises a signal someone is paged for. Second, observability has three distinct planes that teams constantly confuse: the Glue catalog holds technical metadata and permissions, DataZone holds business meaning and lineage, CloudTrail holds who called what. Every number below is dated to October 2026.
====================================================================
QUALITY + OBSERVABILITY SPINE - AWS DATA LAKEHOUSE, LESSON 15
====================================================================
ENGINES ...... Glue Data Quality (DQDL, managed, built on Deequ)
- Data Catalog task OR EvaluateDataQuality in job
Deequ / PyDeequ - OSS, custom analyzers, exotic runtimes
DataBrew - no-code check expressions in profile jobs
custom checks - Lambda / Glue / EMR of choice
RULES ........ Rules = [ ... ] bound to ONE table; 32 rule types;
expressions, where clauses, dynamic rules
(RowCount > avg(last(10))), labels (team=finance),
constants, pre-processing SQL; 2,000 rules and
65 KB per ruleset; no nested or list-type columns
RUNTIMES ..... Catalog task: recommendations (BASIC; ADVANCED via
Bedrock Sep 2026), pushdown partition predicates,
min 2 DPUs - but it CANNOT name failed records
ETL job: row-level DataQualityRules* columns, auto
scaling, Flex, bookmark-driven incremental - but
no recommendation runs
RESULTS ...... S3 JSON -> external table + crawler -> Athena;
service store -> console + Get/List APIs;
Iceberg tables in the Data Catalog (Jul 2026),
partitioned by catalog_id/database/table/day
SIGNALS ...... EventBridge "Data Quality Evaluation Results
Available" (source aws.glue-dataquality) carrying
score, state, rulesSucceeded/Failed/Skipped -
which only works while "Publish metrics to
Amazon CloudWatch" stays SELECTED
CloudWatch namespace Glue Data Quality:
glue.data.quality.rules.passed / .failed
QUARANTINE ... warn | quarantine | halt (InsuranceLake's three
actions); row-level outcomes ONLY; ratio and
threshold rules cannot quarantine; WAP = Iceberg
staging branch -> audit -> fast_forward to main
SLO .......... P95(availability - event) <= target across three
clocks: event time / landing time / availability
time; encode as DataFreshness + FileFreshness
+ RowCount volume alarms
OBSERVABILITY one CloudWatch dashboard per medallion layer;
Glue log groups /aws-glue/jobs/output, /error,
/logs-v2 (continuous logging, Logs IA redirect);
Athena AWS/Athena metrics; X-Ray for Lambda and
Step Functions - NOT for Spark executors
AUDIT ........ CloudTrail management events (first copy free)
vs S3 data events ($0.10 per 100,000, opt-in,
scoped by advanced event selectors, no wildcards)
PLANES ....... Glue Data Catalog = technical metadata + permissions
(Lake Formation grants; registration is NOT lineage)
Amazon DataZone = business catalog + lineage
(OpenLineage-compatible; Glue/Redshift auto-capture)
CloudTrail = access audit (who called, not what a
column was derived from)
Prices ....... Glue DQ worked examples at $0.44 per DPU-hour (a
second line on the same page reads $0.22 - see E2);
Flex $0.29; 2-DPU minimum, 1-minute billing;
anomaly detection 1 DPU per statistic;
EventBridge $1.00 per million custom events;
CloudWatch custom metric $0.30 per metric-month
[us-east-1 list, as of Oct 2026]
====================================================================In this lesson you will:
- pick a quality engine from AWS's own Prescriptive Guidance mapping, Glue Data Quality, DataBrew, Deequ or custom code, and defend the default;
- author DQDL: rulesets bound to one table, the four modifiers (expressions,
where, dynamic rules, labels), the 32 rule types, and which of them can ever produce a failing row; - run evaluation on both runtimes, the Data Catalog task and
EvaluateDataQualityinside a Glue job, and know exactly what each one can and cannot do; - land results in three sinks: S3 JSON, the Glue service store, and Iceberg tables in the Data Catalog, then read them in Athena;
- wire the signals: EventBridge events, CloudWatch metrics in the
Glue Data Qualitynamespace, alarms, dashboards: including the checkbox that silently kills alerts; - build the quarantine pattern end to end: split rows, write the quarantine prefix and table, emit the metric, alert, and run a backfill that stays idempotent;
- compute a freshness SLO across three clocks and defend a P95 number with real arithmetic;
- scope CloudTrail data events per sensitive prefix so the audit trail does not become the biggest line on the bill;
- separate the three planes, Glue catalog, DataZone business catalog and lineage, CloudTrail audit, and name only the lineage sources AWS actually documents;
- wire a working EventBridge β SNS β Lambda remediation path with IAM that fits;
- study two AWS customer cases, a sourced October 2026 update box, eleven exam-style questions and four interactive checks.
1. Four quality engines, one decision
1.1 AWS's own mapping
AWS Prescriptive Guidance publishes a solution-mapping table for data-quality checks. It is the fastest correct answer to "which tool":
Need | Engine | Where it runs | Verified scope |
|---|---|---|---|
No-code column-level or table-level conditions for analysts | AWS Glue DataBrew | Profile job + ruleset | Own CheckExpression syntax, not DQDL; validation report next to the profile |
Custom code in a Glue job or no-code rules on catalog tables | AWS Glue Data Quality | Catalog task or ETL transform | DQDL, managed, serverless, "built on top of the open-source DeeQu framework" |
Bespoke checks in your own ETL of choice | Custom checks | Lambda, Glue, EMR | Whatever you write |
Metrics report, constraint validation and constraint suggestions | Deequ (open source) | EMR, SageMaker, Glue with the right JAR | CompletenessConstraint-style analyzers, metrics repository, anomaly checks |
(AWS Prescriptive Guidance: Data quality checks, accessed 2026-10-11.)
For this course's lakehouse the answer is Glue Data Quality by default, DataBrew kept for business-analyst profiling of CSV/JSON datasets, and PyDeequ only where Deequ-level customization is genuinely needed. That is not a popularity contest: Glue DQ evaluates "tables and supported Amazon Athena views that are stored in the AWS Glue Data Catalog" over "Amazon S3, Amazon Redshift, JDBC sources compatible with the Data Catalog, and transactional data lake formats such as Apache Iceberg, Apache Hudi, and Delta Lake"; that is exactly the substrate you built in Lessons 02 to 08 (Glue Developer Guide, accessed 2026-10-11).
- π Did you know? Glue Data Quality is not a re-implementation: AWS states it is "built on top of the open-source DeeQu framework" and that there is "No lock-inβ¦ allowing you to keep the rules you are authoring in an open language" (Glue Developer Guide, accessed 2026-10-11). That is why a DQDL rule reads like Deequ's constraint vocabulary, and why the open-source library is still a legitimate escape hatch for analyzers the product does not expose.
1.2 What "managed" buys, in one table
Glue Data Quality | Deequ / PyDeequ | DataBrew rules | Great Expectations (DIY) | |
|---|---|---|---|---|
Authoring | DQDL, console, API, Studio node | Scala/Python constraints | No-code check expressions | Python expectation suites |
Managed run | Yes, serverless tasks and ETL integration | No, you run Spark | Yes, inside profile jobs | No, you host it |
Rule recommendations | Yes (BASIC; ADVANCED via Bedrock, Sep 2026) | Constraint suggestion in code | Recommendations tab in console | No |
Row-level failing records | Yes, ETL path only | Yes, if you build the split | No: findings are aggregate | Yes, if you build it |
Alerts | EventBridge + CloudWatch, built in | You build SNS publishing | EventBridge event per validated ruleset | You build it |
AWS support posture | First-party product | Open source (community/AWS samples) | First-party product | Not an AWS offering |
The last row matters. Great Expectations appears in this lesson only as a bring-your-own framework: AWS publishes no managed GX offering, GX's own legacy docs pin the AWS Glue integration to "the most recent version of GX that supports this integration is GX 0.15.50", and the Glue Catalog connector walkthrough is a partner-authored post. Treat GX as your runner to version, patch and support.
2. DQDL: the language, the modifiers, the ruleset contract
2.1 The contract: Rules = [ ... ] bound to one table
A ruleset is a bracketed, comma-separated list bound to exactly one catalog table:
Ruleset = 'Rules = [IsComplete "col1", IsPrimaryKey "col2", RowCount between 2000 and 8000]'
TargetTable = {'TableName': 'orders', 'DatabaseName': 'lakehouse_silver'}That is the shape of create_data_quality_ruleset (Glue Developer Guide: Using APIs to measure and manage data quality, accessed 2026-10-11). Two limits define how big a single ruleset can get: 2,000 rules per ruleset ("If your rulesets are larger, we recommend splitting into multiple rulesets") and a 65 KB ruleset size. Statistics are a separate budget: 100,000 statistics per account, stored free for 2 years (Glue pricing, accessed 2026-10-11).
One language-level exclusion to memorize: "DQDL doesn't currently support nested or list-type column data." Nested structs and arrays are out of scope for direct rule targets: flatten or use CustomSql against a pre-processed view.
2.2 The four modifiers that change semantics
Modifier | Since | What it does | Example |
|---|---|---|---|
Expressions | GA 2023-05-24 (product) | Thresholds, ranges, enums, regex, with threshold | Uniqueness "email" = 1.0, ColumnValues "currency" matches "^[A-Z]{3}$" with threshold > 0.999 |
where clause | 2024-06-26 | Filter rows before the rule evaluates | Completeness "First_Name" > 0.95 where "weight > 10" |
Dynamic rules | preview 2023-11-27, GA 2024-08-07 | Thresholds computed from run history | RowCount > avg(last(10)), Completeness "colA" between min(last(5)) - 1 and max(last(5)) + 1 |
Labels | GA 2025-11-21 | Key-value tags authored in DQDL for slicing results by owner | label team=finance, queryable in rule outcomes, row-level results and API responses |
(Glue DQ release notes and AWS What's New, 2025-11-25, accessed 2026-10-11.)
Three supporting features round it out: constants (2025-11-21) let you define a value once and reference it throughout a script, "preventing issues related to query size limits"; pre-processing queries (GA 2025-11-25) run SparkSQL against the table before evaluation via the PreProcessingQuery field of DataQualityGlueTable, so you can derive columns, filter or validate cross-column relationships; and complex composite rules (2024-11-22) let you nest AND/OR groups instead of stacking flat rules.
Note the trap in the with threshold column: that clause works only with ColumnValues, ColumnDataType and CustomSql. Put it on Completeness and you are writing a rule DQDL will not evaluate the way you think.
2.3 The 32 rule types, and the ones that can name a row
The DQDL rule-type reference lists exactly 32 rule types as of October 2026: AggregateMatch, AllStatistics, ColumnCorrelation, ColumnCount, ColumnDataType, ColumnExists, ColumnLength, ColumnNamesMatchPattern, ColumnValues, Completeness, CustomSql, DataFreshness, DatasetMatch, DistinctValuesCount, Distribution, Entropy, IsComplete, IsPrimaryKey, IsUnique, Mean, ReferentialIntegrity, RowCount, RowCountMatch, SchemaMatch, StandardDeviation, Sum, Uniqueness, UniqueValueRatio, DetectAnomalies, FileFreshness, FileMatch, FileUniqueness and FileSize.
They sort into four families:
ROW-LEVEL (can name the offending record) ..... Completeness, IsComplete,
IsUnique, IsPrimaryKey, Uniqueness, UniqueValueRatio, ColumnValues,
ColumnLength, ColumnDataType, ColumnExists, DetectAnomalies,
DataFreshness, CustomSql WITH a row-level threshold, File* (no)
DATASET-LEVEL (aggregate only - NEVER a failing row) .. RowCount,
RowCountMatch, Mean, StandardDeviation, Sum, ColumnCount,
ColumnExists, ColumnNamesMatchPattern, ColumnCorrelation,
CustomSql (overall form), AggregateMatch
CROSS-DATASET .................................. ReferentialIntegrity,
DatasetMatch, AggregateMatch, SchemaMatch
ANALYZERS / FILE-CENTRIC ........................ AllStatistics,
Distribution, FileFreshness, FileMatch, FileUniqueness, FileSizeAWS's own Studio documentation is blunt about the split: dataset-level rules "cannot identify error records because they're applied at the dataset level" (Glue Developer Guide: Evaluating data quality with AWS Glue Studio, accessed 2026-10-11). Build a quarantine on RowCount and you will quarantine nothing.
2.4 Worked example E1: the silver orders ruleset
Target: lakehouse_silver.orders, an Iceberg table partitioned by dt, evaluated inside the bronzeβsilver job and re-checked daily as a catalog task.
Worked example E1 - DQDL ruleset for lakehouse_silver.orders, as of Oct 2026
Rules = [
ColumnExists "order_id",
ColumnExists "customer_id",
ColumnExists "order_ts",
IsPrimaryKey "order_id", -- not-null + unique in one rule
Completeness "customer_id" > 0.999, -- row-level, quarantinable
Completeness "order_ts" > 0.995, -- row-level, quarantinable
Uniqueness "order_id" > 0.999, -- tolerates 0.1% dupes
ColumnValues "order_status" in ["created","paid","shipped","cancelled"]
with threshold > 0.99, -- <=1% dirty enum tolerated
ColumnValues "currency" matches "^[A-Z]{3}$" with threshold > 0.999,
ColumnValues "total_amount" > 0,
RowCount > 1000, -- dead-pipeline guard
RowCount <= avg(last(10)) * 1.5, -- dynamic volume spike guard
DataFreshness "order_ts" > (now() - 2 days)
where "dt = current_date - 1", -- staleness on yesterday's partition
CustomSql "SELECT order_id FROM primary WHERE total_amount != item_sum"
]
SCORE = rules passed / rules evaluated -> 14/14 = 1.00, 13/14 = 0.929
QUARANTINE-CAPABLE RULES = the Completeness pair, the Uniqueness,
the three ColumnValues, and the row-level CustomSql
DATASET-LEVEL ONLY = both RowCount rules and DataFreshness
-> these ALERT (halt / notify), they never split rowsWhy each line earns its place: IsPrimaryKey collapses two rules into one; the threshold forms on ColumnValues tolerate sub-1% dirt without failing a whole run; RowCount <= avg(last(10)) * 1.5 is a dynamic spike guard that needs no hand-tuned constant; the row-level CustomSql is the only rule here that can hand you a list of order IDs to quarantine; and DataFreshness β¦ where "dt = current_date - 1" scopes staleness to the partition that is supposed to be fresh instead of screaming about the whole history.
AWS's getting-started walkthrough shows the same shape on its sample tickets table, IsComplete "SET_FINE_AMOUNT", Uniqueness "TICKET_NUMBER" > 0.95, ColumnValues "PROVINCE" in [...], ColumnLength "INFRACTION_DESCRIPTION" between 15 and 31, with the important caveat in the docs: recommendations "make it easy to start⦠without writing code", but the rules are generated from current statistics, so treat them as a draft to tighten (accessed 2026-10-11).
2.5 Rule recommendations: BASIC, and ADVANCED via Bedrock
start_data_quality_rule_recommendation_run(DataSource={'GlueTable': {...}}, Role=role_arn) returns a RunId; fetch ResultIds from get_data_quality_ruleset_evaluation_run, then each get_data_quality_result(ResultId=β¦). Recommendation runs are automatically deleted after 90 days. Since 2026-09-18 / 2026-09-25, an ADVANCED mode uses Amazon Bedrock with table metadata plus sampled rows to generate DQDL: a drafting assistant, not an approval.
How to answer
Fill-in-the-blank exercise with 5 blanks. Type the missing words, then check your answer.
Fill in the DQDL and Glue Data Quality keywords this lesson leans on:
A ruleset is written as blank = [ rule list ] and is bound to exactly one catalog table; a filter applied before a rule runs is the blank clause; a threshold computed from run history such as RowCount > blank(last(10)) is a blank rule; and the rule that checks not-null and uniqueness at once is blank.
Word Bank, drag words into the blanks above:
3. Two runtimes: the Data Catalog task and EvaluateDataQuality in the job
They share DQDL, EventBridge, CloudWatch metrics, S3 results and IAM actions. They are not interchangeable.
Data Catalog task ( StartDataQualityRulesetEvaluationRun) | ETL-integrated ( EvaluateDataQuality.apply / Studio node) | |
|---|---|---|
Identify which records failed | "Not supported" | "Supported": row-level outcomes |
Incremental evaluation | Pushdown / catalog partition predicates | AWS Glue job bookmarks |
Auto scaling | Not supported | Supported |
Rule recommendations | Yes (no recommendations inside ETL runs) | No |
ML anomaly detection, dynamic rules, EventBridge, CloudWatch, results to S3 | Supported | Supported |
Flex execution / auto-scaling DPU | : | Supported |
Failure action | Score + event | "Fail job after loading target data" β AssertionError |
(Glue Developer Guide: capability matrix, accessed 2026-10-11.)
3.1 Inside the job
from awsglue.transforms import EvaluateDataQuality
dq = EvaluateDataQuality.apply(
frame=orders_frame,
ruleset=ruleset, # Rules = [ ... ]
publishing_options={
"dataQualityEvaluationContext": "silver_orders", # namespace; default "default_context"
"enableDataQualityCloudWatchMetrics": True, # default False
"enableDataQualityResultsPublishing": True, # default True -> Glue Studio "Data quality" tab
"resultsS3Prefix": "s3://lakehouse-dq/results/" # optional S3 sink
})The transform's output option is the decision that matters: "Original data" versus "Add new columns to indicate data quality errors". The second appends four columns: DataQualityRulesPass, DataQualityRulesFail, DataQualityRulesSkip (string arrays) and DataQualityEvaluationResult ("Passed" or "Failed" at the row level). AWS's own note is the one to memorize: "your overall results can be FAIL, but a certain record might pass." Rules in Studio go "within the brackets and⦠separated by commas": Rules = [ Completeness "year" > 0.8, Completeness "month" > 0.8 ].
Incremental quality comes from job bookmarks: for S3 inputs the watermark is object last-modified time, with a maxBand default of 900,000 ms (15 minutes) during which the listing "is likely to be consistent". Bookmarks require job.init at the top and job.commit at the end and key off a unique transformation_ctx: omit it and the operator is not tracked at all (which is exactly how you deliberately re-read a dimension table every run).
3.2 Three result sinks, and since July 2026 the catalog is one of them
- S3 JSON results (opt-in via
resultsS3Prefixor the task's results location) β register with a crawler,CREATE EXTERNAL TABLEwith a JSON SerDe,MSCK REPAIR TABLE, then Athena. AWS ships the full DDL, partitioned byyear/month/day. - The Glue service store β readable through
GetDataQualityResult/ListDataQualityResultsand rendered in the console's Data quality tab; the Data quality snapshot shows the last 10 runs across rulesets by default. - Iceberg tables in the Data Catalog (GA 2026-07-27) β four opt-in result types, rule results, profiling results, row-level results, observation results, configured through
--additional-run-optionsonStartDataQualityRulesetEvaluationRunoradditional_optionsin an ETL job. Nothing is written by default: "You must explicitly enable each result type."
aws glue start-data-quality-ruleset-evaluation-run \
--data-source '{"GlueTable": {"DatabaseName": "lakehouse_silver","TableName": "orders"}}' \
--role arn:aws:iam::111122223333:role/glue-dq-task \
--ruleset-names '["silver_orders_strict"]' \
--additional-run-options '{
"DataQualityRuleResults": {
"WriteDataQualityRuleResultsEnabled": true,
"CatalogTableConfig": {"DatabaseName": "quality_results","TableName": "rule_results"}
}
}'
PARTITION KEYS of every results table ... catalog_id, database_name,
table_name, day(stored_on)
RULE-RESULT COLUMNS ..................... dq_result_id, rule_name,
rule_description (the DQDL expression), rule_result (PASS|FAIL),
evaluation_message, evaluated_metrics MAP<STRING,DOUBLE>,
catalog_id, database_name, table_name,
ruleset_evaluation_run_id, started_on, completed_on,
evaluated_rule, ruleset_name
ROW-LEVEL COLUMNS ....................... all source columns +
data_quality_rules_pass / _fail / _skip (ARRAY),
data_quality_evaluation_result (Passed|Failed), stored_on,
region, account_idThis is the single biggest change to this lesson's subject in 2026: your quality history becomes an ordinary Iceberg table with the same Athena, Lake Formation, time-travel and QuickSight tooling as the rest of the lakehouse.
3.3 The alerting contract has a hidden checkbox
Every completed evaluation emits an EventBridge event:
{
"detail-type": "Data Quality Evaluation Results Available",
"source": "aws.glue-dataquality",
"detail": {
"contextType": "GLUE_DATA_CATALOG",
"runId": "dqrun-9f2c...",
"databaseName": "lakehouse_silver",
"tableName": "orders",
"catalogId": "111122223333",
"resultID": "dqr-...",
"rulesetNames": ["silver_orders_strict"],
"state": "FAILED",
"score": 0.928,
"rulesSucceeded": 13,
"rulesFailed": 1,
"rulesSkipped": 0
}
}The ETL variant uses contextType: "GLUE_JOB" with jobId, jobName and evaluationContext. The parallel CloudWatch path is equally simple: "Each AWS Glue Data Quality evaluation emits a pair of metrics named glue.data.quality.rules.passed⦠and glue.data.quality.rules.failed" under the Glue Data Quality namespace, published per table and per ruleset.
Do not uncheck "Publish metrics to Amazon CloudWatch." AWS documents it flatly: for both Data Catalog and ETL-job evaluations, "the Publish metrics to Amazon CloudWatch option, which is selected by default, must remain selected for EventBridge publishing to work." Teams switch it off to trim a few custom-metric dollars and lose the CloudWatch metrics and the EventBridge events in the same click: alarms, notifications and remediation all go silent while the rules keep running and the score keeps dropping. The IAM consequence is the same switch: publishing needs cloudwatch:PutMetricData conditioned on "cloudwatch:namespace": "Glue Data Quality".
AWS's stated preference: "Our recommended approach is to set up data quality alerts using Amazon EventBridge, because Amazon EventBridge requires a one-time setup to alert customers." The CloudWatch alternative is a standard alarm: glue.data.quality.rules.failed, statistic Sum, period 1 minute, threshold β₯ 1, action = SNS topic.
- π Did you know? The score is not a quality grade you pass around: AWS defines it as "the percentage of data quality rules that passed for the input." A ruleset of one weak rule scores 1.0 while 40% of rows are junk. That is why the row-level columns and the results tables exist: score for the alert, rows for the fix (Glue Developer Guide, accessed 2026-10-11).
4. Deequ, PyDeequ and DataBrew: the verified side paths
4.1 Deequ: the engine, and where it still earns its keep
AWS's PyDeequ announcement describes the original: Deequ "is used internally at Amazon to verify the quality of many large production datasetsβ¦ The system computes data quality metrics on a regular basis (with every new version of a dataset), verifies constraints defined by dataset producers, and publishes datasets to consumers in case of success. In error cases, dataset publication can be stopped, and producers are notified to take action." Compute β verify β publish-or-stop is the loop Glue DQ productized in 2023 (AWS Big Data Blog, 2020-12-30, updated June 2024).
Remaining legitimate reasons to run Deequ yourself, per AWS's own posts:
Reason | AWS-documented vehicle | Hard constraint |
|---|---|---|
Custom analyzers/constraints DQDL cannot express | PyDeequ on EMR | The Deequ JAR must match your Spark version: pydeequ.deequ_maven_coord β com.amazon.deequ:deequ:2.0.3-spark-3.3; "It is critical to provide the correct Apache Maven coordinates" |
Migration validation: source vs target metric diff | EMR job writes results to a Glue table (e.g. db_deequ.db_migration_validation_result), audited in Athena | Same JAR/Spark pinning |
Very small datasets needing fast startup | Deequ on AWS Lambda with the Spark Runtime for AWS Lambda (SoAL) | SoAL "is larger than the 250 MB limit for zip archive packaging" β container image; AWS recommends Glue Data Quality "for Spark jobs that run longer than 15 minutes or larger files (more than 1 GB)" |
Anomaly checks over a metrics repository | FileSystemMetricsRepository + Holt-Winters, results to SNS | The published Holt-Winters recipe was verified "in AWS Glue 2.0, pydeequ release/1_1_1, Spark 2.4 and deequ-1.1.0_spark-2.4-scala-2.11.jar (only)": version-pin or don't use it |
PyDeequ "supports Spark up to version 3.3.0" as of the PyDeequ 1.1.x notes AWS links, and "can function within Amazon EMR and SageMaker." The honest 2026 summary: most of Deequ's value is already inside Glue Data Quality; reach for the library when the runtime is exotic or the constraint is not expressible.
4.2 DataBrew: rules, but not DQDL
DataBrew's quality story is a ruleset of CheckExpressions evaluated inside a profile job: "If any of a rule's criteria isn't met, the ruleset as a whole fails validation", and the run "produces a validation report in addition to the data profile" at the same location. API shape: CheckExpression, ColumnSelectors (Name or Regex), Disabled, SubstitutionMap, Threshold (Type/Unit/Value). Constraints to respect: only columns of simple types (string, numbers, boolean) participate, and rules live on the Recommendations tab of the ruleset screen.
On completion, "DataBrew sends an Amazon CloudWatch event with the details of that job run⦠for each validated ruleset⦠and the event contains its result (SUCCEEDED, FAILED, or ERROR)" plus a link to the report. That event is your EventBridge pattern for analyst-owned checks. Metrics live in the AWS/DataBrew namespace: the documented example is SessionCount.
DataBrew rules are not DQDL. The check-expression syntax, the console, the events, the report and the failure semantics are all different from Glue Data Quality. Copying Completeness "col" > 0.95 into a DataBrew ruleset, or a DataBrew CheckExpression into a Glue ruleset, fails in both directions. AWS Prescriptive Guidance keeps them separate for a reason: DataBrew for no-code analyst checks, Glue Data Quality for job-embedded and catalog-embedded DQDL, Deequ for a metrics/constraint report, custom code for everything else.
4.3 Worked example E2: what a quality gate costs, and where to put it
AWS's own benchmark: 104 columns, 1 million rows, 10 DPUs (AWS Big Data Blog, 2024-03-12):
Worked example E2 - DQ run cost and placement, us-east-1 list, as of Oct 2026
BENCHMARK (104 cols, 1M rows, 10 DPUs)
0 rules ......... 53.2 s ... 0.15 DPU-hr .... $0.06
10 rules ........ ~150 s ... 0.42 DPU-hr .... $0.18
100 rules ....... -- ....... 0.52 DPU-hr .... $0.23
400 rules ....... 445.7 s .. 1.24 DPU-hr .... $0.54
-> cost is dominated by SCANNING the table, not rule count:
"cost⦠only starts to increase after evaluating rulesets with
more than 100 rules, remaining constant below that number."
PLACEMENT, same 20-rule silver ruleset (30 runs/month)
A. Standalone catalog task, 5 DPUs x 20 min
5 x (20/60) x $0.44 ............................. $0.73 / run
$0.73 x 30 ..................................... $21.90 / month
at the Flex rate $0.29 .......................... $0.48 / run
B. Inside the existing silver job (6 DPUs, +5 min marginal)
6 x (5/60) x $0.44 ............................. $0.22 / run
$0.22 x 30 ..................................... $6.60 / month
AND you get row-level outcomes -> quarantine works
C. Anomaly detection add-on: 20 statistics x 1 DPU x 15/3600 h x $0.44
.............................................. +$0.037 / run
("between 10-20 seconds" per statistic on average)
D. Data profile/quality task floor .............. 2 DPUs, 1-minute minimum
PRICE CONFLICT TO CHECK BEFORE YOU BUDGET
aws.amazon.com/glue/pricing carries two DQ numbers on the same
access date (2026-10-11): the Data Quality worked examples
calculate at $0.44 per DPU-hour (recommendation task 5 DPUs x 10 min
= "$0.37"; catalog evaluation 5 DPUs x 20 min = "$0.73"; ETL DQ
6 DPUs x 20 min = "$0.88", Flex "$0.58"), while a pricing table
line reads Glue Data Quality = "$0.22 per DPU-hour".
Range for option A: $11.00 - $21.90 / month. Verify in console.Two conclusions survive the price ambiguity: budget by table size Γ frequency, not by rule count, and for a quarantine design the in-job evaluation dominates because it is cheaper and it is the only path that can name the rows.
5. Quarantine: a pattern, not a product
5.1 Three actions, two checkpoints
The AWS Solutions Library's InsuranceLake sample formalizes what every production pipeline needs: three actions at two pipeline checkpoints:
Action | Behaviour | Use when |
|---|---|---|
Warn | Log or notify only; data flows on | Cosmetic issues, early runs of a new rule |
Quarantine | Remove failing rows from the flow and write them to separate quarantine storage + a quarantine Data Catalog table | Row-level, business-meaningful defects |
Halt | Stop the pipeline / fail the job | The dataset is unusable downstream |
Checkpoints: before_transform ("immediately after schema mapping⦠before any transforms are run") and after_transform ("just before saving the data to the Cleanse bucket"). The rule that matters for your ruleset design: "The data quality expression must be written using row level outcomes to evaluate and quarantine individual rows" and "Rules that rely on ratios or thresholds are not supported as quarantine rules."
InsuranceLake also shows what a halt on freshness looks like when you want the pipeline to stop rather than split:
CustomSql 'SELECT count(*) FROM primary
WHERE claims_last_updated >= now() - interval 24 hours' >= 15.2 The split, in code
from awsglue.transforms import EvaluateDataQuality, Filter
rowLevelOutcomes = EvaluateDataQuality.apply(
frame=orders_frame,
ruleset=quarantine_capable_ruleset, # only row-level rules
publishing_options={
"dataQualityEvaluationContext": "silver_orders",
"enableDataQualityCloudWatchMetrics": True,
"enableDataQualityResultsPublishing": True,
"resultsS3Prefix": "s3://lakehouse-dq/results/"
})
clean = Filter.apply(frame=rowLevelOutcomes,
f=lambda r: r["DataQualityEvaluationResult"] == "Passed")
dirty = Filter.apply(frame=rowLevelOutcomes,
f=lambda r: r["DataQualityEvaluationResult"] != "Passed")
# clean -> s3://lakehouse-silver/orders/dt=2026-10-10/ (Iceberg commit)
# dirty -> s3://lakehouse-quarantine/orders/dt=2026-10-10/ + catalog table
# glue.data.quality.rules.failed metric -> EventBridge -> SNS/LambdaAWS's Studio guidance also offers a first-party variant: in Data quality actions, choose Fail job after loading target data, which surfaces as AssertionError: The job failed due to failing DQ rules for node: β¦: halt without quarantine.
5.3 Worked example E3: the quarantine loop with real numbers
Worked example E3 - one bad day on lakehouse_silver.orders, as of Oct 2026
Input ................................. 1,000,000 rows, 480 files
Evaluated rules (quarantine-capable) .. Completeness customer_id,
Uniqueness order_id,
ColumnValues order_status,
CustomSql total_amount = item_sum
Rows passing .......................... 987,520 (98.752%)
Rows quarantined ...................... 12,480 (1.248%)
- null customer_id .................. 8,900
- duplicate order_id ............... 2,310
- illegal order_status ............. 1,150
- amount mismatch .................. 120 <- CustomSql row form
Ruleset score ........................ 0.75 (3/4 rules passed)
-> score says "fail", the ROWS say "1.248% of the batch is dirty"
Quarantine write ...................... s3://lakehouse-quarantine/orders/
dt=2026-10-10/ (~4.1 MB Parquet)
Alert ................................ EventBridge state=FAILED -> SNS
-> Lambda -> ticket + marker object
Backfill ......................................................
1. analyst fixes 120 amount rows upstream, re-emits 340 corrected files
2. Step Functions "replay-partition" clears the quarantine partition
for dt=2026-10-10 (InsuranceLake clears quarantine storage and
tables on partition reload)
3. same job re-runs the partition; bookmark rewound or a new
transformation_ctx, output to a NEW prefix or Iceberg MERGE
4. silver commit + gold refresh; DQ results table records the pass
Cost of the re-run (6 DPUs x 5 min marginal, $0.44/DPU-hr) ... $0.22The last two lines are the point: quarantine without a backfill path is just data loss with extra steps. And because the results are Iceberg tables now, the "what did gold look like before the bad partition" question is answered with time travel instead of a restore.
5.4 Write-Audit-Publish: the Iceberg-native form
AWS's WAP pattern replaces the physical quarantine copy with branches: write everything to a stg branch β run Glue DQ against it β filter passing rows into an audit branch β fast_forward to main with Spark's fast_forward procedure. The blog's demo rule set checked sensor temperature "between β10 and 50 Β°C", and it names the trade-off honestly: "The primary challenge of the WAP pattern is the increased latency it introduces." The dead-letter-queue approach (the S3 quarantine prefix) is the other documented option: pick by whether you can afford the latency or the duplicate storage.
How to answer
Ordering exercise with 7 items. Move them into the correct order, then check your answer.
Drag the quarantine-and-backfill loop into the order it must run:
- π Did you know? Bookmarks track sources, not targets. AWS's docs state plainly that "when you rewind or reset a bookmark, AWS Glue does not clean the target files because there could be multiple targets and targets are not tracked with job bookmarks." That is why the backfill step above writes to a new prefix or MERGEs, replaying into the same path appends duplicates that your
IsPrimaryKeyrule will then dutifully quarantine next run (Glue Developer Guide, job bookmarks, accessed 2026-10-11).
6. Freshness SLOs: three clocks and the arithmetic
6.1 Separate the clocks before you set a target
Clock | Definition | Where you measure it |
|---|---|---|
Event time | When the business event happened: carried in the data | A timestamp column ( order_ts) |
Landing time | When the object hit the bronze prefix | S3 LastModified |
Availability time | When the silver/gold commit made it queryable in Athena | Table commit / results timestamp |
Freshness SLO = P95(availability β event) β€ target. The pipeline contributes two spans: landing β event (ingestion lag) and availability β landing (processing + DQ + backfill lag). DQDL gives you two encodings: DataFreshness against a timestamp column of the table ("Checks if data is fresh", metric Column.[Column].DataFreshness.Compliance, row-level results: Yes) and FileFreshness against S3 file timestamps (file/folder path + threshold, row-level: No).
6.2 Worked example E4: a 3-hour SLO, P95 arithmetic
Worked example E4 - freshness SLO math, 30 daily batches, as of Oct 2026
SLO: P95(availability - event) <= 3 h = 180 min
SPAN MEAN P95
source CDC: event -> bronze .... 35 min 55 min
bronze -> silver (incl. DQ) ..... 12 min 22 min
silver -> gold + catalog ........ 9 min 15 min
orchestration wait (schedule
offset + Step Functions) ...... 38 min 70 min
------- -------
P95 sum (conservative, add
the component P95s) ........... 94 min 162 min = 2.7 h
HEADROOM vs 3 h SLO ............. 18 min
LEVERS, cheapest first:
1. cut the 38-min schedule offset (EventBridge cron alignment)
2. parallelise the gold aggregation
3. tune the CDC / increase DQ concurrency
ALERT LADDER:
- DQDL: DataFreshness "order_ts" > (now() - 2 days) where dt = yesterday
- CloudWatch: silver RowCount = 0 (dead pipeline, not slow)
- EventBridge: Glue job state FAILED
- Dashboard: rolling 7-day P95 freshness trendAdd the component P95s rather than averaging means, P95 of a sum is bounded by the sum of the P95s, so 162 minutes is the defensible number to put in front of a stakeholder, and the 18 minutes of headroom tells you which lever to pull before you breach. The failure ladder is deliberately redundant: a freshness rule catches stale data, a zero-RowCount alarm catches a dead pipeline, and a job-FAILED event catches the crash, three different problems that all look like "the dashboard is old".
6.3 Encoding volume and schema drift as alarms
Signal | DQDL / metric | Alarm shape | What it really means |
|---|---|---|---|
Freshness | DataFreshness, FileFreshness, FileMatch | glue.data.quality.rules.failed Sum β₯ 1, 1 min | Staleness or a stopped feed |
Volume | RowCount > 1000, RowCount <= avg(last(10)) * 1.5 (dynamic) | Same metric, scoped to the volume ruleset | Collapse (0 rows) or a runaway duplicate load |
Schema drift | SchemaMatch, ColumnExists, ColumnDataType, ColumnNamesMatchPattern, ColumnLength | EventBridge on state: FAILED filtered to the schema ruleset β Lambda opens a ticket | A source changed shape under you |
Value drift | ColumnValues β¦ with threshold, DetectAnomalies on another rule's metric | Score alarm + anomaly statistics (billed per statistic) | Distribution shifted, not just broke |
Pipeline crash | n/a: job failure event | EventBridge on Glue job FAILED | Nothing ran |
DetectAnomalies deserves a special mention: it "checks for anomalies in another rule type's reported metrics", which turns yesterday's threshold rules into a self-tuning baseline: at 1 DPU per statistic, per run, which is why AWS recommends enabling it selectively.
7. Operational observability: one dashboard per layer
7.1 Logs by layer
Layer | Where the logs go | Notes |
|---|---|---|
Glue jobs | /aws-glue/jobs/output (stdout/stderr), /aws-glue/jobs/error (system, daemon, Glue Logger), /aws-glue/jobs/logs-v2 (continuous logging: driver + executor, real time) | Redirect with --continuous-log-logGroup; Glue 4.0+/5.0 "all jobs have real-time logging capabilities"; a security configuration appends its name; KMS-encrypted groups need logs:AssociateKmsKey |
Glue job failures | Job insights streams in the same /aws-glue/jobs/logs-v2/ group | -job-insights-rca-driver gives the failing script line number, last Spark action and a concise event list; a rule-based stream adds root-cause analysis |
Cost on logs | A Logs Infrequent Access class group, e.g. /aws-glue/jobs/logs-v2-infrequent-access, pointed at by --continuous-log-logGroup | Caveats: "You cannot change the log class of a log group after it's created", and Logs IA cannot be viewed in the standard Logs console |
Redshift Serverless | /aws/redshift/<namespace>/<log_type> for connection, user and user-activity logs | Auto-created if missing: with Never Expire retention. Set a retention policy or the bill grows without you |
Athena | Metrics in AWS/Athena (opt-in per workgroup: "publish query metrics to CloudWatch") + state-change events | EngineExecutionTime, ProcessedBytes, TotalExecutionTime; dimensions QueryState, QueryType, WorkGroup. Detailed query history is 30 days: long-horizon audit needs EventBridge β Lambda β Firehose β S3 |
Orchestration | X-Ray: Lambda ( Active vs PassThrough tracing modes) and Step Functions (service map) | Traces writable 7 days, queryable 30 days; Step Functions minimum guaranteed trace size 100 KiB. X-Ray does not trace Spark executors inside Glue |
Multi-account setups centralise with cross-account subscription filters, a Firehose/Kinesis log destination, or Organizations-level CloudWatch Logs data centralization (destination log-group names support ${source.accountId}, ${source.region}, ${source.logGroup}, ${source.org.id}).
7.2 The dashboard map
AWS Prescriptive Guidance's recommendation is one dashboard per concern: "a CloudWatch dashboard can show users the status of loads⦠or what percentage of their data was dropped due to low quality or which sources have the maximum failures." For this lakehouse that becomes one dashboard per medallion layer plus an account-level data-operations board:
Free tier as of Oct 2026: 10 custom metrics, 3 dashboards referencing up to 50 metrics each, 10 alarm metrics, 1 million API requests and 5 GB log ingestion per month; custom metrics are $0.30 per metric-month for the first 10,000, then $0.10 and $0.05 tiers. Dashboards and alarms are prorated by the hour from creation: an unused alarm is not free, and anomaly-detection alarms "incur costs for each metric that you list in the alarm metric expression, plus two additional alarm-metric costs."
How to answer
Matching exercise with 8 pairs. Match each item on the left with its answer on the right.
Match each observed signal to the alarm you should build on it:
Drag answers from here:
8. CloudTrail data events: scoped prefixes and the bill
CloudTrail is the data-plane audit trail, and, unscoped, the surprise bill. "By default, trails and event data stores do not log data events. Additional charges apply for data events." Enabling them requires advanced event selectors with eventCategory Equals Data and resources.type Equals AWS:S3:Object, scoped with resources.ARN StartsWith, note the trailing slash is part of the ARN: arn:aws:s3:lakehouse-bronze/.
Rules of the road:
- "Selectors don't support the use of wildcards like
*"; you enumerate prefixes, you do not glob. - Basic selectors: up to 5 event selectors and 250 data resources per trail; advanced selectors allow up to 500 specified values across conditions, and the two styles cannot be mixed.
- Exclusions are free filters:
{"Field": "eventSource", "NotEquals": ["s3lifecycle.amazonaws.com"]}drops lifecycle noise;readOnly = falsegives write-only audit for a landing zone. - Prices: data events $0.10 per 100,000 delivered to S3 (aggregations $0.03 per 100,000 analyzed); management events first copy free, additional copies $2.00 per 100,000. Management events answer who ran
StartJobRun/GrantPermissions/StartQueryExecution; data events answer who touched which object.
8.1 Worked example E5: three selectors, one bill
Worked example E5 - scoped data-event audit for the lakehouse, as of Oct 2026
One trail, THREE advanced selectors (never the whole bucket):
SELECTOR 1 - bronze landing (write-only)
eventCategory = Data, resources.type = AWS::S3::Object
resources.ARN StartsWith arn:aws:s3:::lakehouse-bronze/
eventName Equals [PutObject, CompleteMultipartUpload], readOnly = false
volume 20,000,000 events/month
cost . (20,000,000 / 100,000) x $0.10 ............. $20.00 / month
SELECTOR 2 - quarantine bucket (read + write, small, full audit)
resources.ARN StartsWith arn:aws:s3:::lakehouse-quarantine/
volume 500,000 events/month
cost . (500,000 / 100,000) x $0.10 ................ $0.50 / month
SELECTOR 3 - PII silver prefix (read + write)
resources.ARN StartsWith arn:aws:s3:::lakehouse-silver/pii/
volume 2,000,000 events/month
cost . (2,000,000 / 100,000) x $0.10 .............. $2.00 / month
-----------------------------
SCOPED TOTAL ................................. $22.50 / month
UNSCOPED equivalent (AWS's own example:
300,000,000 events x $0.1/100,000) ......... $300.00 / month
SAVING ....................................... $277.50 / month
Management events (first copy free) keep the "who ran what" story:
StartJobRun, GrantPermissions, StartQueryExecution -> free dashboard.CloudTrail data events are the classic analytics surprise bill. They are off by default, they are billed per event, and a busy bronze prefix with multipart uploads emits both PutObject and CompleteMultipartUpload for every object. Scope one selector per sensitive prefix, bronze landing, quarantine, PII silver, and keep readOnly = false on the landing zone where read audit adds nothing. Off-by-default plus per-event pricing is exactly why AWS built advanced event selectors: "fine-grained selectors for logging data events, which can help you control costs by only logging the specific data events of interest" (CloudTrail User Guide, accessed 2026-10-11).
9. Three planes: DataZone, the Glue catalog, CloudTrail
9.1 What each plane is for
Plane | Service | Answers | Explicitly does not answer |
|---|---|---|---|
Technical catalog + permissions | AWS Glue Data Catalog + Lake Formation | What tables/columns/partitions exist? Who may read them? | Who read them; what a column was derived from |
Business catalog + lineage | Amazon DataZone | What does this data mean? Where did it come from and what consumes it? In business language: "catalog data across your organization with business context", plus business names, descriptions, glossary terms, metadata forms | Permission enforcement; API-call audit |
Access audit | AWS CloudTrail | Which principal called which API on which resource, when, from where? | Column-level derivation; business meaning |
Lake Formation registration authorizes access to a location, it is a permission scope, not a lineage edge. CloudTrail records activity, it is not a derivation graph. Presenting either as "lineage" is the single most common conflation in this space.
9.2 Lineage: the sources you can actually name
Source | What you get | Status |
|---|---|---|
Glue data-source runs in DataZone | Dataset nodes for Glue or Redshift assets published in the DataZone catalog are auto-generated; the Data Source run "fetches information from table metadata to build lineage" for S3, DynamoDB, Catalog, Delta Lake, Iceberg and Hudi tables stored in Amazon S3: JDBC and DocumentDB/MongoDB are not supported | Verified |
Redshift query history in DataZone | "The lineage runs captures queries executed for a given database and generates lineage events" | Verified |
OpenLineage-compatible events via DataZone APIs | Data lineage in DataZone is "an OpenLineage-compatible feature⦠from OpenLineage-enabled systems or through APIs": schedulers, warehouses, SQL engines push events in; you have to opt in because some jobs/connections may be test | Verified (ingest side) |
Glue job runs | JobRun API and EventBridge job events give the S3 β table edges of your own pipelines | Verified as run metadata |
QuickSight | Enters this lesson as a consumer of the DQ and cost result tables in Athena: the role AWS's own blogs use it in | Consumer only |
CloudTrail | Who accessed what: the audit plane above | Audit, not lineage |
DataZone sits on top of the business catalog: you create data sources for Glue and Redshift, import technical metadata into a project inventory, curate it with business names/glossary terms/metadata forms, and publish: "Only the latest version of the inventory asset can be published to the catalog." A consumer then sees "a view of that asset's lineage with 1-level upstream and downstream." It is a governance and discovery layer, not a replacement for the Glue Data Catalog that Athena and Redshift Spectrum query.
- π Did you know? DataZone lineage reached general availability on 2024-12-03, and the announcement is explicit that capture is automatic for Glue and Redshift and programmatic for everything else: producers "hydrate the lineage information programmatically by creating custom lineage nodes using APIs that accept OpenLineage compatible events from existing pipeline components, such as schedulers, warehouses, analysis tools, and SQL engines." So the integration test for your lakehouse is simple: publish a Glue asset, run its data source, and see whether the upstream node appears, before you promise anyone end-to-end lineage (AWS News Blog, 2024-12-03).
Caption to memorise: catalog = technical metadata and permissions (Glue + Lake Formation); DataZone = business catalog and lineage; CloudTrail = access audit. Three planes, three tools.
10. Worked example E6: EventBridge β SNS β Lambda remediation, wired
10.1 The rule
{
"source": ["aws.glue-dataquality"],
"detail-type": ["Data Quality Evaluation Results Available"],
"detail": {
"context": {
"contextType": ["GLUE_DATA_CATALOG"],
"databaseName": ["lakehouse_silver"],
"tableName": ["orders"]
},
"state": ["FAILED"],
"rulesetNames": ["silver_orders_strict"]
}
}10.2 The targets, in order
Target 1, SNS topic dq-alerts (email / Chatbot for the human channel). SNS delivery to Lambda and SQS is free of an SNS delivery charge, "No charge for deliveries to SQS Queues. Standard SQS pricing applies", and likewise for Lambda.
Target 2: Lambda dq-remediate:
import json, os, boto3
from datetime import datetime, timezone
glue = boto3.client("glue")
s3 = boto3.client("s3")
sfn = boto3.client("stepfunctions")
def handler(event, context):
d = event["detail"]
# 1. fetch the failing rules and metrics for this run
res = glue.get_data_quality_result(ResultId=d["resultID"])
failing = [r for r in res.get("RuleResults", []) if r.get("RuleResult") == "FAIL"]
# 2. drop a marker object so the state is queryable in Athena
ts = datetime.now(timezone.utc).strftime("%Y/%m/%d/%H%M%S")
s3.put_object(
Bucket="lakehouse-quarantine",
Key=f"_alerts/{d['databaseName']}/{d['tableName']}/{ts}.json",
Body=json.dumps({"runId": d["runId"], "score": d.get("score"),
"failedRules": [f["RuleName"] for f in failing]}).encode())
# 3. start the replay of the affected partition
sfn.start_execution(
stateMachineArn=os.environ["REPLAY_SFN"],
input=json.dumps({"database": d["databaseName"], "table": d["tableName"],
"ruleset": d["rulesetNames"][0], "resultId": d["resultID"]}))
return {"failed": len(failing)}10.3 The IAM that fits, and what it costs
E6 IAM (least privilege, as of Oct 2026)
DQ task role ................ glue:CreateDataQualityRuleset,
glue:StartDataQualityRulesetEvaluationRun,
glue:StartDataQualityRuleRecommendationRun, glue:GetDataQualityResult,
glue:PublishDataQuality, glue:PutDataQualityStatisticAnnotation,
glue:PutDataQualityProfileAnnotation
+ cloudwatch:PutMetricData with condition
"cloudwatch:namespace": "Glue Data Quality" <- required for metrics
+ s3:PutObject on the results prefix <- required for S3 results
Lambda role .................. glue:GetDataQualityResult on dataQualityRuleset/*,
s3:PutObject on the _alerts/ prefix,
states:StartExecution on the replay state machine
E6 monthly cost, us-east-1 list, as of Oct 2026
EventBridge custom events ... 12 DQ events/day x 30 = 360/month
360 / 1,000,000 x $1.00 ............................. $0.00036
EventBridge -> Lambda same-account delivery ............. $0.00
SNS -> Lambda delivery .................................. $0.00 (no SNS charge)
Lambda calls (12/month, 128 MB, ~200 ms) ................ free tier
CloudWatch custom metrics: 2 DQ metrics + 4 layer metrics
6 x $0.30 ........................................... $1.80 / month
(free tier covers the first 10 custom metrics)
CloudWatch alarms x 3 (hourly prorated, standard res) ... verify in console
-----------
REMEDIATION PLANE TOTAL ............................ ~$1.80 / monthThe punchline is the economics: the alerting path is almost free; the data path is what costs money. Which is the correct shape: a lakehouse that pages someone for $1.80 a month and spends $21.90 a month actually measuring its data.
2026 Updates (as of October 2026)
What changed in the quality, observability and lineage story between 2025 and October 2026: each line checked against a primary AWS source:
- ADVANCED rule recommendations through Amazon Bedrock (2026-09-18; documentation history 2026-09-25). Glue Data Quality can now use "table metadata and sampled rows to generate Data Quality Definition Language (DQDL)" in an ADVANCED mode: a drafting assistant sitting on top of the same DQDL, not a new language (Glue DQ release notes and doc history, accessed 2026-10-11).
- Athena views are now in scope (2026-09-18). The landing page now says Glue DQ "evaluates tables and supported Amazon Athena views that are stored in the AWS Glue Data Catalog": older material claiming Athena views are unsupported is out of date; verify against the live page before teaching an exclusion.
- Distribution Analyzer and Iceberg results tables (2026-07-27). You can "compute frequency distributions for your data columns" (binned histograms for numeric columns) and "write data quality results to Apache Iceberg tables in the AWS Glue Data Catalog": quality history becomes a first-class, time-travelable lakehouse table.
- Glue Data Quality is still the current quality product (GA 2023-05-24). The 2023 Big Data Blog milestone, rules run at ETL time or on a schedule, score visualizations in the console, remains the product state as of Oct 2026, with the 2025 to 2026 releases (labels, constants, pre-processing queries, file rules, Iceberg results, Bedrock recommendations) layered on top rather than replaced.
- S3 Metadata (2025-01-27) puts object metadata behind SQL. "S3 Metadata makes it easy to query and filter S3 object metadata using SQL⦠you can query object metadata using Amazon Athena", with S3 Metadata tables appearing in the Glue Data Catalog: a new source of operational lineage-adjacent facts (object counts, sizes, timestamps) for freshness dashboards.
- DataZone domains upgrade into SageMaker Unified Studio (2025-09-11), and re:Invent 2025 added Bedrock AgentCore evals + policy controls alongside "SageMaker Unified Studio one-click onboarding". DataZone remains the governance and metadata backbone of the unified data + ML + analytics IDE, so the business-catalog and lineage plane this lesson describes is now the layer that Unified Studio sits on (AWS Big Data Blog, 2025-09-11; AWS re:Invent 2025 top announcements, 2025-12-01).
Real-World Case Studies
AWS publishes what this looks like at scale. Every figure below is customer- or AWS-claimed and unaudited, with source and date named: the examinable point is the pattern.
Case A, Siemens: centralized logs that turned days into 10 minutes
Element | Detail |
|---|---|
Customer | Siemens, industrial manufacturing / technology: Cloud Security Operations division |
Challenge | More than 800 AWS accounts producing security and access logs that could not be stored or searched centrally; investigations meant manual correlation |
Services | Amazon Security Lake as the central S3-backed store, Amazon S3 server access logs and VPC Flow Logs as sources, Cribl (AWS Partner) routing the relevant subset onward to Splunk |
Pattern | Land everything cheaply on S3, project only the expensive slices into the analysis engine: the same economics as the analytics lake, applied to observability telemetry |
Outcome as published | "5 to 7 TB of security data daily" ingested with "only the most relevant 600 GB" forwarded: a 90 percent reduction; $1.3 million saved annually while aggregating more logs; new-source onboarding cut to 1 to 2 days; root-cause identification time down 75%; in one investigation, "identified what data in Amazon S3 was impacted in an event within 10 minutes as opposed to several days" |
Source | AWS case study, Siemens Boosts Security Visibility Across 800+ AWS Accounts Using Amazon Security Lake (undated; accessed 2026-10-11) |
The lesson for this course is the shape of the answer: Siemens's 10-minutes-versus-several-days is what per-layer log groups, scoped selectors and one dashboard per layer buy you; you are not diagnosing faster because the tool is smarter, you are diagnosing faster because the evidence was already indexed and scoped. Note what the case does not claim: Security Lake is a security-log aggregation service, not AWS Lake Formation, and it produces audit evidence, not column lineage.
Case B, Vanguard: a "data swamp" turned into owned data products
Element | Detail |
|---|---|
Customer | Vanguard, investment management: Financial Advisor Services ("FAS 360") |
Challenge | A "fragmented 'data swamp' of Parquet files on Amazon Simple Storage Service (Amazon S3)" that had to become a structured, governed system |
Services | Amazon Redshift multi-warehouse architecture (RA3 producer + Serverless consumers, zero-copy sharing), Apache Iceberg data products on S3, AWS Glue for ETL and metrics computation |
Scale after two years | 20 TB in Redshift plus 150 TB in the S3 data lake; 600+ AWS Glue ETL jobs (a 30x increase); 300+ tables (3x); 250+ materialized views; 500+ dashboards (25x); 500,000+ user queries per month |
The quality link | "Vanguard is curating data products on the data lake using Apache Iceberg format and leveraging AWS Glue for metrics computation and data lake integration": the curation step is where rules, freshness SLOs and ownership attach |
Source | AWS Big Data Blog, How Vanguard transformed analytics with Amazon Redshift multi-warehouse architecture, published 2026-03-18 (accessed 2026-10-11) |
Read the 600 jobs and 500,000 monthly queries as a quality-budget statement: at that volume a silent bad partition is not an inconvenience, it is a month of wrong decisions. The "data swamp" phrase is the industry's own shorthand for what happens when a lake accumulates files without curation, ownership and checks; which is precisely the failure mode this lesson's rules, quarantine and SLOs exist to prevent. (Scale numbers are Vanguard/AWS-reported as of the March 2026 post; they are snapshots, not guarantees.)
Practice Questions
How to answer
Multiple-choice question with 4 options. Select one option, then choose Check Answer.
A Data Catalog DQ task fails and you need the exact order IDs that broke the rule so they can be fixed. What is the correct move as of October 2026?
How to answer
Multiple-choice question with 4 options. Select one option, then choose Check Answer.
Your EventBridge rule on 'Data Quality Evaluation Results Available' stopped firing although the ruleset still runs and the score still drops. What is the most likely documented cause?
How to answer
Multiple-choice question with 4 options. Select one option, then choose Check Answer.
A quarantine flow is built on RowCount, Mean, StandardDeviation and an overall-form CustomSql. What happens when rows fail?
How to answer
Multiple-choice question with 4 options. Select one option, then choose Check Answer.
Which DQDL construct should carry a freshness SLO of 'yesterday's partition must contain events no older than 2 days', scoped so history is not flagged?
How to answer
Multiple-choice question with 4 options. Select one option, then choose Check Answer.
Same 20-rule silver ruleset evaluated 30 times a month. Which placement is correct for a quarantine design, using AWS's published example arithmetic?
How to answer
Multiple-choice question with 4 options. Select one option, then choose Check Answer.
For your freshness SLO the component P95s are: CDC 55 min, bronze-to-silver including DQ 22 min, silver-to-gold 15 min, orchestration wait 70 min. What do you report?
How to answer
Multiple-choice question with 4 options. Select one option, then choose Check Answer.
You must audit who wrote objects to the bronze landing zone. Which configuration is correct and cost-controlled as of October 2026?
How to answer
Multiple-choice question with 4 options. Select one option, then choose Check Answer.
A stakeholder asks: 'Where did this gold column come from, and who read it last Tuesday?' Which pairing is correct?
How to answer
Multiple-choice question with 4 options. Select one option, then choose Check Answer.
Which statement about DQDL rulesets is correct as of October 2026?
How to answer
Multiple-choice question with 4 options. Select one option, then choose Check Answer.
Your team wants Deequ-level constraint work on a 400 MB dataset that must finish in under 5 minutes. What does AWS guidance support?
How to answer
Multiple-choice question with 4 options. Select one option, then choose Check Answer.
Which comparative verdict for data quality on an AWS lakehouse is correct as of October 2026?
Comparative Verdict: Glue Data Quality vs Deequ self-managed vs Great Expectations DIY vs third-party observability
- Versus Deequ self-managed: Deequ is the engine underneath Glue Data Quality, so running it yourself re-implements recommendations, scoring, EventBridge events, CloudWatch metrics, the console, results tables and IAM: all of which ship managed. Self-managed Deequ still wins in three documented places: analyzers DQDL does not express, headless EMR batches, and migration validation where you diff source-vs-target metrics into a catalog table. Its costs are real: the JAR must match your Spark version ("It is critical to provide the correct Apache Maven coordinates"), published PyDeequ guidance tops out around Spark 3.3, and the famous Holt-Winters recipe was pinned to Glue 2.0 + one specific JAR. Choose it for the analyzer, not for the platform.
- Versus Great Expectations DIY: GX is a capable framework that AWS does not offer as a managed service, its own docs pin the Glue integration to GX 0.15.50, and the Glue Catalog connector is a partner-authored walkthrough. On this stack you would host it, version it, patch it, and rebuild the alerting that EventBridge gives you for free. The calculus changes only if you need the same suites enforced across non-AWS engines, and even then, DQDL's stated goal is to "standardize data quality rules so that you can use the same data quality rules across different databases and engines", so try that first.
- Versus third-party observability: CloudWatch already gives you logs per layer (
/aws-glue/jobs/output,/error,/logs-v2), metrics per layer (Glue Data Quality,AWS/Athena, custom EMF), alarms onglue.data.quality.rules.failed, dashboards per medallion layer, and EventBridge fan-out at $1.00 per million custom events with $0 same-account delivery: the whole remediation plane in E6 cost about $1.80/month. A vendor earns its fee on things this stack does not do out of the box: cross-account/cross-cloud correlation, long-retention log analytics, ML alert routing, and SLO burn-rate analytics across services. If your estate is AWS-only and your SLOs are the four in Section 6, buy nothing yet. - The one-sentence verdict: in October 2026, run Glue Data Quality as the default on both paths, catalog tasks for scheduled monitoring with recommendations,
EvaluateDataQualityinside the job wherever you need row-level quarantine, sink the results into Iceberg tables in the Data Catalog, alert through EventBridge with the CloudWatch publish checkbox left ON, alarm onglue.data.quality.rules.failedper table per ruleset, quarantine to a scoped prefix with a real backfill, encode freshness as a three-clock P95 SLO, keep CloudTrail data events scoped to sensitive prefixes, and reserve Deequ, DataBrew, GX and third-party observability for the specific gaps each one actually fills.
Key Takeaways:
- Four engines, one default: AWS Prescriptive Guidance maps no-code analyst checks to DataBrew (own
CheckExpressionsyntax, aggregate findings, EventBridge event per validated ruleset), job-embedded and catalog-embedded checks to Glue Data Quality (DQDL, managed, "built on top of the open-source DeeQu framework"), bespoke logic to custom code, and a metrics/constraint report to Deequ. Glue DQ evaluates Glue Catalog tables and supported Athena views over S3, Redshift, Catalog-compatible JDBC, Iceberg/Hudi/Delta. - DQDL contract:
Rules = [... ]bound to one table; 32 rule types; limits of 2,000 rules and 65 KB per ruleset; no nested or list-type columns; four modifiers, expressions (with thresholdonly on ColumnValues/ColumnDataType/CustomSql),where(2024-06-26), dynamic rules (RowCount > avg(last(10)), GA 2024-08-07), labels (team=finance, GA 2025-11-21), plus constants, pre-processing SQL and composite rules. - Row-level outcomes are ETL-only: the Data Catalog task supports recommendations (BASIC; ADVANCED via Bedrock since 2026-09-18), pushdown partition predicates and a 2-DPU minimum, but "Not supported" for identifying failed records; only
EvaluateDataQualityin a job emitsDataQualityRulesPass/Fail/SkipandDataQualityEvaluationResult. Dataset-level rules (RowCount, Mean, StandardDeviation, ColumnCount, overall CustomSql, AggregateMatch and friends) never produce a failing row, so they alert, they do not quarantine. - Three result sinks, all explicit: S3 JSON + crawler + Athena external table; the Glue service store behind
GetDataQualityResult/ListDataQualityResultsand the console's last-10-runs snapshot; and Iceberg tables in the Data Catalog (GA 2026-07-27): rule, profiling, row-level and observation results, each opt-in via--additional-run-options, partitioned bycatalog_id, database_name, table_name, day(stored_on). - The alerting contract has a load-bearing checkbox: EventBridge
detail-type: Data Quality Evaluation Results Available,source: aws.glue-dataquality, carryingscore,stateandrulesSucceeded/Failed/Skipped, and it only works while "Publish metrics to Amazon CloudWatch" stays selected. The CloudWatch twin is namespaceGlue Data Quality, metricsglue.data.quality.rules.passed/.failed, per table per ruleset; alarm on Sum β₯ 1 in 1 minute β SNS. IAM needsglue:PublishDataQualityandcloudwatch:PutMetricDataconditioned on that namespace. - Quarantine is a pattern with three actions: InsuranceLake's warn / quarantine / halt at
before_transformandafter_transform; only row-level expressions may quarantine; ratio and threshold rules cannot. Quarantine = filtered rows βs3://lakehouse-quarantine/<table>/dt=β¦+ catalog table β metric β EventBridge β SNS + Lambda β backfill that clears the quarantine partition and replays through a rewound bookmark or newtransformation_ctx(rewinds do not clean targets). WAP is the Iceberg-native variant:stgbranch β audit βfast_forwardtomain, trading latency for no duplicate storage. - Freshness is three clocks: event time β landing time β availability time, with P95(availability β event) β€ target. Worked example: 55 + 22 + 15 + 70 = 162 min = 2.7 h against a 3-hour SLO = 18 minutes headroom, and the first lever is the 70-minute orchestration wait. Encode it as
DataFreshness(timestamp column) orFileFreshness(S3 file time), and pair it with a volume alarm (RowCount = 0) and a schema-drift ruleset (SchemaMatch,ColumnExists,ColumnDataType). - Observability is layered, and X-Ray stops at Lambda/Step Functions: Glue log groups
/aws-glue/jobs/output,/errorand/logs-v2(redirectable, Logs-IA-able), job-insights root-cause streams with the failing line number, AthenaAWS/Athenametrics (EngineExecutionTime,ProcessedBytes) with 30-day query history, Redshift Serverless/aws/redshift/<namespace>/<log_type>(Never Expire by default: set retention), and one CloudWatch dashboard per medallion layer plus an account data-operations board. - CloudTrail data events are opt-in and per-event: management events' first copy is free; S3 data events cost $0.10 per 100,000: scoped selectors (bronze write-only $20.00 + quarantine $0.50 + PII silver $2.00 = $22.50/month) versus an unscoped 300M-event trail at $300. Advanced event selectors only, no wildcards,
resources.ARN StartsWithwith the trailing slash, up to 500 values;NotEquals s3lifecycle.amazonaws.comdrops noise. CloudTrail is audit, never lineage. - Three planes, three tools: Glue Data Catalog + Lake Formation = technical metadata and permissions (registration is a permission scope, not a lineage edge); Amazon DataZone = business catalog (assets, glossary terms, metadata forms, publish/subscribe) and the lineage graph, auto-capture from Glue data-source runs (S3, DynamoDB, Catalog, Delta, Iceberg, Hudi; not JDBC or DocumentDB/MongoDB) and Redshift query history, plus OpenLineage-compatible events via API (opt-in), with consumers seeing 1-level upstream/downstream; CloudTrail = who called what. Case anchors: Siemens centralized logs across 800+ accounts, cut log volume 90% (5 to 7 TB/day β 600 GB), saved $1.3M/year and identified impacted S3 data in 10 minutes vs several days with root causes down 75% (AWS case study, accessed 2026-10-11); Vanguard escaped a "data swamp of Parquet files" into Iceberg data products with 600+ Glue jobs (30Γ), 150 TB in S3, and 500,000+ queries/month, the scale at which rules, SLOs and ownership stop being optional (AWS Big Data Blog, 2026-03-18).