Skip to content

chore(spanner): implement cache updater for location-aware routing - #6271

Open
olavloite wants to merge 1 commit into
googleapis:mainfrom
olavloite:spanner-routing-cache-updater
Open

chore(spanner): implement cache updater for location-aware routing#6271
olavloite wants to merge 1 commit into
googleapis:mainfrom
olavloite:spanner-routing-cache-updater

Conversation

@olavloite

Copy link
Copy Markdown
Contributor

Implement CacheUpdater to coordinate between Spanner CacheUpdate wire messages and the client's in-memory routing table (KeyRangeCache) and connection pool (ConnectionCache).

Implement `CacheUpdater` to coordinate between Spanner `CacheUpdate` wire messages and the client's
in-memory routing table (`KeyRangeCache`) and connection pool (`ConnectionCache`).
@olavloite
olavloite requested review from a team as code owners August 3, 2026 18:18
@product-auto-label product-auto-label Bot added the api: spanner Issues related to the Spanner API. label Aug 3, 2026

@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 the CacheUpdater module to handle location-aware routing cache updates by applying CacheUpdate payloads to the KeyRangeCache and pre-warming server connections in the ConnectionCache. The review feedback recommends using a safer, error-returning method like try_get instead of get to avoid potential panics when pre-warming connections, as well as logging errors instead of swallowing them. Additionally, it suggests replacing flaky tokio::task::yield_now() calls in unit tests with a robust polling mechanism with a timeout.

Comment on lines +126 to +131
handle.spawn(async move {
// Calling `get` asynchronously initializes the server connection in the cache
// if it does not already exist, ensuring foreground RPCs don't incur connection
// handshake latency.
let _ = connection_cache.get(&address_string, &config).await;
});

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

In prewarm_server_connections, the background task uses connection_cache.get which can panic. We should prefer using try_get (or other safe, error-returning methods) over get to ensure robust error handling and prevent unexpected panics. Additionally, instead of completely swallowing the error with let _ =, we should log it using tracing::warn! to ensure visibility and aid in debugging connection issues.

            handle.spawn(async move {
                // Calling `try_get` asynchronously initializes the server connection in the cache
                // if it does not already exist, ensuring foreground RPCs don't incur connection
                // handshake latency.
                if let Err(err) = connection_cache.try_get(&address_string, &config).await {
                    tracing::warn!(
                        ?err,
                        address = %address_string,
                        "Failed to pre-warm connection to Spanner server"
                    );
                }
            });
References
  1. Demand Explosive Correctness: Never swallow errors or ignore Result types. Fail loudly and explicitly when appropriate. (link)
  2. Prefer using try_get (or other safe, error-returning methods) over get (which can panic) in Rust to ensure robust error handling and prevent unexpected panics.


updater.process_cache_update(&update);

tokio::task::yield_now().await;

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

Using tokio::task::yield_now().await; to wait for background tasks to complete is highly prone to flakiness in unit tests, especially when the background task involves multiple yield points (such as awaiting Channel::create inside connection_cache.get). To ensure robust and reliable tests, we should poll the cache with a short sleep and a timeout instead of yielding once. This applies to all other tests in this file that use yield_now().

        let start = std::time::Instant::now();
        while updater.connection_cache().get_if_present("10.0.0.1:15000").is_none() {
            if start.elapsed() > std::time::Duration::from_secs(1) {
                panic!("timed out waiting for connection to be pre-warmed");
            }
            tokio::time::sleep(std::time::Duration::from_millis(1)).await;
        }

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

Labels

api: spanner Issues related to the Spanner API.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant