TL;DR: Without bookmarks, a nightly ETL reads every file in the bucket — yesterday's, last week's, three years ago's — to find the sliver of new data. Job Bookmarks are Glue's built-in state tracker: they remember which files (by path + last-modified), S3 partitions, and JDBC high-water marks have been processed, so the next run picks up where the last stopped and only new or changed data flows through. It's a one-checkbox, no-code-change fix that routinely turns full rescans into incremental processing — 90%+ off for append-only sources.
The numbers
- Worked example — 5 TB of historical logs, 50 GB new/day: without bookmarks scans all 5 TB (3 hrs × 10 DPUs = 30 DPU-hrs ≈ $13.20/day); with bookmarks scans only 50 GB (10 min ≈ 1.67 DPU-hrs ≈ $0.73/day). ~$374/mo, ~$4,488/yr on one job.
- First run = baseline: processes everything, records what it saw; from run two only the delta is touched (no "empty bookmark = empty job" failure).
- Field examples: an 80 TB clickstream (200 GB new/day) went 6 hrs/$85/day → 12 min/$1.20/day ($2,514/mo saved); a 50M-row payment-table pull dropped to ~100K rows/run (~$14,000/yr); manufacturing telemetry saved >$60,000/yr on one hourly pipeline.
Do this
- Enable it — one setting: Job details → Advanced properties → Job bookmark → Enable, or CLI
--job-bookmark-option job-bookmark-enable. State is stored internally, scoped per job. - Point JDBC jobs at a monotonic column — a timestamp or auto-increment ID as the high-water mark; each run pulls only rows greater than the last bookmark.
- Partition S3 sources by date (
year=/month=/day=) — bookmarks then skip whole partitions before listing files: faster startup, cheaper evaluation, easier debugging. - Verify Glue-context reads — bookmarks apply to
glueContext.create_dynamic_frame.from_catalog/from_options, not rawspark.read.parquet(...); convert raw-Spark reads on bookmarked sources. - Reset deliberately when you need a full reprocess — Job details → Reset job bookmark, or
aws glue reset-job-bookmark --job-name <name>(backfills, logic fixes, schema changes).
Gotchas
- In-place file rewrites break the model — bookmarks track path + last-modified, so if upstream edits old files (rather than appending new ones), they get reprocessed. Works cleanly only on write-once/append-only data.
- Raw Spark reads silently ignore bookmarks — the #1 reason "I enabled bookmarks but it still scans everything"; look here first.
- Retroactively edited DB rows with no change signal won't be re-picked-up — bookmarks need a reliable "newer than" marker.
Skip this if
- The job is a full-refresh dimension rebuild, an all-time running-total aggregation that needs the complete history each run, or a one-off migration/backfill that won't repeat.
- The source mutates old records in place with no detectable change signal. Otherwise — for the ~90% of ETL that's "new data arrives, old data doesn't change" — it's a no-brainer, and pairs naturally with AWS Glue Flex Execution (34% off DPU-hours) and Glue Auto Scaling on the same workloads.