Post

AWS Glue Job Bookmarks Explained with a Practical Example

In this article, let us understand AWS Glue job bookmarks and build a small example using files in S3. Job bookmarks are useful when a Glue job runs on a schedule and should process only new data instead of reading every file again. This sounds simple, but details around file timestamps, transformation contexts, and job resets are easy to miss.

For our example, CSV files arrive in an S3 landing folder every day. A Glue PySpark job reads them, performs a small transformation, and writes Parquet files to another S3 location. We will first see what a bookmark stores, then configure and test one.

What does a Glue job bookmark store?

A job bookmark is state maintained by AWS Glue for a job. After a successful run, Glue records enough information to identify source data that was already processed. On the next run, the source reader uses that state to skip old data.

For an S3 source, Glue mainly uses the last modified time of each object. It does not add a processed flag to the file, and the state is not stored inside our script. For supported JDBC sources, bookmarks work differently and usually rely on one or more bookmark key columns.

The bookmark belongs to the Glue job name. If we create another job with the same script, that second job has its own bookmark state.

SettingBehaviourTypical use
DisableReads all matching source data on every runFull refresh or testing
EnableReads data after the last successful checkpoint and updates stateNormal incremental load
PauseUses bookmark state but does not update itReprocessing a controlled range

Create the sample source data

Let us assume our bucket has this layout:

1
2
3
s3://my-data-demo/orders/input_date=2026-08-30/orders.csv
s3://my-data-demo/orders/input_date=2026-08-31/orders.csv
s3://my-data-demo/orders/input_date=2026-09-01/orders.csv

Each CSV file can contain a few rows:

order_id,customer_id,amount,order_time
1001,C101,45.50,2026-09-01T08:20:00Z
1002,C205,79.00,2026-09-01T09:10:00Z

For a demo, we can create the Glue table using a crawler. In production, I prefer managing the table and crawler configuration using Terraform or CloudFormation so a manual crawler change does not alter the schema unexpectedly.

Glue script with bookmark support

The important part is to read through a Glue DynamicFrame and provide a stable transformation context.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import sys
from awsglue.context import GlueContext
from awsglue.job import Job
from awsglue.utils import getResolvedOptions
from pyspark.context import SparkContext
from pyspark.sql.functions import col, to_timestamp

args = getResolvedOptions(sys.argv, ["JOB_NAME"])
sc = SparkContext()
glue_context = GlueContext(sc)
spark = glue_context.spark_session

job = Job(glue_context)
job.init(args["JOB_NAME"], args)

source = glue_context.create_dynamic_frame.from_catalog(
    database="sales_raw",
    table_name="orders",
    transformation_ctx="orders_source"
)

df = source.toDF()
cleaned = (
    df.filter(col("order_id").isNotNull())
      .withColumn("amount", col("amount").cast("decimal(12,2)"))
      .withColumn("order_time", to_timestamp("order_time"))
)

(cleaned.write
    .mode("append")
    .partitionBy("input_date")
    .parquet("s3://my-data-demo/orders-parquet/"))

job.commit()

The job initialization and commit calls are required. The commit updates the bookmark after processing has completed. The transformation context identifies this source in the saved state. We should not casually rename orders_source after the job starts running because Glue can treat the renamed context as a new source and read old files again.

The input_date partition column must also be available in the DynamicFrame for this sample write. Depending on how the catalog table was created, we might need to add or derive that column before writing.

Enable the bookmark

In the AWS console, open the Glue job, go to Job details, expand Advanced properties, and set Job bookmark to Enable. The equivalent default argument is:

1
--job-bookmark-option job-bookmark-enable

If the job is deployed using Terraform, set it under default_arguments:

1
2
3
4
5
default_arguments = {
  "--job-language"        = "python"
  "--job-bookmark-option" = "job-bookmark-enable"
  "--enable-metrics"      = "true"
}

Run the job once with the first two input files. Both files should be written to the output. Add the third file and run the same job again. This time Glue should create source records only for the new file. The Spark UI, CloudWatch logs, and output file counts are useful for confirming this; I would not rely only on the green Succeeded status.

What happens when a job fails?

Glue updates the bookmark when job.commit() completes. If the job fails before that point, the next run can read the same input again. That is normally safer than skipping it, but it means the output operation should be designed for retries.

In our example, append mode can create duplicate output if Spark writes some files and the job fails before committing the bookmark. Job bookmarks provide incremental source tracking, but they do not make the complete ETL process exactly-once.

For production, I would normally write into a temporary run location first and then merge into an Apache Iceberg, Hudi, or Delta table using order_id as the business key. Another option is to overwrite only affected partitions, provided late-arriving records are handled correctly.

Resetting and pausing bookmarks

During testing, we often need to process all files again. We can reset saved state from the console or with the AWS CLI:

1
aws glue reset-job-bookmark --job-name orders-incremental-job

The next enabled run reads the source as if the job had no bookmark. Resetting does not delete existing output, so clear or isolate test output first if duplicate data would be a problem.

Pause mode is useful for a backfill because it reads using existing bookmark boundaries without moving the latest checkpoint. Glue also supports job-bookmark-from and job-bookmark-to job arguments for selecting a range of previous runs. I would test this using a separate output path before applying it to a production table.

Things to be careful about

The first limitation is that an S3 bookmark is based on object modification time, not a business date inside the file. If someone replaces an old object, Glue can process it again because its modification time changed. This is another reason to keep landing files immutable.

Bookmarks also do not discover files excluded by the catalog table location, partition metadata, or pushdown predicate. If new S3 partitions are not registered in the Data Catalog, the job may not see them at all.

Changing the source path, job name, or transformation context can break continuity with existing bookmark state. Treat such changes as a migration and test them with known input files.

Finally, not every source and file format has identical bookmark support. For a JDBC source, choose bookmark keys that are strictly increasing or at least reliable and non-null. A frequently updated timestamp can work, but duplicate timestamp values and late transactions need careful testing.

Simple production checklist

Before enabling bookmarks for a real pipeline, I would check the following:

  1. Input files are immutable and have unique object names.
  2. The Glue job always initializes and commits the job.
  3. Every bookmarked source has a stable transformation context.
  4. Output writes are idempotent or can safely handle a retry.
  5. CloudWatch metrics or reconciliation queries compare source and target counts.
  6. There is a documented process for reset and backfill runs.

A small reconciliation query can catch issues early:

1
2
3
4
5
6
select input_date,
       count(*) as row_count,
       count(distinct order_id) as order_count
from analytics.orders
group by input_date
order by input_date desc;

Glue job bookmarks are a convenient way to avoid scanning the same source files in every scheduled run. They work well when files are immutable and the job configuration stays stable. The main thing to remember is that a bookmark tracks source progress only. We still need retry-safe writes, reconciliation, and a clear backfill process for a reliable production pipeline.

This post is licensed under CC BY 4.0 by the author.