DataflowTemplates / gcs-spanner-dv
AGENTS.md1.3k starsChanged 5 months agoApache-2.0View on GitHub
[!IMPORTANT] For AI agents: The code is the source of truth. If this document conflicts with the code, follow the code and fix this document. If your change adds or alters a user-facing feature, pipeline stage, BigQuery schema, or known bug, update the matching section (and the Mermaid diagram) in the same change.
Trending
More talk lately.about the whole repository
Who's talking about GoogleCloudPlatform/DataflowTemplates
- Stack Overflow“Cloud Pub/Sub to GCS, write per element (Dataflow Pipeline)”Stack Overflow · Jun 22, 2018 · 4 votes
- Stack Overflow“Debugging Dataflow template GCS to BigQuery”Stack Overflow · May 20, 2018 · 4 votes
- Stack Overflow“Dataflow read from a topic PubSub and write to Bigquery (multiples tables)”Stack Overflow · Mar 18, 2020 · 3 votes
- Stack Overflow“Google Dataflow: get job name and start time from the running pipeline itself”Stack Overflow · May 14, 2019 · 3 votes
- Stack Overflow“Is it possible to update existing Google cloud dataflow pipelines when using template for deployment ?”Stack Overflow · Apr 24, 2018 · 3 votes
The biggest 5 of 80 mentions linking to the repository that our crawl found. Each line is their own headline.
See all 80
Talked about on GitHub and Stack Overflow (80 times).
On GitHub · 50
- May 26, 2026Enforce binary-only constraints during early requirements cache attempts — apache/beam1 reaction · 6 comments
- May 21, 2026Revert gcsio 3 upgrades — apache/beam1 reaction · 1 comment
- Oct 15, 2026Add support for zoneinfo UTC timestamps — apache/beam0 reactions · 2 comments
- Oct 8, 2026[Failing Test]: Spanner test timeout blocking release — apache/beam0 reactions · 1 comment
- Oct 2, 2026Link Checker Report — rsamborski/google-skills0 reactions
45 more on GitHub
- Sep 22, 2026Faster canonical BSON type discovery for _id — gsbingo17/mongodb-to-new0 reactions
- Sep 22, 2026docs: update launcher VM phase description — neo4j/docs-dataflow-connector0 reactions · 2 comments
- Jul 7, 2026[Task]: Update mheap version to include Node 24 support — apache/beam0 reactions · 1 comment
- May 8, 2026Enhancing counters for MongoDbToMongoDb template — michaeltle-goog/DataflowTemplates0 reactions
- Apr 24, 2026docs: mention import-spec upgrade — neo4j/docs-dataflow-connector0 reactions · 1 comment
- Jan 30, 2026Spanner is hanging when DCP is enabled — apache/beam0 reactions · 4 comments
- Jan 16, 2026[Bug]: Remove Spanner exclusion once bug is fixed — apache/beam0 reactions · 4 comments
- Jan 16, 2026Fix spanner io hang by closing connections — apache/beam0 reactions · 3 comments
- Jan 15, 2026Pin spanner version, not just spanner bom — apache/beam0 reactions · 2 comments
- Jan 14, 2026Update neo4j resource manager — apache/beam0 reactions · 4 comments
- Jan 14, 2026Revert spanner version — apache/beam0 reactions · 4 comments
- Dec 29, 2025Documentation update on RetryDLQ flow — GoogleCloudPlatform/spanner-migration-tool0 reactions · 1 comment
- Oct 20, 2025Fix BigQueryIO File load validate runtime value provider — apache/beam0 reactions · 2 comments
- Aug 28, 2025update the changes.md for upgrading MongoDB Java driver — apache/beam0 reactions · 1 comment
- Apr 16, 2025[Task]: Add Validates Runner Yaml Suite (or add to existing suites) — apache/beam0 reactions
- Apr 6, 2025Fix flaky test for greedy allocator — SurenSK/alluxio_WLTest0 reactions
- Mar 28, 2025Support Time Partitioned Test Tables in BigqueryResourceManager — apache/beam0 reactions · 3 comments
- Mar 28, 2025[Feature Request]: Support Time Partitioned Test Tables in BigqueryResourceManager — apache/beam0 reactions · 1 comment
- Feb 20, 2025Update SpannerSchema to support postgres tokenlist — apache/beam0 reactions · 2 comments
- Nov 15, 2024[Bug]: Certain nullable type resolved to not null coder after #32705 — apache/beam0 reactions · 8 comments
- Nov 3, 2024Fixing Google DataflowTemplates test — TestingResearchIllinois/idoft0 reactions
- Nov 3, 2024Marking PR opened for DataflowTemplates — TestingResearchIllinois/idoft0 reactions
- Oct 31, 2024Bump Hadoop versions for compatibility test — apache/beam0 reactions · 6 comments
- Oct 24, 2024Adding one Opened GoogleDataflowTemplates pull request — TestingResearchIllinois/idoft0 reactions
- Oct 10, 2024Opened 1 Flink PR and 1 DataflowTemplates PR — TestingResearchIllinois/idoft0 reactions
- Oct 10, 2024Opened a pull request for DataflowTemplates — TestingResearchIllinois/idoft0 reactions
- Sep 13, 2024Bring docs up to date with new spec and features (templates GA) — neo4j/docs-dataflow-connector0 reactions · 1 comment
- Sep 10, 2024Fixed flaky test DataStoreConvertersTest.testCheckSameKey() — mihirgune/DataflowTemplates0 reactions
- Aug 14, 2024[7.3.1] Add a version indicator and selector to all docs pages — bazelbuild/bazel0 reactions
- Aug 1, 2024Handle rc container in _update_container_image_for_dataflow — apache/beam0 reactions · 1 comment
- Jul 5, 2024Fix failing `CodeCov` report — theochem/Selector0 reactions · 1 comment
- Jun 12, 2024Add a version indicator and selector to all docs pages — bazelbuild/bazel0 reactions · 25 comments
- Mar 25, 2024Bring docs up to date with new spec and features — neo4j/docs-dataflow-connector0 reactions · 2 comments
- Mar 13, 2024[Bug] Annotation processor generated duplicated classes from dependency that also used autovalue — google/auto0 reactions · 3 comments
- Mar 12, 2024[Bug]: hamcrest required in class path in 2.55.0 — apache/beam0 reactions · 9 comments
- Feb 8, 2024[Bug]: DualInputNestedValueProvider is not able to get Value properly if input is RuntimeValueProvider. — apache/beam0 reactions · 4 comments
- Feb 3, 2024[Task]: Reverse it component migration — apache/beam0 reactions · 1 comment
- Nov 13, 2023[Bug]: Can not use Avro over 1.8.2 with Beam 2.52.0 — apache/beam0 reactions · 6 comments
- Oct 27, 2023The project DataflowTemplates can't be parsed when opened by VSCode from WSL — redhat-developer/vscode-java0 reactions
- Aug 20, 2023Increase Elasticsearch wait time from 1 to 2 minutes, make it configurable — testcontainers/testcontainers-java0 reactions · 4 comments
- Aug 3, 2023Intermittent `Stream closed` exception after upgrading to 1.10.2 — google/auto0 reactions · 16 comments
- Jun 5, 2023[Bug]: Possible data loss in BigtableIO r/w if timestamp not set (default to epoch) — apache/beam0 reactions · 9 comments
- Jan 26, 2023[Task]: Migrate DataflowTemplates integration test utilities to Apache Beam repository — apache/beam0 reactions · 3 comments
- Jul 6, 2022Introduce support for RECORD type in TextIOToBigQuery template — chmurakrajowa/OChKDataflowTemplates0 reactions
- Jun 10, 2022Add OutputProject option to PubSubCDC etc. — 1st-penguin/DataflowTemplates0 reactions
On Stack Overflow · 30
- Jun 22, 2018Cloud Pub/Sub to GCS, write per element (Dataflow Pipeline)4 votes · 3 answers
- May 20, 2018Debugging Dataflow template GCS to BigQuery4 votes
- Mar 18, 2020Dataflow read from a topic PubSub and write to Bigquery (multiples tables)3 votes · 1 answer
- May 14, 2019Google Dataflow: get job name and start time from the running pipeline itself3 votes
- Apr 24, 2018Is it possible to update existing Google cloud dataflow pipelines when using template for deployment ?3 votes
25 more on Stack Overflow
- Apr 26, 2023Can a google dataflow spanner to big query change stream be configured to only watch/post Inserts and ignore all updates/deletes2 votes
- Jan 9, 2023How to concate Two ValueProvider and assign it to new ValueProvider?2 votes
- Mar 17, 2022Consumer wrongly ignoring already consumed messages2 votes
- Nov 18, 2021Is there a version of Apache Beam that can read JSON from Google Spanner?2 votes
- Aug 9, 2021Is there a way to match avro schema with Bigquery and Bigtable?2 votes
- May 21, 2020Using performance.now() in Dataflow Javascript UDF2 votes
- Apr 12, 2019Error when building this GCP Dataflow sample for Pubsub to Bigquery streaming2 votes · 2 answers
- Feb 11, 2019Exporting data from a table in spanner to GCS cloud storage2 votes
- Nov 27, 2018Is there a way to customize Google provided Cloud Dataflow templates?2 votes
- May 22, 2018Skipping records in Dataflow JavaScript UDF2 votes
- Feb 21, 2018Cloud Dataflow: How to use Google-provided Templates for PubSub to BigQuery2 votes
- Aug 3, 2023How to convert a JsonObject into an Avro Generic Record?1 vote
- Apr 18, 2022ApacheBeam ElasticsearchIO is not working with latest elasticsearch1 vote
- Sep 1, 2021Flex Template Release Notes1 vote
- Feb 1, 2021Using Google Dataflow template, is there simple way to whitelist all tables in a Database instead of passing a comma separated list of all the tables1 vote · 1 answer
- Oct 7, 2020Can Dataflow CDC be used for initial dump too?1 vote · 1 answer
- Apr 29, 2020Google Cloud Platform - Dataflow doesn't decrypt data with KMS key1 vote · 1 answer
- Dec 17, 2019Dataflow: string to pubsub message1 vote
- Jan 22, 2019Unable to run GCP DataflowTemplates locally1 vote · 1 answer
- Oct 2, 2018Google Data Flow Template1 vote
- Dec 5, 2022Encryption/Decryption of Bigdata using apache beam0 votes
- Apr 29, 2021Comparing objects using PAssert containsInAnyOrder in Apache Beam0 votes
- Jan 6, 2021Dataflow batch template provided by google does not work0 votes · 1 answer
- Sep 17, 2020Read the data from Google Cloud Sql to BigQuery using Clud Dataflow0 votes · 1 answer
- May 20, 2019Defining TableSchema in PubSubToBigQuery template0 votes
Where files are headingNew AGENTS.md files have more rules about keeping secrets out of the code, and fewer about code style.
Of the AGENTS.md files we read, the 31,439 first seen in the last six months, against the older ones: the kind of rule that gained most share, and the kind that lost most. About files like this in general, not this one, and an estimate, since a kind is a pattern in the words.
In this repository13 other files it gives its agents.
- smt-e2e-dataflow-debuggingSkill
- smt-functional-testingSkill
- add-source-cdc-data-generatorSkill
- add-integ-tests-datastream-to-spannerSkill
- add-source-datastream-to-spannerSkill
- add-integ-tests-gcs-spanner-dvSkill
- add-integ-tests-sourcedb-to-spannerSkill
- add-source-sourcedb-to-spannerSkill
- add-source-datatype-integ-testSkill
- add-source-functional-integ-testSkill
- meta-test-orchestratorSkill
- add-integ-tests-spanner-to-sourcedbSkill
- add-source-spanner-to-sourcedbSkill
The file
We split this AGENTS.md into the rules it gives an agent
9 rules. About 0 are common, 2 are distinctive, and 7 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈2
Distinctive · estimate
This author's own choices. Worth a look.
7
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 154
MismatchedRecords: Must scale to an unbounded number of rows and data volume.
- Distinctive: line 170
Feature Completeness: When implementing new features or making significant code changes, you MUST refer to the Supported Features & …
- Very specific: line 171
Adding per-table options: Add fields to TableLevelConfig (the optionalConfigurations.<table> value) and keep TableConfiguration holding the …
- Very specific: line 175
Hashing Stability: Do not change the hashing algorithm (ComparisonRecordMapper.buildRecord and UnifiedHasherVisitor.java) without …
- Very specific: line 176
Unit Testing Guidelines: Use JUnit 4 (@RunWith(JUnit4.class)), Apache Beam TestPipeline (@Rule) + PAssert, Google Truth, and Mockito …
- Very specific: line 185
New Source Dialects: When adding support for a new source database that needs an all-datatypes E2E IT (like …
- Very specific: line 186
Assertions & Style: Write distinct scenarios as separate @Test methods with descriptive names (used to prefix GCP resource names).
- Very specific: line 188
All load tests extend GCSSpannerDVLTBase.java.
- Very specific: line 191
Assertions at scale: Prefer asserting exact BigQuery rows, as in ITs.
≈ “Common” is an estimate: at least 1 in 5 of the 40,246 AGENTS.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
AGENTS.md: GCS Spanner Data Validator
[!IMPORTANT] For AI agents: The code is the source of truth. If this document conflicts with the code, follow the code and fix this document. If your change adds or alters a user-facing feature, pipeline stage, BigQuery schema, or known bug, update the matching section (and the Mermaid diagram) in the same change.
Commands
# Setup — run once, and again after pulling or changing an upstream module (spanner-common, spanner-migrations-sdk, it, ...).
# Installs upstream jars into ~/.m2 and builds v2/spanner-custom-shard/target/*.jar, which ITs/LTs upload for custom transformations:
mvn install -pl v2/gcs-spanner-dv,v2/spanner-custom-shard -am -DskipTests -Djib.skip
# Compile (-am also compiles upstream modules such as spanner-common):
mvn test-compile -pl v2/gcs-spanner-dv -am
# Unit tests:
mvn test -pl v2/gcs-spanner-dv -Dspotless.check.skip=true -Dcheckstyle.skip=true -Djacoco.skip=true
# Formatting / style:
mvn spotless:check -pl v2/gcs-spanner-dv
# Integration test (DataflowRunner tests stage the template from local source into -DstageBucket):
mvn clean test -pl v2/gcs-spanner-dv -Dtest=<it_test_name> -Dproject=<project_id> -Dregion=<region> -DartifactBucket=<bucket_name> -DstageBucket=<bucket_name> -DspannerInstanceId=<spanner_instance_id> -Dspotless.check.skip=true -Dcheckstyle.skip=true -Djacoco.skip=true
# Build & stage the Flex Template:
mvn clean package -PtemplatesStage -DskipTests -DprojectId=<project_id> -DbucketName=<bucket_name> -DstagePrefix=<stage_prefix> -DtemplateName="Avro_to_Spanner_Data_Validator" -pl v2/gcs-spanner-dv -am
# Load test:
mvn verify -PtemplatesLoadTests -pl v2/gcs-spanner-dv -Dtest=<lt_test_name> -Dproject=cloud-teleport-testing -Dregion=us-central1 -DartifactBucket=<bucket_name> -DstageBucket=<bucket_name> -DspecPath=<staged_template_spec_path> -DexportProject=<project_id> -DexportDataset=<dataset> -DexportTable=<table> -Dspotless.check.skip=true -Dcheckstyle.skip=true -Djacoco.skip=true- Runner selection: The test's category decides the runner: tests tagged
DirectRunnerTestalways run on DirectRunner; all others run on DataflowRunner. To iterate quickly on a DataflowRunner test, run just that test method locally with-DdirectRunnerTest(except tests using custom transformations, which must not run on DirectRunner). -DspecPath: For ITs, do not pass-DspecPath=gs://dataflow-templates-<region>/latest/...to validate a change — it runs the released template, not your code. Load tests are the opposite: without-DspecPath,GCSSpannerDVLTBasefalls back to the released template, so to test a change, point it at a template you staged.- Deployment: Terraform module in
terraform/Avro_to_Spanner_Data_Validator, samples interraform/samples.
Overview
- Core Intent: A batch Dataflow pipeline (class
GCSSpannerDV, templateAvro_to_Spanner_Data_Validator) that validates migration correctness by reading records from a source system (via GCS AVRO files) and destination Cloud Spanner, hashing the records, comparing them, and writing validation statistics and mismatch reports to BigQuery. Thegcs-spanner-dvtemplate is called AFTER thesourcedb-to-spannerdataflow template, which writes source rows in AVRO format to<gcsOutputDirectory>/<sourceTableName>/<shardId>/*.avro(the<shardId>directory segment is omitted for non-sharded runs) ifgcsOutputDirectoryis passed. The GCS Avro envelope schema (SourceRowWithMetadata) is defined inSourceRow.gcsSchema()(SourceRow.java), and payload column type mappings are defined inUnifiedTypeMapper.java. - Supported Features & Configurations:
- Data Transformations: Supported using custom transformations implementing
ISpannerMigrationTransformer(v2/spanner-migrations-sdk), applied to source Avro records viaGenericRecordTypeConvertor.java(v2/spanner-common; see sample implementations inv2/spanner-custom-shard, e.g.,CustomTransformationForDVIT.java). Configured viatransformationJarPath,transformationClassName, andtransformationCustomParameters. - Schema Transformations: Supported using overrides or session files (primarily for renaming tables/columns or dropping columns). Configured via
schemaOverridesFilePath,tableOverrides, orcolumnOverrides, orsessionFilePathif using a session file. Only one mapper is used, with precedencesessionFilePath>schemaOverridesFilePath>tableOverrides/columnOverrides> identity (SchemaMapperProviderFn.java); lower-priority inputs are silently ignored. - Table Filtering: Users can restrict validation to a subset of tables via
--tables(comma-separated source table names) or--tableConfigurationFilePath(GCS JSON file{"tableNames": ["t1", "t2"]}, parsed intoTableConfiguration.java). Both flags are mutually exclusive and accept source table names; a Spanner-only table (no source table) is named by its Spanner name. - Shard Subsetting: Users can restrict validation to selected logical shards (the
<shardId>directories in GCS) via--shardIds, combined with table filtering as an intersection. On the Spanner side, every in-scope table must be narrowed either by a session-fileshardIdColumnor by a user-writtenspannerQueryintableConfigurationFilePath(optionalConfigurations.<table>.spannerQuery, which wins if both exist). - Sharded Sources: The pipeline accounts for sharded database topologies. Users generally use one of these configurations:
- They may choose to have a
shardIdColumn(default name:migration_shard_id) in their Spanner schema that stores the logical shard ID of the row's origin. This is currently supported by passing a session file so the pipeline is aware of theshardIdColumn. (Note: Supporting this via the overrides file is planned as a follow-up.) - They may combine schema overrides with a custom transformation that returns the
shardIdColumnvalue. Hashing works, but this is not recommended because shard ID column support for overrides is incomplete: the overrides mappers returnnullfromgetShardIdColumnName, so the pipeline itself is unaware of the column. - They may choose not to store this shard information directly in Spanner.
- They may choose to have a
- Data Transformations: Supported using custom transformations implementing
Architecture & Data Flow
The following Mermaid diagram is the Single Source of Truth (SOT) for the pipeline architecture. External .dot and .svg files are not required.
flowchart TD
subgraph Storage ["Storage & Databases"]
GCS[("Google Cloud Storage<br/>(Avro Data)")]
Spanner[("Cloud Spanner<br/>(Destination DB)")]
end
subgraph Config ["Configuration & Mapping"]
TableConfig["TableConfiguration<br/>(--tables / --tableConfigurationFilePath /<br/>--shardIds / spannerQuery)"]
SchemaMapper["SchemaMapperProviderFn<br/>(Session / Overrides / Identity)"]
CustomTransform["CustomTransformation<br/>(Optional JAR)"]
end
subgraph InfoSchema ["SpannerInformationSchemaProcessorTransform"]
ProcessInfoSchema["ProcessInformationSchemaFn<br/>(strong read of Spanner DDL)"]
DdlView["View.asSingleton<br/>(PCollectionView<Ddl>)"]
ProcessInfoSchema --> DdlView
end
subgraph SourceReader ["SourceReaderTransform"]
FilePatterns["Create.of(getFilePatterns)<br/>(one glob per table/shard, or /**.avro)"]
MatchFiles["FileIO.matchAll<br/>(EmptyMatchTreatment.ALLOW)"]
KeepFiles["Filter<br/>(selected tables/shards, if needed)"]
ReadFiles["FileIO.readMatches +<br/>AvroIO.parseFilesGenericRecords"]
SourceHash["SourceHashFn<br/>(ComparisonRecordMapper)"]
FilePatterns --> MatchFiles --> KeepFiles --> ReadFiles --> SourceHash
end
subgraph SpannerReader ["SpannerReaderTransform"]
CreateReadOps["CreateSpannerReadOpsFn<br/>(one query per allowed table)"]
BatchSpannerRead["SpannerIO.readAll<br/>(batching, 15s exact staleness)"]
SpannerHash["SpannerHashFn<br/>(ComparisonRecordMapper)"]
CreateReadOps --> BatchSpannerRead --> SpannerHash
end
subgraph MatchRecords ["MatchRecordsTransform"]
CoGroup["WithKeys(hash) +<br/>CoGroupByKey"]
Funnel["FunnelComparedRecordsFn<br/>(MATCHED_TAG /<br/>MISSING_IN_SPANNER_TAG /<br/>MISSING_IN_SOURCE_TAG)"]
CoGroup --> Funnel
end
subgraph ReportResults ["ReportResultsTransform"]
FormatMismatches["transformMismatchedRecords + Flatten<br/>(MISSING_IN_DESTINATION /<br/>MISSING_IN_SOURCE)"]
CountPerTable["Count per tableName (x3)<br/>+ CoGroupByKey"]
ComputeStats["ComputeTableStatsFn"]
CombineSummary["ValidationSummaryCombineFn<br/>(Combine.globally, withoutDefaults)"]
CountPerTable --> ComputeStats --> CombineSummary
end
subgraph BigQuery ["BigQuery (Validation Results)"]
BQMismatches[("MismatchedRecords<br/>(FILE_LOADS)")]
BQTableStats[("TableValidationStats<br/>(FILE_LOADS)")]
BQSummary[("ValidationSummary<br/>(STREAMING_INSERTS)")]
end
Spanner --> ProcessInfoSchema
DdlView -.-> SourceHash
DdlView -.-> CreateReadOps
DdlView -.-> SpannerHash
TableConfig -.-> FilePatterns
TableConfig -.-> CreateReadOps
SchemaMapper -.-> SourceHash
SchemaMapper -.-> CreateReadOps
SchemaMapper -.-> SpannerHash
CustomTransform -.-> SourceHash
GCS --> MatchFiles
Spanner --> BatchSpannerRead
SourceHash -->|"Source ComparisonRecords (SOURCE_TAG)"| CoGroup
SpannerHash -->|"Spanner ComparisonRecords (SPANNER_TAG)"| CoGroup
Funnel -->|"MISSING_IN_SPANNER_TAG /<br/>MISSING_IN_SOURCE_TAG"| FormatMismatches
Funnel -->|"All tags"| CountPerTable
FormatMismatches --> BQMismatches
ComputeStats --> BQTableStats
CombineSummary --> BQSummaryDetailed Data Flow Steps
- Source Avro files under
gcsInputDirectoryare matched using glob patterns fromSourceReaderTransform.getFilePatterns(narrowed to the selected tables and shards when a filter is set), read, and mapped to their Spanner representations usingComparisonRecordMapper.javainsideSourceHashFn.java. (If aCustomTransformationis configured, source records are transformed before hashing.) CreateSpannerReadOpsFn.javaemits one query per allowed Spanner table (narrowed to the selected shards when--shardIdsis set), and a singleSpannerIO.readAllstage (batching, 15s exact staleness) reads them all. Rows are converted inSpannerHashFn.java.- Both sides are converted into a standard
ComparisonRecord.javaDTO and hashed inComparisonRecordMapper.buildRecord: a Murmur3 128-bit hasher over alphabetically ordered column names and values (values are fed type-aware byUnifiedHasherVisitor.java), followed by the table name.ComparisonRecord.javacarries only(tableName, schemaName, primaryKeyColumns, hash, shardId)— no row payload, so shuffle scales with row count rather than row width.- Shard ID Asymmetry: Source
ComparisonRecords populateshardIdfrom the Avro envelope, whereas SpannerComparisonRecords always haveshardId = null(soMISSING_IN_SOURCErows in BigQuery always haveshard_id = NULL).
- Shard ID Asymmetry: Source
MatchRecordsTransform.javaperforms aCoGroupByKeyon this hash.FunnelComparedRecordsFn.javaroutes records to TupleTagsMATCHED_TAG,MISSING_IN_SPANNER_TAG, andMISSING_IN_SOURCE_TAG(mapped downstream inReportResultsTransform.javato BigQuery mismatch typesMISSING_IN_DESTINATIONandMISSING_IN_SOURCE).- Note on Mismatched Column Values: Because grouping is done on the hash of the entire record, if a row exists in both source and destination with the same primary key but different column values, their hashes will differ. The pipeline logs one as
MISSING_IN_SOURCE(for the Spanner version) and one asMISSING_IN_DESTINATION(for the source version), so each value-mismatched row counts twice inmismatch_row_count.
- Note on Mismatched Column Values: Because grouping is done on the hash of the entire record, if a row exists in both source and destination with the same primary key but different column values, their hashes will differ. The pipeline logs one as
- Finally,
ReportResultsTransform.javawritesMismatchedRecords,TableValidationStats, andValidationSummaryto BigQuery (WRITE_APPEND, keyed byrun_id). Tables with 0 rows on both sides emit noTableValidationStatsrow and are not counted intotal_tables_validated.
Expected Scale
The entire pipeline (reading, hashing, matching, and reporting) must be designed to stream and scale to unbounded rows without in-memory accumulation.
- ValidationSummary: Expected cardinality is at most 1 row per pipeline run (0 rows if no table produced stats).
- TableValidationStats: Expected cardinality is at most 1 row per table; tables with no rows on either side get no row (bounded by Cloud Spanner's limit of up to 5,000 tables per database).
- MismatchedRecords: Must scale to an unbounded number of rows and data volume.
Code Layout (src/main/java/com/google/cloud/teleport/v2/)
templates: pipeline entry point.options: pipeline options (GCSSpannerDVOptions.java).config: table and shard filtering, and per-table options.transforms: top-level PTransforms (one per stage in the diagram).dofn: DoFns for reading, hashing, matching, and stats.fn: schema mapper provider, summary combiner, and helpers.dto:ComparisonRecord, BigQuery row DTOs, andBigQuerySchemas.java.mapper:ComparisonRecordMapper(Avro/Spanner →ComparisonRecord).visitor: type-aware hashing and PK string formatting.
Design doc: go/gcs-spanner-dv-dd
AI Agent Tips
- Feature Completeness: When implementing new features or making significant code changes, you MUST refer to the Supported Features & Configurations section (Data Transformations, Schema Transformations, Table Filtering, Shard Subsetting, Sharded Sources) to ensure your changes gracefully support all configurations.
- Adding per-table options: Add fields to
TableLevelConfig(theoptionalConfigurations.<table>value) and keepTableConfigurationholding the wholeMap<String, TableLevelConfig>rather than one map per field. - Modifying BigQuery DTOs:
- Evaluate if the new field is fundamental to verifying the correctness of the pipeline's core logic. If so, update the corresponding test-assertion DTOs in
GCSSpannerDVTestAsserts.java. Transient or dynamic execution metadata (such asrun_idor timestamps) should be excluded from test DTOs.
- Evaluate if the new field is fundamental to verifying the correctness of the pipeline's core logic. If so, update the corresponding test-assertion DTOs in
- Data Type Mappings & Conversions:
sourcedb-to-spannerwrites source records to Avro using Datastream unified types (see Datastream Unified Types documentation). The DV pipeline casts these Avro values to SpannerValues based on the target column datatype read directly from the Spanner DDL (there is no fixed Avro-to-Spanner mapping). - Hashing Stability: Do not change the hashing algorithm (
ComparisonRecordMapper.buildRecordandUnifiedHasherVisitor.java) without cross-validating both the Avro and Spanner code paths to avoid breaking existing pipeline correctness. - Unit Testing Guidelines: Use JUnit 4 (
@RunWith(JUnit4.class)), Apache BeamTestPipeline(@Rule) +PAssert, Google Truth, and Mockito (mockito-inline). - Integration Tests
- Base Classes & Reference: Standard ITs extend
GCSSpannerDVITBase.java(reference:GCSSpannerDVCoreMatchingIT.java); end-to-end tests (SourceDbToSpanner->GCSSpannerDV) extendEndToEndTestingITBase.java(reference:BulkMigrationAndValidationE2EIT.java). - Runner Selection: Tag
DirectRunnertests with@Category({TemplateIntegrationTest.class, DirectRunnerTest.class}); tests tagged onlyTemplateIntegrationTestrun onDataflowRunner. Mandatory: Tests using custom transformations must NOT be taggedDirectRunnerTestbecauseCustomTransformationImplFetcher(v2/spanner-common,.../spanner/migrations/utils/) caches the first transformer it loads in astaticfield that is never reset. DV always passes aCustomTransformationto the fetcher, so once a transformer is cached it leaks into every later pipeline in the same JVM (sequential or concurrent). ReuseCustomTransformationForDVIT.java(inv2/spanner-custom-shard) when possible. - Test Data & Avro Envelopes: Generate records programmatically at runtime using
GCSSpannerDVAvroSetupHelper.java(RecordBuilderfor standardUsers/AccountRolesschemas) orGenericRecordBuilder(never check in binary.avrofiles). Wherever possible, try to re-use theUsers/AccountRolesschemas fromGCSSpannerDVAvroSetupHelper. Place custom.avsc, session, and SQL files in an isolatedsrc/test/resources/<TestName>/directory. Custom.avscfiles must followsrc/test/resources/GCSSpannerDVAvroSetupHelper/users.avsc: root recordSourceRowWithMetadatawithtableName(string),shardId(["string", "null"]with no default, so.set("shardId", null)is required when building),primaryKeys(arrayofstring), andpayload(where every column uses["null", "<type>"]with"default": null). - Spanner Staleness (
DirectRunneronly):SpannerReaderTransform.javareads with a 15-second exact staleness bound. If aDirectRunnertest creates tables or writes rows in Spanner during setup, callThread.sleep(20000);after the last Spanner DDL or write, before launching the pipeline (not needed forDataflowRunner). - Sharded ITs: Tests using a
shardIdColumnmust load a session file (sessionFilePath) and placeshardIdColumnas the first primary key column in the Spanner DDL (seeGCSSpannerDVShardedIT.java). - Shard Subsetting ITs: Upload Avro to
input/<Table>/<shardId>/(not flat) so shard and table filtering apply (seeGCSSpannerDVSubsetShardIT.java). - Spanner Limits (
GCSSpannerDVWideRowMax*IT.java): These ITs cover Spanner limits (max columns, cell size, key size, key columns, name lengths, string size). Changes to type handling, hashing, PK formatting, or read/shuffle paths must keep them passing. - New Source Dialects: When adding support for a new source database that needs an all-datatypes E2E IT (like
BulkMigrationAndValidationMySQLAllDataTypesE2EIT.java), use theadd-integ-tests-gcs-spanner-dvskill (v2/gcs-spanner-dv/.agents/skills/). It delegates toadd-source-datatype-integ-test(v2/spanner-common/.agents/skills/) and requires a datatype mapping matrix file. - Assertions & Style: Write distinct scenarios as separate
@Testmethods with descriptive names (used to prefix GCP resource names). Assert exact BigQuery output rows viaGCSSpannerDVTestAsserts.java(assertValidationSummary,assertTableValidationStats,assertMismatchedRecords) — never assert only row counts.
- Base Classes & Reference: Standard ITs extend
- Load Tests (
src/test/java/com/google/cloud/teleport/v2/templates/loadtesting/):- All load tests extend
GCSSpannerDVLTBase.java. CallsetUpResourceManagers()for an ephemeral Spanner database, orsetUpResourceManagers(spannerProjectId, spannerInstanceId, spannerDatabaseId)(which usesSpannerResourceManager.Builder.useStaticDatabase()) to attach to a pre-existing static Spanner database without creating or dropping it during cleanup. - Recommendation: For large load tests, always use pre-populated static resources — especially for the Avro GCS input directory — rather than generating data in-test, to avoid slow setup and test flakiness.
- Static resources are read-only: Cleanup never drops a static database, and other load tests assert on its exact contents. Never run DDL or DML against static Spanner databases or write to static GCS inputs. Existing static resources live in project
cloud-teleport-testing(Spanner instanceteleport-avro-to-spanner-dv, bucketgs://nokill-avro-to-spanner-dv/); see the constants in each load test. - Assertions at scale: Prefer asserting exact BigQuery rows, as in ITs. When the output is too large to read row by row (typically
MismatchedRecords), asserting counts is fine; useGCSSpannerDVTestAsserts.countMismatchedRecords.
- All load tests extend
- Known Bugs, Issues & Quirks:
- Duplicate source Avro records cause false
MATCH(b/543222130): InFunnelComparedRecordsFn.java, when bothsourceGroupandspannerGroupare non-empty for a hash, all records insourceGroupare emitted asMATCHED(andComputeTableStatsFn.javasetsdestinationRowCount = matched + onlyInSpanner). Duplicate source Avro records for an existing Spanner row are therefore counted as matched rather than flagged as mismatches. - Non-canonical JSON hashing (
b/546487364):UnifiedHasherVisitor.visitJsonhashes raw JSON text without canonicalization, so key-ordering or formatting differences between source Avro and SpannerJSON/PG_JSONBproduce false mismatches (MySQL/PostgreSQL all-datatypes E2E ITs currently skip non-trivial JSON for this reason). - Unmatched filters are silently ignored: A table (
--tables/tableConfigurationFilePath) or shard ID (--shardIds) that matches nothing in GCS or Spanner is dropped without an error. It is absent from all BigQuery output (noTableValidationStatsorMismatchedRecordsrows, not counted inValidationSummary), so a typo can let the run reportMATCHwithout validating anything for it. - No DLQ / Fail-Fast on Mapper Errors: Any unchecked exception in
ComparisonRecordMapper.java(e.g.Table not found in DDL, custom transformation throw) fails the entire batch job after work-item retries. - BigQuery sinks are independent:
MismatchedRecords,TableValidationStats, andValidationSummaryare separate writes, and the summary is computed from table stats rather than gated on the other loads. AValidationSummaryrow does not proveMismatchedRecordsloaded successfully. - Named schemas are untested:
sourcedb-to-spannerdoes not support custom namespaces, so DV has never been tested with named schemas; treat them as unsupported (e.g.,TableValidationStatsis keyed by table name alone andschema_nameis alwaysNULL). - Primary Key Requirement:
gcs-spanner-dvis only supported for tables with deterministic primary keys. Tables without primary keys (which rely on SMT-injected synthetic UUID PKs) or sharded tables with cross-shard PK collisions (withoutmigration_shard_idin the Spanner PK) will produce mismatches.
- Duplicate source Avro records cause false
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

