Skip to content

Commit e31c896

Browse files
committed
Avoid primary fallback for read replica routing
1 parent 860e446 commit e31c896

7 files changed

Lines changed: 72 additions & 223 deletions

File tree

config/quickwit.yaml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,10 @@ version: 0.8
105105
#
106106
# Optional PostgreSQL read replica URI. Nodes started with the
107107
# `metastore_read_replica` service connect to it over a read-only connection and
108-
# serve stale-tolerant read-only metastore requests (e.g. from searchers). When
109-
# unset, those requests are served by the primary metastore. Defaults to unset.
108+
# serve stale-tolerant read-only metastore requests (e.g. from searchers).
109+
# When set, nodes that issue stale-tolerant read-only metastore requests require
110+
# a `metastore_read_replica` node at startup and do not fall back to the primary
111+
# metastore. Defaults to unset.
110112
#
111113
# metastore_read_replica_uri: postgres://username:password@read-replica-host:port/db
112114
#

docs/configuration/node-config.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ A commented example is available here: [quickwit.yaml](https://github.com/quickw
3030
| `peer_seeds` | List of IP addresses or hostnames used to bootstrap the cluster and discover the complete set of nodes. This list may contain the current node address and does not need to be exhaustive. If the list of peer seeds contains a host name, Quickwit will resolve it by querying the DNS every minute. On kubernetes for instance, it is a good practise to set it to a [headless service](https://kubernetes.io/docs/concepts/services-networking/service/#headless-services). | `QW_PEER_SEEDS` | |
3131
| `data_dir` | Path to directory where data (tmp data, splits kept for caching purpose) is persisted. This is mostly used in indexing. | `QW_DATA_DIR` | `./qwdata` |
3232
| `metastore_uri` | Metastore URI. Can be a local directory or `s3://my-bucket/indexes` or `postgres://username:password@localhost:5432/metastore`. [Learn more about the metastore configuration](metastore-config.md). | `QW_METASTORE_URI` | `{data_dir}/indexes` |
33-
| `metastore_read_replica_uri` | Optional PostgreSQL read replica URI. Nodes running the `metastore_read_replica` service connect to it over a read-only connection and serve stale-tolerant read-only metastore requests (e.g. from searchers). | `QW_METASTORE_READ_REPLICA_URI` | |
33+
| `metastore_read_replica_uri` | Optional PostgreSQL read replica URI. Nodes running the `metastore_read_replica` service connect to it over a read-only connection and serve stale-tolerant read-only metastore requests (e.g. from searchers). When set, nodes that issue those requests require a `metastore_read_replica` node at startup and do not fall back to the primary metastore. | `QW_METASTORE_READ_REPLICA_URI` | |
3434
| `default_index_root_uri` | Default index root URI that defines the location where index data (splits) is stored. The index URI is built following the scheme: `{default_index_root_uri}/{index-id}` | `QW_DEFAULT_INDEX_ROOT_URI` | `{data_dir}/indexes` |
3535
| environment variable only | Log level of Quickwit. Can be a direct log level, or a comma separated list of `module_name=level` | `RUST_LOG` | `info` |
3636

quickwit/quickwit-config/src/node_config/serialize.rs

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -374,9 +374,17 @@ fn validate(node_config: &NodeConfig) -> anyhow::Result<()> {
374374

375375
/// Validates the configuration of the [`QuickwitService::MetastoreReadReplica`] role.
376376
///
377-
/// The role serves the same gRPC service as [`QuickwitService::Metastore`], so the two cannot run
378-
/// on the same node, and it requires a PostgreSQL `metastore_read_replica_uri` to connect to.
377+
/// When configured, `metastore_read_replica_uri` must be a PostgreSQL URI. The
378+
/// [`QuickwitService::MetastoreReadReplica`] role serves the same gRPC service as
379+
/// [`QuickwitService::Metastore`], so the two cannot run on the same node, and the read-replica
380+
/// role requires `metastore_read_replica_uri` to connect to.
379381
fn validate_metastore_read_replica(node_config: &NodeConfig) -> anyhow::Result<()> {
382+
if let Some(uri) = &node_config.metastore_read_replica_uri {
383+
if !uri.protocol().is_database() {
384+
bail!("`metastore_read_replica_uri` must point to a PostgreSQL database, got `{uri}`");
385+
}
386+
}
387+
380388
let read_replica_enabled =
381389
node_config.is_service_enabled(QuickwitService::MetastoreReadReplica);
382390
if !read_replica_enabled {
@@ -389,10 +397,7 @@ fn validate_metastore_read_replica(node_config: &NodeConfig) -> anyhow::Result<(
389397
);
390398
}
391399
match &node_config.metastore_read_replica_uri {
392-
Some(uri) if uri.protocol().is_database() => Ok(()),
393-
Some(uri) => {
394-
bail!("`metastore_read_replica_uri` must point to a PostgreSQL database, got `{uri}`")
395-
}
400+
Some(_) => Ok(()),
396401
None => {
397402
bail!(
398403
"the `metastore_read_replica` service requires `metastore_read_replica_uri` to be \
@@ -1141,6 +1146,23 @@ mod tests {
11411146
assert!(error.to_string().contains("must point to a PostgreSQL"));
11421147
}
11431148

1149+
#[tokio::test]
1150+
async fn test_metastore_read_replica_uri_must_be_a_database_even_without_role() {
1151+
let config_yaml = r#"
1152+
version: 0.8
1153+
node_id: test-node
1154+
metastore_read_replica_uri: s3://not-a-database
1155+
"#;
1156+
let error = load_node_config_with_env(
1157+
ConfigFormat::Yaml,
1158+
config_yaml.as_bytes(),
1159+
&Default::default(),
1160+
)
1161+
.await
1162+
.unwrap_err();
1163+
assert!(error.to_string().contains("must point to a PostgreSQL"));
1164+
}
1165+
11441166
#[tokio::test]
11451167
async fn test_metastore_and_read_replica_roles_are_mutually_exclusive() {
11461168
let config_yaml = r#"

quickwit/quickwit-metastore/src/lib.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ pub use metastore::file_backed::FileBackedMetastore;
4242
pub(crate) use metastore::index_metadata::serialize::{IndexMetadataV0_8, VersionedIndexMetadata};
4343
#[cfg(feature = "postgres")]
4444
pub use metastore::postgres::PostgresqlMetastore;
45-
pub use metastore::read_replica_routing_metastore::ReadReplicaRoutingMetastore;
4645
pub use metastore::read_service::{MetastoreReadService, MetastoreReadServiceClient};
4746
pub use metastore::{
4847
AddSourceRequestExt, CreateIndexRequestExt, CreateIndexResponseExt, IndexMetadata,

quickwit/quickwit-metastore/src/metastore/mod.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ pub(crate) mod index_metadata;
1818
pub mod postgres;
1919

2020
pub mod control_plane_metastore;
21-
pub mod read_replica_routing_metastore;
2221
pub mod read_service;
2322

2423
use std::cmp::Ordering;

quickwit/quickwit-metastore/src/metastore/read_replica_routing_metastore.rs

Lines changed: 0 additions & 164 deletions
This file was deleted.

quickwit/quickwit-serve/src/lib.rs

Lines changed: 39 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ use quickwit_jaeger::JaegerService;
9393
use quickwit_janitor::{JanitorService, start_janitor_service};
9494
use quickwit_metastore::{
9595
ControlPlaneMetastore, ListIndexesMetadataResponseExt, MetastoreReadServiceClient,
96-
MetastoreResolver, ReadReplicaRoutingMetastore,
96+
MetastoreResolver,
9797
};
9898
use quickwit_opentelemetry::otlp::{OtlpGrpcLogsService, OtlpGrpcTracesService};
9999
use quickwit_proto::control_plane::ControlPlaneServiceClient;
@@ -245,31 +245,31 @@ fn metastore_max_in_flight_requests(node_config: &NodeConfig, uri: &Uri) -> usiz
245245
}
246246
}
247247

248-
/// Builds the read-only metastore client used by the search and analytics paths.
249-
///
250-
/// Stale-tolerant reads are routed to `metastore_read_replica` nodes when any are present in the
251-
/// cluster, falling back to `primary` otherwise.
252-
async fn build_search_metastore_client(
248+
async fn build_metastore_client(
253249
cluster: &Cluster,
250+
service: QuickwitService,
254251
max_message_size: ByteSize,
255-
primary: MetastoreServiceClient,
256-
) -> MetastoreReadServiceClient {
257-
let read_replica_balance_channel =
258-
balance_channel_for_service(cluster, QuickwitService::MetastoreReadReplica).await;
259-
let read_replica_connections = read_replica_balance_channel.connection_keys_watcher();
260-
let read_replica_metastore = MetastoreServiceClient::tower()
252+
) -> anyhow::Result<MetastoreServiceClient> {
253+
info!(%service, "connecting to metastore service");
254+
255+
let balance_channel = balance_channel_for_service(cluster, service).await;
256+
257+
if !balance_channel
258+
.wait_for(Duration::from_secs(300), |connections| {
259+
!connections.is_empty()
260+
})
261+
.await
262+
{
263+
bail!("could not find any `{service}` node in the cluster");
264+
}
265+
Ok(MetastoreServiceClient::tower()
261266
.stack_layer(RetryLayer::new(RetryPolicy::from(RetryParams::standard())))
262267
.stack_layer(TimeoutLayer::new(GRPC_METASTORE_SERVICE_TIMEOUT))
263268
.stack_layer(METASTORE_GRPC_CLIENT_METRICS_LAYER.clone())
264269
.stack_layer(tower::limit::GlobalConcurrencyLimitLayer::new(
265270
get_metastore_client_max_concurrency(),
266271
))
267-
.build_from_balance_channel(read_replica_balance_channel, max_message_size, None);
268-
Arc::new(ReadReplicaRoutingMetastore::new(
269-
primary,
270-
read_replica_metastore,
271-
read_replica_connections,
272-
))
272+
.build_from_balance_channel(balance_channel, max_message_size, None))
273273
}
274274

275275
async fn balance_channel_for_service(
@@ -569,27 +569,12 @@ pub async fn serve_quickwit(
569569
if let Some(metastore_server) = &metastore_server_opt {
570570
metastore_server.clone()
571571
} else {
572-
info!("connecting to metastore");
573-
574-
let balance_channel =
575-
balance_channel_for_service(&cluster, QuickwitService::Metastore).await;
576-
577-
if !balance_channel
578-
.wait_for(Duration::from_secs(300), |connections| {
579-
!connections.is_empty()
580-
})
581-
.await
582-
{
583-
bail!("could not find any metastore node in the cluster");
584-
}
585-
MetastoreServiceClient::tower()
586-
.stack_layer(RetryLayer::new(RetryPolicy::from(RetryParams::standard())))
587-
.stack_layer(TimeoutLayer::new(GRPC_METASTORE_SERVICE_TIMEOUT))
588-
.stack_layer(METASTORE_GRPC_CLIENT_METRICS_LAYER.clone())
589-
.stack_layer(tower::limit::GlobalConcurrencyLimitLayer::new(
590-
get_metastore_client_max_concurrency(),
591-
))
592-
.build_from_balance_channel(balance_channel, grpc_config.max_message_size, None)
572+
build_metastore_client(
573+
&cluster,
574+
QuickwitService::Metastore,
575+
grpc_config.max_message_size,
576+
)
577+
.await?
593578
};
594579
// Instantiate a control plane server if the `control-plane` role is enabled on the node.
595580
// Otherwise, instantiate a control plane client.
@@ -742,15 +727,21 @@ pub async fn serve_quickwit(
742727
))
743728
};
744729

745-
// Searchers and the DataFusion analytics path route their stale-tolerant, read-only metastore
746-
// requests to `metastore_read_replica` nodes when any are present in the cluster, and fall back
747-
// to the primary metastore otherwise.
748-
let search_metastore_client = build_search_metastore_client(
749-
&cluster,
750-
grpc_config.max_message_size,
751-
metastore_through_control_plane.clone(),
752-
)
753-
.await;
730+
// Searchers and the DataFusion analytics path use the primary metastore by default. When
731+
// `metastore_read_replica_uri` is configured, they require `metastore_read_replica` nodes and
732+
// do not fall back to the primary if none are available.
733+
let search_metastore_client: MetastoreReadServiceClient =
734+
if node_config.metastore_read_replica_uri.is_some() {
735+
let read_replica_metastore = build_metastore_client(
736+
&cluster,
737+
QuickwitService::MetastoreReadReplica,
738+
grpc_config.max_message_size,
739+
)
740+
.await?;
741+
Arc::new(read_replica_metastore)
742+
} else {
743+
Arc::new(metastore_through_control_plane.clone())
744+
};
754745

755746
let (search_job_placer, search_service, searcher_pool) = setup_searcher(
756747
&node_config,

0 commit comments

Comments
 (0)