mirror of
https://github.com/neondatabase/neon.git
synced 2026-01-03 19:42:55 +00:00
A few PS changes (#12540)
# TLDR All changes are no-op except some metrics. ## Summary of changes I ### Pageserver Added a new global counter metric `pageserver_pagestream_handler_results_total` that categorizes pagestream request results according to their outcomes: 1. Success 2. Internal errors 3. Other errors Internal errors include: 1. Page reconstruction error: This probably indicates a pageserver bug/corruption 2. LSN timeout error: Could indicate overload or bugs with PS's ability to reach other components 3. Misrouted request error: Indicates bugs in the Storage Controller/HCC Other errors include transient errors that are expected during normal operation or errors indicating bugs with other parts of the system (e.g., malformed requests, errors due to cancelled operations during PS shutdown, etc.) ## Summary of changes II This PR adds a pageserver endpoint and its counterpart in storage controller to list visible size of all tenant shards. This will be a prerequisite of the tenant rebalance command. ## Problem III We need a way to download WAL segments/layerfiles from S3 and replay WAL records. We cannot access production S3 from our laptops directly, and we also can't transfer any user data out of production systems for GDPR compliance, so we need solutions. ## Summary of changes III This PR adds a couple of tools to support the debugging workflow in production: 1. A new `pagectl download-remote-object` command that can be used to download remote storage objects assuming the correct access is set up. ## Summary of changes IV This PR adds a command to list all visible delta and image layers from index_part. This is useful to debug compaction issues as index_part often contain a lot of covered layers due to PITR. --------- Co-authored-by: William Huang <william.huang@databricks.com> Co-authored-by: Chen Luo <chen.luo@databricks.com> Co-authored-by: Vlad Lazar <vlad@neon.tech>
This commit is contained in:
@@ -2,7 +2,9 @@
|
||||
//! Management HTTP API
|
||||
//!
|
||||
use std::cmp::Reverse;
|
||||
use std::collections::{BinaryHeap, HashMap};
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::BinaryHeap;
|
||||
use std::collections::HashMap;
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
@@ -3214,6 +3216,30 @@ async fn get_utilization(
|
||||
.map_err(ApiError::InternalServerError)
|
||||
}
|
||||
|
||||
/// HADRON
|
||||
async fn list_tenant_visible_size_handler(
|
||||
request: Request<Body>,
|
||||
_cancel: CancellationToken,
|
||||
) -> Result<Response<Body>, ApiError> {
|
||||
check_permission(&request, None)?;
|
||||
let state = get_state(&request);
|
||||
|
||||
let mut map = BTreeMap::new();
|
||||
for (tenant_shard_id, slot) in state.tenant_manager.list() {
|
||||
match slot {
|
||||
TenantSlot::Attached(tenant) => {
|
||||
let visible_size = tenant.get_visible_size();
|
||||
map.insert(tenant_shard_id, visible_size);
|
||||
}
|
||||
TenantSlot::Secondary(_) | TenantSlot::InProgress(_) => {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
json_response(StatusCode::OK, map)
|
||||
}
|
||||
|
||||
async fn list_aux_files(
|
||||
mut request: Request<Body>,
|
||||
_cancel: CancellationToken,
|
||||
@@ -4151,6 +4177,7 @@ pub fn make_router(
|
||||
.put("/v1/io_engine", |r| api_handler(r, put_io_engine_handler))
|
||||
.put("/v1/io_mode", |r| api_handler(r, put_io_mode_handler))
|
||||
.get("/v1/utilization", |r| api_handler(r, get_utilization))
|
||||
.get("/v1/list_tenant_visible_size", |r| api_handler(r, list_tenant_visible_size_handler))
|
||||
.post(
|
||||
"/v1/tenant/:tenant_shard_id/timeline/:timeline_id/ingest_aux_files",
|
||||
|r| testing_api_handler("ingest_aux_files", r, ingest_aux_files),
|
||||
|
||||
@@ -2847,6 +2847,24 @@ pub(crate) static MISROUTED_PAGESTREAM_REQUESTS: Lazy<IntCounter> = Lazy::new(||
|
||||
.expect("failed to define a metric")
|
||||
});
|
||||
|
||||
// Global counter for PageStream request results by outcome. Outcomes are divided into 3 categories:
|
||||
// - success
|
||||
// - internal_error: errors that indicate bugs in the storage cluster (e.g. page reconstruction errors, misrouted requests, LSN timeout errors)
|
||||
// - other_error: transient error conditions that are expected in normal operation or indicate bugs with other parts of the system (e.g. error due to pageserver shutdown, malformed requests etc.)
|
||||
pub(crate) static PAGESTREAM_HANDLER_RESULTS_TOTAL: Lazy<IntCounterVec> = Lazy::new(|| {
|
||||
register_int_counter_vec!(
|
||||
"pageserver_pagestream_handler_results_total",
|
||||
"Number of pageserver pagestream handler results by outcome (success, internal_error, other_error)",
|
||||
&["outcome"]
|
||||
)
|
||||
.expect("failed to define a metric")
|
||||
});
|
||||
|
||||
// Constants for pageserver_pagestream_handler_results_total's outcome labels
|
||||
pub(crate) const PAGESTREAM_HANDLER_OUTCOME_SUCCESS: &str = "success";
|
||||
pub(crate) const PAGESTREAM_HANDLER_OUTCOME_INTERNAL_ERROR: &str = "internal_error";
|
||||
pub(crate) const PAGESTREAM_HANDLER_OUTCOME_OTHER_ERROR: &str = "other_error";
|
||||
|
||||
// Metrics collected on WAL redo operations
|
||||
//
|
||||
// We collect the time spent in actual WAL redo ('redo'), and time waiting
|
||||
|
||||
@@ -70,7 +70,7 @@ use crate::context::{
|
||||
};
|
||||
use crate::metrics::{
|
||||
self, COMPUTE_COMMANDS_COUNTERS, ComputeCommandKind, GetPageBatchBreakReason, LIVE_CONNECTIONS,
|
||||
MISROUTED_PAGESTREAM_REQUESTS, SmgrOpTimer, TimelineMetrics,
|
||||
MISROUTED_PAGESTREAM_REQUESTS, PAGESTREAM_HANDLER_RESULTS_TOTAL, SmgrOpTimer, TimelineMetrics,
|
||||
};
|
||||
use crate::pgdatadir_mapping::{LsnRange, Version};
|
||||
use crate::span::{
|
||||
@@ -1441,20 +1441,57 @@ impl PageServerHandler {
|
||||
let (response_msg, ctx) = match handler_result {
|
||||
Err(e) => match &e.err {
|
||||
PageStreamError::Shutdown => {
|
||||
// BEGIN HADRON
|
||||
PAGESTREAM_HANDLER_RESULTS_TOTAL
|
||||
.with_label_values(&[metrics::PAGESTREAM_HANDLER_OUTCOME_OTHER_ERROR])
|
||||
.inc();
|
||||
// END HADRON
|
||||
|
||||
// If we fail to fulfil a request during shutdown, which may be _because_ of
|
||||
// shutdown, then do not send the error to the client. Instead just drop the
|
||||
// connection.
|
||||
span.in_scope(|| info!("dropping connection due to shutdown"));
|
||||
return Err(QueryError::Shutdown);
|
||||
}
|
||||
PageStreamError::Reconnect(reason) => {
|
||||
span.in_scope(|| info!("handler requested reconnect: {reason}"));
|
||||
PageStreamError::Reconnect(_reason) => {
|
||||
span.in_scope(|| {
|
||||
// BEGIN HADRON
|
||||
// We can get here because the compute node is pointing at the wrong PS. We
|
||||
// already have a metric to keep track of this so suppressing this log to
|
||||
// reduce log spam. The information in this log message is not going to be that
|
||||
// helpful given the volume of logs that can be generated.
|
||||
// info!("handler requested reconnect: {reason}")
|
||||
// END HADRON
|
||||
});
|
||||
// BEGIN HADRON
|
||||
PAGESTREAM_HANDLER_RESULTS_TOTAL
|
||||
.with_label_values(&[
|
||||
metrics::PAGESTREAM_HANDLER_OUTCOME_INTERNAL_ERROR,
|
||||
])
|
||||
.inc();
|
||||
// END HADRON
|
||||
return Err(QueryError::Reconnect);
|
||||
}
|
||||
PageStreamError::Read(_)
|
||||
| PageStreamError::LsnTimeout(_)
|
||||
| PageStreamError::NotFound(_)
|
||||
| PageStreamError::BadRequest(_) => {
|
||||
// BEGIN HADRON
|
||||
if let PageStreamError::Read(_) | PageStreamError::LsnTimeout(_) = &e.err {
|
||||
PAGESTREAM_HANDLER_RESULTS_TOTAL
|
||||
.with_label_values(&[
|
||||
metrics::PAGESTREAM_HANDLER_OUTCOME_INTERNAL_ERROR,
|
||||
])
|
||||
.inc();
|
||||
} else {
|
||||
PAGESTREAM_HANDLER_RESULTS_TOTAL
|
||||
.with_label_values(&[
|
||||
metrics::PAGESTREAM_HANDLER_OUTCOME_OTHER_ERROR,
|
||||
])
|
||||
.inc();
|
||||
}
|
||||
// END HADRON
|
||||
|
||||
// print the all details to the log with {:#}, but for the client the
|
||||
// error message is enough. Do not log if shutting down, as the anyhow::Error
|
||||
// here includes cancellation which is not an error.
|
||||
@@ -1472,7 +1509,15 @@ impl PageServerHandler {
|
||||
)
|
||||
}
|
||||
},
|
||||
Ok((response_msg, _op_timer_already_observed, ctx)) => (response_msg, Some(ctx)),
|
||||
Ok((response_msg, _op_timer_already_observed, ctx)) => {
|
||||
// BEGIN HADRON
|
||||
PAGESTREAM_HANDLER_RESULTS_TOTAL
|
||||
.with_label_values(&[metrics::PAGESTREAM_HANDLER_OUTCOME_SUCCESS])
|
||||
.inc();
|
||||
// END HADRON
|
||||
|
||||
(response_msg, Some(ctx))
|
||||
}
|
||||
};
|
||||
|
||||
let ctx = ctx.map(|req_ctx| {
|
||||
|
||||
@@ -5719,6 +5719,16 @@ impl TenantShard {
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
/// HADRON
|
||||
/// Return the visible size of all timelines in this tenant.
|
||||
pub(crate) fn get_visible_size(&self) -> u64 {
|
||||
let timelines = self.timelines.lock().unwrap();
|
||||
timelines
|
||||
.values()
|
||||
.map(|t| t.metrics.visible_physical_size_gauge.get())
|
||||
.sum()
|
||||
}
|
||||
|
||||
/// Builds a new tenant manifest, and uploads it if it differs from the last-known tenant
|
||||
/// manifest in `Self::remote_tenant_manifest`.
|
||||
///
|
||||
|
||||
@@ -225,7 +225,7 @@ impl fmt::Display for ImageLayerName {
|
||||
/// storage and object names in remote storage consist of the LayerName plus some extra qualifiers
|
||||
/// that uniquely identify the physical incarnation of a layer (see [crate::tenant::remote_timeline_client::remote_layer_path])
|
||||
/// and [`crate::tenant::storage_layer::layer::local_layer_path`])
|
||||
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
|
||||
#[derive(Debug, PartialEq, Eq, Hash, Clone, Ord, PartialOrd)]
|
||||
pub enum LayerName {
|
||||
Image(ImageLayerName),
|
||||
Delta(DeltaLayerName),
|
||||
|
||||
Reference in New Issue
Block a user