apache/beam · critical
unable to create batch read only transaction:
Error message
unable to create batch read only transaction:
What it means
During processing, generatePartitionsFn panics if it cannot create a Cloud Spanner batch read-only transaction. The error from the Spanner client is appended to the panic message. Unlike construction-time panics, this fires on workers while the pipeline is running, usually due to connectivity, credentials, or an invalid database path.
Source
Thrown at sdks/go/pkg/beam/io/spannerio/generate_partitions.go:84
db string,
query string,
options queryOptions,
) *generatePartitionsFn {
if db == "" {
panic("no database provided")
}
return &generatePartitionsFn{
spannerFn: newSpannerFn(db),
Query: query,
Options: options,
}
}
func (f *generatePartitionsFn) ProcessElement(ctx context.Context, _ []byte, emit func(partitionedRead)) error {
txn, err := f.client.BatchReadOnlyTransaction(ctx, f.Options.TimestampBound)
if err != nil {
panic("unable to create batch read only transaction: " + err.Error())
}
defer txn.Close()
mode := spannerpb.ExecuteSqlRequest_PROFILE
partitions, err := txn.PartitionQueryWithOptions(ctx, spanner.Statement{SQL: f.Query}, partitionOptions(f.Options), spanner.QueryOptions{Mode: &mode})
if err != nil {
panic(fmt.Sprintf("unable to partition query: %v", err))
}
for _, p := range partitions {
emit(newPartitionedRead(txn.ID, p))
}
return nil
}
View on GitHub (pinned to 12126d8942)
Solutions
- Read the wrapped error in the panic message and fix the underlying Spanner client error.
- Verify the database path and that the pipeline's service account has Spanner read permissions.
- Check network connectivity (VPC, firewall, TestEndpoint override) from workers.
- Confirm the TimestampBound options are valid (e.g. a readable timestamp not in the future).
Example fix
// before spannerio.Read with TestEndpoint pointing at a dead emulator // after use a reachable emulator endpoint and export SPANNER_EMULATOR_HOST, or remove the override for production
Defensive patterns
Strategy: try-catch
Validate before calling
// pre-flight: open a plain client and ping
client, err := spanner.NewClient(ctx, db)
if err != nil {
return fmt.Errorf("spanner unreachable: %w", err)
}
client.Close() Try / catch
defer func() {
if r := recover(); r != nil {
if s, ok := r.(string); ok && strings.Contains(s, "unable to create batch read only transaction") {
// log s, alert, retry pipeline with backoff
} else { panic(r) }
}
}() Prevention
- Verify worker network access to Spanner (VPC/firewall, no dead TestEndpoint).
- Grant the pipeline service account Spanner read roles.
- Validate TimestampBound settings (not in the future, stale reads bounded).
When it happens
Trigger: f.client.BatchReadOnlyTransaction(ctx, f.Options.TimestampBound) returns an error — e.g. unreachable Spanner endpoint, missing/insufficient IAM permissions (spanner.backup/reader or db reader), bad TestEndpoint override, or invalid TimestampBound.
Common situations: Running Dataflow workers without access to the Spanner VPC/network; service account lacking spanner.databaseClient or viewer roles; typo'd instance/database path; stale or invalid credentials.
Understand the failure class
Background: Database query failed: Internal Server Error 500s wrapping SQL, Prisma, and connection failures — what to check first — this error's family across 16 libraries.
Related errors
- unable to partition query: %v
- database not provided!
- no database provided
- spannerio.Query: invalid option: %v
- no database provided!
AI-assisted analysis of apache/beam@12126d8942 (2026-09-13).
Data as JSON: /api/errors/afd0271e6304bf84.
Report an issue: GitHub.