Skip to content

firestore: Collections() iterator is billed per collection rather than once #14444

@jameshartig

Description

@jameshartig

Client

firestore v1.22.0

Environment

Go 1.26.1

Code and Dependencies

Ensure you have created a regional, native, standard, default database in the project.

package main

import (
	"context"
	"errors"
	"fmt"
	"log"

	"cloud.google.com/go/firestore"
	"google.golang.org/api/iterator"
)

func main() {
	ctx := context.Background()
	client, err := firestore.NewClient(ctx, "YOUR_PROJECT_HERE")
	if err != nil {
		log.Fatal(err)
	}
	defer client.Close()

	// ensure 10 collections exist
	for i := range 10 {
		name := fmt.Sprintf("test%d", i)
		_, err := client.Collection(name).Doc("dummy").Create(context.Background(), struct{}{})
		if err != nil {
			log.Fatal(err)
		}
	}

	// List the collections 1000 times
	for range 1000 {
		_, err := client.Collections(ctx).Next()
		if err != nil && !errors.Is(err, iterator.Done) {
			log.Fatal(err)
		}
	}
}
go.mod
module test

go 1.26.1

require (
	cloud.google.com/go/firestore v1.22.0
	google.golang.org/api v0.276.0
)

require (
	cloud.google.com/go v0.123.0 // indirect
	cloud.google.com/go/auth v0.20.0 // indirect
	cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
	cloud.google.com/go/compute/metadata v0.9.0 // indirect
	cloud.google.com/go/longrunning v0.9.0 // indirect
	github.com/cespare/xxhash/v2 v2.3.0 // indirect
	github.com/felixge/httpsnoop v1.0.4 // indirect
	github.com/go-logr/logr v1.4.3 // indirect
	github.com/go-logr/stdr v1.2.2 // indirect
	github.com/google/s2a-go v0.1.9 // indirect
	github.com/googleapis/enterprise-certificate-proxy v0.3.14 // indirect
	github.com/googleapis/gax-go/v2 v2.21.0 // indirect
	go.opentelemetry.io/auto/sdk v1.2.1 // indirect
	go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 // indirect
	go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 // indirect
	go.opentelemetry.io/otel v1.43.0 // indirect
	go.opentelemetry.io/otel/metric v1.43.0 // indirect
	go.opentelemetry.io/otel/trace v1.43.0 // indirect
	golang.org/x/crypto v0.49.0 // indirect
	golang.org/x/net v0.52.0 // indirect
	golang.org/x/oauth2 v0.36.0 // indirect
	golang.org/x/sync v0.20.0 // indirect
	golang.org/x/sys v0.42.0 // indirect
	golang.org/x/text v0.35.0 // indirect
	golang.org/x/time v0.15.0 // indirect
	google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7 // indirect
	google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect
	google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 // indirect
	google.golang.org/grpc v1.80.0 // indirect
	google.golang.org/protobuf v1.36.11 // indirect
)

Expected behavior

Firestore pricing says:

For queries other than document reads, such as a request for a list of collection IDs, you are billed for one document read. If fetching the complete set of results requires more than one request (for example, if you are using pagination), you are billed once per request.

To the best of my knowledge the Go client ends up calling ListCollectionIds over gRPC a single time to satisfy the Next() call. My understanding of the above billing statement is that I should only be charged a single read operation.

Actual behavior

Instead, I'm charged 10 read operations per Collections(ctx) call. I created a brand new project and after running the above Go binary I'm sitting at 10,000 reads for the day.

Screenshots

Image

Additional context

I'm not sure if this is a Go-specific bug or if it affects other languages as well.

Metadata

Metadata

Assignees

Labels

api: firestoreIssues related to the Firestore API.triage meI really want to be triaged.

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions