Skip to content

fix(firestore): use pooled HTTP/2 connections by default to fix slow/failing concurrent requests#305

Open
demolaf wants to merge 12 commits into
fix/ci-codecov-action-allowlistfrom
firestore/http2-pooled-client
Open

fix(firestore): use pooled HTTP/2 connections by default to fix slow/failing concurrent requests#305
demolaf wants to merge 12 commits into
fix/ci-codecov-action-allowlistfrom
firestore/http2-pooled-client

Conversation

@demolaf

@demolaf demolaf commented Jul 23, 2026

Copy link
Copy Markdown
Member

Fixes #297.

Dart's Firestore client used dart:io's HttpClient, which only speaks HTTP/1.1 — meaning every concurrent request opened its own TCP+TLS connection. At high concurrency (thousands of simultaneous requests), this caused connection-storm failures (ClientException, HandshakeException) and made Dart significantly slower than Node, whose Firestore client multiplexes many concurrent operations over a small pool of HTTP/2 connections.

This PR makes pooled HTTP/2 the default transport for all production Firestore traffic, with no configuration required:

  • New ClientPool<T>, mirroring nodejs-firestore's ClientPool (dev/src/pool.ts): packs load onto the most-full connection under a 100-stream cap (matching Node's default) rather than spreading evenly, opens a new connection once existing ones are full, stops routing new work to a connection once it's shown a connection-level failure, garbage collects idle connections past a configurable threshold, and drains in-flight operations gracefully before shutting down.
  • Http2ClientPool wraps ClientPool<ClientTransportConnection> as a plain http.BaseClient; googleapis_auth's existing token-refresh logic wraps it via baseClient:, so credential handling is unchanged.
  • The local emulator path stays on plain HTTP/1.1, matching nodejs-firestore's own REST-mode behavior for the emulator (its gRPC mode uses HTTP/2, but this SDK is REST-only, so gRPC's behavior isn't the right comparison).

Benchmark

Concurrency Run 1 Run 2 Run 3
3000 2438.35ms 1602.67ms 1245.91ms
5000 2460.53ms 2199.92ms 2111.67ms
10000 7195.02ms 1518.93ms 2180.65ms

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a custom HTTP/2 client pool (Http2ClientPool) to multiplex Firestore requests over shared HTTP/2 connections in production, bringing the Dart SDK closer to the Node.js implementation. While the addition of HTTP/2 support is a great performance improvement, several critical issues were identified in the review: HTTP/2 header keys must be lowercased to prevent protocol errors; the statusCompleter and bodyController must be properly closed on stream completion or error to prevent hanging requests; socket leaks must be avoided by destroying the socket if HTTP/2 negotiation fails; and failed connections must be cleaned up from the pool to prevent memory leaks.

Comment thread packages/google_cloud_firestore/lib/src/firestore_http_client.dart Outdated
Comment thread packages/google_cloud_firestore/lib/src/firestore_http_client.dart Outdated
Comment thread packages/google_cloud_firestore/lib/src/firestore_http_client.dart Outdated
Comment thread packages/google_cloud_firestore/lib/src/firestore_http_client.dart Outdated
Comment thread packages/google_cloud_firestore/lib/src/firestore_http_client.dart Outdated
Comment thread packages/google_cloud_firestore/lib/src/firestore_http_client.dart Outdated
@demolaf
demolaf marked this pull request as draft July 23, 2026 12:51
@demolaf demolaf changed the title Firestore/http2 pooled client fix(firestore): use pooled HTTP/2 connections by default to fix slow/failing concurrent requests Jul 23, 2026
@demolaf
demolaf marked this pull request as ready for review July 23, 2026 16:06
@demolaf

demolaf commented Jul 23, 2026

Copy link
Copy Markdown
Member Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces HTTP/2 connection pooling by default for the Firestore client to address slow and failing requests under high concurrency. It implements a custom ClientPool and Http2ClientPool to manage and reuse HTTP/2 connections, and adds corresponding unit tests. The reviewer provided several actionable suggestions to improve robustness and performance: linking the cancellation of the response stream to the underlying HTTP/2 stream subscription to prevent resource leaks, wrapping resource cleanup in a try-catch block to handle failures safely, running connection collection unawaited to avoid adding latency to requests, and properly awaiting the termination of the connection pool during client shutdown to prevent hangs.

Comment thread packages/google_cloud_firestore/lib/src/firestore_http_client.dart Outdated
Comment thread packages/google_cloud_firestore/lib/src/firestore_http_client.dart
Comment on lines +209 to +213
if (_terminated) {
_maybeCompleteDrain();
} else {
await _collectIfIdle(pooled);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Awaiting _collectIfIdle inside the finally block blocks the completion of the run call (and thus the user's Firestore request) until the idle connection is fully destroyed. Since connection cleanup is a background task, we should run it unawaited to avoid adding unnecessary latency to the user's request.

Suggested change
if (_terminated) {
_maybeCompleteDrain();
} else {
await _collectIfIdle(pooled);
}
if (_terminated) {
_maybeCompleteDrain();
} else {
unawaited(_collectIfIdle(pooled));
}

Comment thread packages/google_cloud_firestore/lib/src/firestore_http_client.dart
Comment thread packages/google_cloud_firestore/lib/src/firestore_http_client.dart
…ke pool shutdown awaitable, guard cleanup errors
@demolaf
demolaf changed the base branch from main to fix/ci-codecov-action-allowlist July 23, 2026 17:23
@github-actions

github-actions Bot commented Jul 23, 2026

Copy link
Copy Markdown

Coverage Report

✅ Coverage 74.86% meets 40% threshold

Total Coverage: 74.86%
Lines Covered: 5254/7018

Package Breakdown

Package Coverage
firebase_admin_sdk 72.66%
google_cloud_firestore 77.28%

Minimum threshold: 40%

@brianquinlan

Copy link
Copy Markdown
Collaborator

@demolaf

Would it make sense for the Client interface to live in package:http2?

@demolaf

demolaf commented Jul 24, 2026

Copy link
Copy Markdown
Member Author

@brianquinlan this makes sense, do we land this first and open a new issue/PR on dart-lang/http repo or vice versa?

@brianquinlan

Copy link
Copy Markdown
Collaborator

I would love to see a PR on the dart-lang/http repo. I'm going to be out next week so maybe @natebosch can review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Concurrent request execution is exponentially slow

2 participants