mirror of
https://github.com/neondatabase/neon.git
synced 2026-08-13 09:41:27 +00:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 68488f5c74 | |||
| 1389927d36 | |||
| 06357afe6d | |||
| 6943dac164 | |||
| 505aa242ac | |||
| 1c516906e7 | |||
| 7d7cd8375c |
@@ -110,7 +110,6 @@ pub fn launch_disk_usage_global_eviction_task(
|
||||
|
||||
disk_usage_eviction_task(&state, task_config, storage, &conf.tenants_path(), cancel)
|
||||
.await;
|
||||
info!("disk usage based eviction task finishing");
|
||||
Ok(())
|
||||
},
|
||||
);
|
||||
@@ -126,13 +125,16 @@ async fn disk_usage_eviction_task(
|
||||
tenants_dir: &Path,
|
||||
cancel: CancellationToken,
|
||||
) {
|
||||
scopeguard::defer! {
|
||||
info!("disk usage based eviction task finishing");
|
||||
};
|
||||
|
||||
use crate::tenant::tasks::random_init_delay;
|
||||
{
|
||||
if random_init_delay(task_config.period, &cancel)
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
info!("shutting down");
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -167,7 +169,6 @@ async fn disk_usage_eviction_task(
|
||||
tokio::select! {
|
||||
_ = tokio::time::sleep_until(sleep_until) => {},
|
||||
_ = cancel.cancelled() => {
|
||||
info!("shutting down");
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -314,7 +315,7 @@ pub async fn disk_usage_eviction_task_iteration_impl<U: Usage>(
|
||||
partition,
|
||||
candidate.layer.get_tenant_id(),
|
||||
candidate.layer.get_timeline_id(),
|
||||
candidate.layer.filename().file_name(),
|
||||
candidate.layer,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use metrics::metric_vec_duration::DurationResultObserver;
|
||||
use metrics::{
|
||||
register_counter_vec, register_histogram, register_histogram_vec, register_int_counter,
|
||||
register_int_counter_vec, register_int_gauge, register_int_gauge_vec, register_uint_gauge_vec,
|
||||
Counter, CounterVec, Histogram, HistogramVec, IntCounter, IntCounterVec, IntGauge, IntGaugeVec,
|
||||
UIntGauge, UIntGaugeVec,
|
||||
register_int_counter_vec, register_int_gauge, register_int_gauge_vec, register_uint_gauge,
|
||||
register_uint_gauge_vec, Counter, CounterVec, Histogram, HistogramVec, IntCounter,
|
||||
IntCounterVec, IntGauge, IntGaugeVec, UIntGauge, UIntGaugeVec,
|
||||
};
|
||||
use once_cell::sync::Lazy;
|
||||
use pageserver_api::models::TenantState;
|
||||
@@ -203,6 +203,49 @@ pub static PAGE_CACHE: Lazy<PageCacheMetrics> = Lazy::new(|| PageCacheMetrics {
|
||||
},
|
||||
});
|
||||
|
||||
pub struct PageCacheSizeMetrics {
|
||||
pub max_bytes: UIntGauge,
|
||||
|
||||
pub current_bytes_ephemeral: UIntGauge,
|
||||
pub current_bytes_immutable: UIntGauge,
|
||||
pub current_bytes_materialized_page: UIntGauge,
|
||||
}
|
||||
|
||||
static PAGE_CACHE_SIZE_CURRENT_BYTES: Lazy<UIntGaugeVec> = Lazy::new(|| {
|
||||
register_uint_gauge_vec!(
|
||||
"pageserver_page_cache_size_current_bytes",
|
||||
"Current size of the page cache in bytes, by key kind",
|
||||
&["key_kind"]
|
||||
)
|
||||
.expect("failed to define a metric")
|
||||
});
|
||||
|
||||
pub static PAGE_CACHE_SIZE: Lazy<PageCacheSizeMetrics> = Lazy::new(|| PageCacheSizeMetrics {
|
||||
max_bytes: {
|
||||
register_uint_gauge!(
|
||||
"pageserver_page_cache_size_max_bytes",
|
||||
"Maximum size of the page cache in bytes"
|
||||
)
|
||||
.expect("failed to define a metric")
|
||||
},
|
||||
|
||||
current_bytes_ephemeral: {
|
||||
PAGE_CACHE_SIZE_CURRENT_BYTES
|
||||
.get_metric_with_label_values(&["ephemeral"])
|
||||
.unwrap()
|
||||
},
|
||||
current_bytes_immutable: {
|
||||
PAGE_CACHE_SIZE_CURRENT_BYTES
|
||||
.get_metric_with_label_values(&["immutable"])
|
||||
.unwrap()
|
||||
},
|
||||
current_bytes_materialized_page: {
|
||||
PAGE_CACHE_SIZE_CURRENT_BYTES
|
||||
.get_metric_with_label_values(&["materialized_page"])
|
||||
.unwrap()
|
||||
},
|
||||
});
|
||||
|
||||
static WAIT_LSN_TIME: Lazy<HistogramVec> = Lazy::new(|| {
|
||||
register_histogram_vec!(
|
||||
"pageserver_wait_lsn_seconds",
|
||||
|
||||
@@ -53,8 +53,8 @@ use utils::{
|
||||
lsn::Lsn,
|
||||
};
|
||||
|
||||
use crate::repository::Key;
|
||||
use crate::tenant::writeback_ephemeral_file;
|
||||
use crate::{metrics::PageCacheSizeMetrics, repository::Key};
|
||||
|
||||
static PAGE_CACHE: OnceCell<PageCache> = OnceCell::new();
|
||||
const TEST_PAGE_CACHE_SIZE: usize = 50;
|
||||
@@ -187,6 +187,8 @@ pub struct PageCache {
|
||||
/// Index of the next candidate to evict, for the Clock replacement algorithm.
|
||||
/// This is interpreted modulo the page cache size.
|
||||
next_evict_slot: AtomicUsize,
|
||||
|
||||
size_metrics: &'static PageCacheSizeMetrics,
|
||||
}
|
||||
|
||||
///
|
||||
@@ -718,6 +720,9 @@ impl PageCache {
|
||||
|
||||
if let Ok(version_idx) = versions.binary_search_by_key(old_lsn, |v| v.lsn) {
|
||||
versions.remove(version_idx);
|
||||
self.size_metrics
|
||||
.current_bytes_materialized_page
|
||||
.sub_page_sz(1);
|
||||
if versions.is_empty() {
|
||||
old_entry.remove_entry();
|
||||
}
|
||||
@@ -730,11 +735,13 @@ impl PageCache {
|
||||
let mut map = self.ephemeral_page_map.write().unwrap();
|
||||
map.remove(&(*file_id, *blkno))
|
||||
.expect("could not find old key in mapping");
|
||||
self.size_metrics.current_bytes_ephemeral.sub_page_sz(1);
|
||||
}
|
||||
CacheKey::ImmutableFilePage { file_id, blkno } => {
|
||||
let mut map = self.immutable_page_map.write().unwrap();
|
||||
map.remove(&(*file_id, *blkno))
|
||||
.expect("could not find old key in mapping");
|
||||
self.size_metrics.current_bytes_immutable.sub_page_sz(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -762,6 +769,9 @@ impl PageCache {
|
||||
slot_idx,
|
||||
},
|
||||
);
|
||||
self.size_metrics
|
||||
.current_bytes_materialized_page
|
||||
.add_page_sz(1);
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -772,6 +782,7 @@ impl PageCache {
|
||||
Entry::Occupied(entry) => Some(*entry.get()),
|
||||
Entry::Vacant(entry) => {
|
||||
entry.insert(slot_idx);
|
||||
self.size_metrics.current_bytes_ephemeral.add_page_sz(1);
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -782,6 +793,7 @@ impl PageCache {
|
||||
Entry::Occupied(entry) => Some(*entry.get()),
|
||||
Entry::Vacant(entry) => {
|
||||
entry.insert(slot_idx);
|
||||
self.size_metrics.current_bytes_immutable.add_page_sz(1);
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -881,6 +893,12 @@ impl PageCache {
|
||||
|
||||
let page_buffer = Box::leak(vec![0u8; num_pages * PAGE_SZ].into_boxed_slice());
|
||||
|
||||
let size_metrics = &crate::metrics::PAGE_CACHE_SIZE;
|
||||
size_metrics.max_bytes.set_page_sz(num_pages);
|
||||
size_metrics.current_bytes_ephemeral.set_page_sz(0);
|
||||
size_metrics.current_bytes_immutable.set_page_sz(0);
|
||||
size_metrics.current_bytes_materialized_page.set_page_sz(0);
|
||||
|
||||
let slots = page_buffer
|
||||
.chunks_exact_mut(PAGE_SZ)
|
||||
.map(|chunk| {
|
||||
@@ -903,6 +921,30 @@ impl PageCache {
|
||||
immutable_page_map: Default::default(),
|
||||
slots,
|
||||
next_evict_slot: AtomicUsize::new(0),
|
||||
size_metrics,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trait PageSzBytesMetric {
|
||||
fn set_page_sz(&self, count: usize);
|
||||
fn add_page_sz(&self, count: usize);
|
||||
fn sub_page_sz(&self, count: usize);
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn count_times_page_sz(count: usize) -> u64 {
|
||||
u64::try_from(count).unwrap() * u64::try_from(PAGE_SZ).unwrap()
|
||||
}
|
||||
|
||||
impl PageSzBytesMetric for metrics::UIntGauge {
|
||||
fn set_page_sz(&self, count: usize) {
|
||||
self.set(count_times_page_sz(count));
|
||||
}
|
||||
fn add_page_sz(&self, count: usize) {
|
||||
self.add(count_times_page_sz(count));
|
||||
}
|
||||
fn sub_page_sz(&self, count: usize) {
|
||||
self.sub(count_times_page_sz(count));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -608,10 +608,7 @@ impl RemoteTimelineClient {
|
||||
self.calls_unfinished_metric_begin(&op);
|
||||
upload_queue.queued_operations.push_back(op);
|
||||
|
||||
info!(
|
||||
"scheduled layer file upload {}",
|
||||
layer_file_name.file_name()
|
||||
);
|
||||
info!("scheduled layer file upload {layer_file_name}");
|
||||
|
||||
// Launch the task immediately, if possible
|
||||
self.launch_queued_tasks(upload_queue);
|
||||
@@ -664,7 +661,7 @@ impl RemoteTimelineClient {
|
||||
});
|
||||
self.calls_unfinished_metric_begin(&op);
|
||||
upload_queue.queued_operations.push_back(op);
|
||||
info!("scheduled layer file deletion {}", name.file_name());
|
||||
info!("scheduled layer file deletion {name}");
|
||||
}
|
||||
|
||||
// Launch the tasks immediately, if possible
|
||||
@@ -828,7 +825,7 @@ impl RemoteTimelineClient {
|
||||
.queued_operations
|
||||
.push_back(op);
|
||||
|
||||
info!("scheduled layer file deletion {}", name.file_name());
|
||||
info!("scheduled layer file deletion {name}");
|
||||
deletions_queued += 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -335,7 +335,7 @@ impl LayerAccessStats {
|
||||
/// All layers should implement a minimal `std::fmt::Debug` without tenant or
|
||||
/// timeline names, because those are known in the context of which the layers
|
||||
/// are used in (timeline).
|
||||
pub trait Layer: std::fmt::Debug + Send + Sync {
|
||||
pub trait Layer: std::fmt::Debug + std::fmt::Display + Send + Sync {
|
||||
/// Range of keys that this layer covers
|
||||
fn get_key_range(&self) -> Range<Key>;
|
||||
|
||||
@@ -373,9 +373,6 @@ pub trait Layer: std::fmt::Debug + Send + Sync {
|
||||
ctx: &RequestContext,
|
||||
) -> Result<ValueReconstructResult>;
|
||||
|
||||
/// A short ID string that uniquely identifies the given layer within a [`LayerMap`].
|
||||
fn short_id(&self) -> String;
|
||||
|
||||
/// Dump summary of the contents of the layer to stdout
|
||||
fn dump(&self, verbose: bool, ctx: &RequestContext) -> Result<()>;
|
||||
}
|
||||
@@ -512,10 +509,12 @@ pub mod tests {
|
||||
fn is_incremental(&self) -> bool {
|
||||
self.layer_desc().is_incremental
|
||||
}
|
||||
}
|
||||
|
||||
/// Boilerplate to implement the Layer trait, always use layer_desc for persistent layers.
|
||||
fn short_id(&self) -> String {
|
||||
self.layer_desc().short_id()
|
||||
/// Boilerplate to implement the Layer trait, always use layer_desc for persistent layers.
|
||||
impl std::fmt::Display for LayerDescriptor {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.layer_desc().short_id())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -394,10 +394,11 @@ impl Layer for DeltaLayer {
|
||||
fn is_incremental(&self) -> bool {
|
||||
self.layer_desc().is_incremental
|
||||
}
|
||||
|
||||
/// Boilerplate to implement the Layer trait, always use layer_desc for persistent layers.
|
||||
fn short_id(&self) -> String {
|
||||
self.layer_desc().short_id()
|
||||
}
|
||||
/// Boilerplate to implement the Layer trait, always use layer_desc for persistent layers.
|
||||
impl std::fmt::Display for DeltaLayer {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.layer_desc().short_id())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -210,9 +210,15 @@ pub enum LayerFileName {
|
||||
|
||||
impl LayerFileName {
|
||||
pub fn file_name(&self) -> String {
|
||||
self.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for LayerFileName {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Image(fname) => fname.to_string(),
|
||||
Self::Delta(fname) => fname.to_string(),
|
||||
Self::Image(fname) => write!(f, "{fname}"),
|
||||
Self::Delta(fname) => write!(f, "{fname}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -230,10 +230,12 @@ impl Layer for ImageLayer {
|
||||
fn is_incremental(&self) -> bool {
|
||||
self.layer_desc().is_incremental
|
||||
}
|
||||
}
|
||||
|
||||
/// Boilerplate to implement the Layer trait, always use layer_desc for persistent layers.
|
||||
fn short_id(&self) -> String {
|
||||
self.layer_desc().short_id()
|
||||
/// Boilerplate to implement the Layer trait, always use layer_desc for persistent layers.
|
||||
impl std::fmt::Display for ImageLayer {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.layer_desc().short_id())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -131,13 +131,6 @@ impl Layer for InMemoryLayer {
|
||||
true
|
||||
}
|
||||
|
||||
fn short_id(&self) -> String {
|
||||
let inner = self.inner.read().unwrap();
|
||||
|
||||
let end_lsn = inner.end_lsn.unwrap_or(Lsn(u64::MAX));
|
||||
format!("inmem-{:016X}-{:016X}", self.start_lsn.0, end_lsn.0)
|
||||
}
|
||||
|
||||
/// debugging function to print out the contents of the layer
|
||||
fn dump(&self, verbose: bool, _ctx: &RequestContext) -> Result<()> {
|
||||
let inner = self.inner.read().unwrap();
|
||||
@@ -240,6 +233,15 @@ impl Layer for InMemoryLayer {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for InMemoryLayer {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let inner = self.inner.read().unwrap();
|
||||
|
||||
let end_lsn = inner.end_lsn.unwrap_or(Lsn(u64::MAX));
|
||||
write!(f, "inmem-{:016X}-{:016X}", self.start_lsn.0, end_lsn.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl InMemoryLayer {
|
||||
///
|
||||
/// Get layer size on the disk
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use anyhow::Result;
|
||||
use core::fmt::Display;
|
||||
use std::ops::Range;
|
||||
use utils::{
|
||||
id::{TenantId, TimelineId},
|
||||
@@ -48,8 +49,8 @@ impl PersistentLayerDesc {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn short_id(&self) -> String {
|
||||
self.filename().file_name()
|
||||
pub fn short_id(&self) -> impl Display {
|
||||
self.filename()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -71,10 +71,7 @@ impl Layer for RemoteLayer {
|
||||
_reconstruct_state: &mut ValueReconstructState,
|
||||
_ctx: &RequestContext,
|
||||
) -> Result<ValueReconstructResult> {
|
||||
bail!(
|
||||
"layer {} needs to be downloaded",
|
||||
self.filename().file_name()
|
||||
);
|
||||
bail!("layer {self} needs to be downloaded");
|
||||
}
|
||||
|
||||
/// debugging function to print out the contents of the layer
|
||||
@@ -106,10 +103,12 @@ impl Layer for RemoteLayer {
|
||||
fn is_incremental(&self) -> bool {
|
||||
self.layer_desc().is_incremental
|
||||
}
|
||||
}
|
||||
|
||||
/// Boilerplate to implement the Layer trait, always use layer_desc for persistent layers.
|
||||
fn short_id(&self) -> String {
|
||||
self.layer_desc().short_id()
|
||||
/// Boilerplate to implement the Layer trait, always use layer_desc for persistent layers.
|
||||
impl std::fmt::Display for RemoteLayer {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.layer_desc().short_id())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -128,7 +128,7 @@ impl LayerFileManager {
|
||||
// A layer's descriptor is present in the LayerMap => the LayerFileManager contains a layer for the descriptor.
|
||||
self.0
|
||||
.get(&desc.key())
|
||||
.with_context(|| format!("get layer from desc: {}", desc.filename().file_name()))
|
||||
.with_context(|| format!("get layer from desc: {}", desc.filename()))
|
||||
.expect("not found")
|
||||
.clone()
|
||||
}
|
||||
@@ -1381,9 +1381,9 @@ impl Timeline {
|
||||
.read()
|
||||
.unwrap()
|
||||
.observe(delta);
|
||||
info!(layer=%local_layer.short_id(), residence_millis=delta.as_millis(), "evicted layer after known residence period");
|
||||
info!(layer=%local_layer, residence_millis=delta.as_millis(), "evicted layer after known residence period");
|
||||
} else {
|
||||
info!(layer=%local_layer.short_id(), "evicted layer after unknown residence period");
|
||||
info!(layer=%local_layer, "evicted layer after unknown residence period");
|
||||
}
|
||||
|
||||
true
|
||||
@@ -2462,11 +2462,7 @@ impl TraversalLayerExt for Arc<dyn PersistentLayer> {
|
||||
format!("{}", local_path.display())
|
||||
}
|
||||
None => {
|
||||
format!(
|
||||
"remote {}/{}",
|
||||
self.get_timeline_id(),
|
||||
self.filename().file_name()
|
||||
)
|
||||
format!("remote {}/{self}", self.get_timeline_id())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2474,11 +2470,7 @@ impl TraversalLayerExt for Arc<dyn PersistentLayer> {
|
||||
|
||||
impl TraversalLayerExt for Arc<InMemoryLayer> {
|
||||
fn traversal_id(&self) -> TraversalId {
|
||||
format!(
|
||||
"timeline {} in-memory {}",
|
||||
self.get_timeline_id(),
|
||||
self.short_id()
|
||||
)
|
||||
format!("timeline {} in-memory {self}", self.get_timeline_id())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2998,7 +2990,7 @@ impl Timeline {
|
||||
}
|
||||
|
||||
/// Flush one frozen in-memory layer to disk, as a new delta layer.
|
||||
#[instrument(skip_all, fields(tenant_id=%self.tenant_id, timeline_id=%self.timeline_id, layer=%frozen_layer.short_id()))]
|
||||
#[instrument(skip_all, fields(tenant_id=%self.tenant_id, timeline_id=%self.timeline_id, layer=%frozen_layer))]
|
||||
async fn flush_frozen_layer(
|
||||
self: &Arc<Self>,
|
||||
frozen_layer: Arc<InMemoryLayer>,
|
||||
@@ -3677,7 +3669,7 @@ impl Timeline {
|
||||
let remotes = deltas_to_compact
|
||||
.iter()
|
||||
.filter(|l| l.is_remote_layer())
|
||||
.inspect(|l| info!("compact requires download of {}", l.filename().file_name()))
|
||||
.inspect(|l| info!("compact requires download of {l}"))
|
||||
.map(|l| {
|
||||
l.clone()
|
||||
.downcast_remote_layer()
|
||||
@@ -3701,7 +3693,7 @@ impl Timeline {
|
||||
);
|
||||
|
||||
for l in deltas_to_compact.iter() {
|
||||
info!("compact includes {}", l.filename().file_name());
|
||||
info!("compact includes {l}");
|
||||
}
|
||||
|
||||
// We don't need the original list of layers anymore. Drop it so that
|
||||
@@ -4316,8 +4308,8 @@ impl Timeline {
|
||||
if l.get_lsn_range().end > horizon_cutoff {
|
||||
debug!(
|
||||
"keeping {} because it's newer than horizon_cutoff {}",
|
||||
l.filename().file_name(),
|
||||
horizon_cutoff
|
||||
l.filename(),
|
||||
horizon_cutoff,
|
||||
);
|
||||
result.layers_needed_by_cutoff += 1;
|
||||
continue 'outer;
|
||||
@@ -4327,8 +4319,8 @@ impl Timeline {
|
||||
if l.get_lsn_range().end > pitr_cutoff {
|
||||
debug!(
|
||||
"keeping {} because it's newer than pitr_cutoff {}",
|
||||
l.filename().file_name(),
|
||||
pitr_cutoff
|
||||
l.filename(),
|
||||
pitr_cutoff,
|
||||
);
|
||||
result.layers_needed_by_pitr += 1;
|
||||
continue 'outer;
|
||||
@@ -4346,7 +4338,7 @@ impl Timeline {
|
||||
if &l.get_lsn_range().start <= retain_lsn {
|
||||
debug!(
|
||||
"keeping {} because it's still might be referenced by child branch forked at {} is_dropped: xx is_incremental: {}",
|
||||
l.filename().file_name(),
|
||||
l.filename(),
|
||||
retain_lsn,
|
||||
l.is_incremental(),
|
||||
);
|
||||
@@ -4377,10 +4369,7 @@ impl Timeline {
|
||||
if !layers
|
||||
.image_layer_exists(&l.get_key_range(), &(l.get_lsn_range().end..new_gc_cutoff))?
|
||||
{
|
||||
debug!(
|
||||
"keeping {} because it is the latest layer",
|
||||
l.filename().file_name()
|
||||
);
|
||||
debug!("keeping {} because it is the latest layer", l.filename());
|
||||
// Collect delta key ranges that need image layers to allow garbage
|
||||
// collecting the layers.
|
||||
// It is not so obvious whether we need to propagate information only about
|
||||
@@ -4397,7 +4386,7 @@ impl Timeline {
|
||||
// We didn't find any reason to keep this file, so remove it.
|
||||
debug!(
|
||||
"garbage collecting {} is_dropped: xx is_incremental: {}",
|
||||
l.filename().file_name(),
|
||||
l.filename(),
|
||||
l.is_incremental(),
|
||||
);
|
||||
layers_to_remove.push(Arc::clone(&l));
|
||||
@@ -4551,7 +4540,7 @@ impl Timeline {
|
||||
/// If the caller has a deadline or needs a timeout, they can simply stop polling:
|
||||
/// we're **cancellation-safe** because the download happens in a separate task_mgr task.
|
||||
/// So, the current download attempt will run to completion even if we stop polling.
|
||||
#[instrument(skip_all, fields(layer=%remote_layer.short_id()))]
|
||||
#[instrument(skip_all, fields(layer=%remote_layer))]
|
||||
pub async fn download_remote_layer(
|
||||
&self,
|
||||
remote_layer: Arc<RemoteLayer>,
|
||||
@@ -4589,7 +4578,7 @@ impl Timeline {
|
||||
TaskKind::RemoteDownloadTask,
|
||||
Some(self.tenant_id),
|
||||
Some(self.timeline_id),
|
||||
&format!("download layer {}", remote_layer.short_id()),
|
||||
&format!("download layer {}", remote_layer),
|
||||
false,
|
||||
async move {
|
||||
let remote_client = self_clone.remote_client.as_ref().unwrap();
|
||||
@@ -4865,15 +4854,12 @@ impl Timeline {
|
||||
continue;
|
||||
}
|
||||
|
||||
let last_activity_ts = l
|
||||
.access_stats()
|
||||
.latest_activity()
|
||||
.unwrap_or_else(|| {
|
||||
// We only use this fallback if there's an implementation error.
|
||||
// `latest_activity` already does rate-limited warn!() log.
|
||||
debug!(layer=%l.filename().file_name(), "last_activity returns None, using SystemTime::now");
|
||||
SystemTime::now()
|
||||
});
|
||||
let last_activity_ts = l.access_stats().latest_activity().unwrap_or_else(|| {
|
||||
// We only use this fallback if there's an implementation error.
|
||||
// `latest_activity` already does rate-limited warn!() log.
|
||||
debug!(layer=%l, "last_activity returns None, using SystemTime::now");
|
||||
SystemTime::now()
|
||||
});
|
||||
|
||||
resident_layers.push(LocalLayerInfoForDiskUsageEviction {
|
||||
layer: l,
|
||||
|
||||
@@ -70,7 +70,6 @@ impl Timeline {
|
||||
};
|
||||
|
||||
self_clone.eviction_task(cancel).await;
|
||||
info!("eviction task finishing");
|
||||
Ok(())
|
||||
},
|
||||
);
|
||||
@@ -78,6 +77,9 @@ impl Timeline {
|
||||
|
||||
#[instrument(skip_all, fields(tenant_id = %self.tenant_id, timeline_id = %self.timeline_id))]
|
||||
async fn eviction_task(self: Arc<Self>, cancel: CancellationToken) {
|
||||
scopeguard::defer! {
|
||||
info!("eviction task finishing");
|
||||
}
|
||||
use crate::tenant::tasks::random_init_delay;
|
||||
{
|
||||
let policy = self.get_eviction_policy();
|
||||
@@ -86,7 +88,6 @@ impl Timeline {
|
||||
EvictionPolicy::NoEviction => Duration::from_secs(10),
|
||||
};
|
||||
if random_init_delay(period, &cancel).await.is_err() {
|
||||
info!("shutting down");
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -101,7 +102,6 @@ impl Timeline {
|
||||
ControlFlow::Continue(sleep_until) => {
|
||||
tokio::select! {
|
||||
_ = cancel.cancelled() => {
|
||||
info!("shutting down");
|
||||
break;
|
||||
}
|
||||
_ = tokio::time::sleep_until(sleep_until) => { }
|
||||
@@ -209,7 +209,7 @@ impl Timeline {
|
||||
let last_activity_ts = hist_layer.access_stats().latest_activity().unwrap_or_else(|| {
|
||||
// We only use this fallback if there's an implementation error.
|
||||
// `latest_activity` already does rate-limited warn!() log.
|
||||
debug!(layer=%hist_layer.filename().file_name(), "last_activity returns None, using SystemTime::now");
|
||||
debug!(layer=%hist_layer, "last_activity returns None, using SystemTime::now");
|
||||
SystemTime::now()
|
||||
});
|
||||
|
||||
|
||||
@@ -71,6 +71,8 @@ pub(super) async fn handle_walreceiver_connection(
|
||||
ctx: RequestContext,
|
||||
node: NodeId,
|
||||
) -> anyhow::Result<()> {
|
||||
debug_assert_current_span_has_tenant_and_timeline_id();
|
||||
|
||||
WALRECEIVER_STARTED_CONNECTIONS.inc();
|
||||
|
||||
// Connect to the database in replication mode.
|
||||
@@ -140,6 +142,9 @@ pub(super) async fn handle_walreceiver_connection(
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
// Enrich the log lines emitted by this closure with meaningful context.
|
||||
// TODO: technically, this task outlives the surrounding function, so, the
|
||||
// spans won't be properly nested.
|
||||
.instrument(tracing::info_span!("poller")),
|
||||
);
|
||||
|
||||
|
||||
@@ -32,3 +32,7 @@ CREATE VIEW local_cache AS
|
||||
SELECT P.* FROM local_cache_pages() AS P
|
||||
(pageoffs int8, relfilenode oid, reltablespace oid, reldatabase oid,
|
||||
relforknumber int2, relblocknumber int8, accesscount int4);
|
||||
|
||||
CREATE FUNCTION copy_from(conninfo cstring) RETURNS BIGINT
|
||||
AS 'MODULE_PATHNAME', 'copy_from'
|
||||
LANGUAGE C;
|
||||
|
||||
@@ -13,20 +13,32 @@
|
||||
|
||||
#include "access/xact.h"
|
||||
#include "access/xlog.h"
|
||||
#include "access/relation.h"
|
||||
#include "access/xloginsert.h"
|
||||
#include "storage/buf_internals.h"
|
||||
#include "storage/bufmgr.h"
|
||||
#include "catalog/pg_type.h"
|
||||
#include "catalog/namespace.h"
|
||||
#include "replication/walsender.h"
|
||||
#include "funcapi.h"
|
||||
#include "miscadmin.h"
|
||||
#include "access/htup_details.h"
|
||||
#include "utils/pg_lsn.h"
|
||||
#include "utils/guc.h"
|
||||
#include "utils/wait_event.h"
|
||||
#include "utils/rel.h"
|
||||
#include "utils/varlena.h"
|
||||
#include "utils/builtins.h"
|
||||
|
||||
#include "neon.h"
|
||||
#include "walproposer.h"
|
||||
#include "pagestore_client.h"
|
||||
#include "control_plane_connector.h"
|
||||
|
||||
#include "libpq-fe.h"
|
||||
#include "libpq/pqformat.h"
|
||||
#include "libpq/libpq.h"
|
||||
|
||||
PG_MODULE_MAGIC;
|
||||
void _PG_init(void);
|
||||
|
||||
@@ -46,6 +58,7 @@ _PG_init(void)
|
||||
PG_FUNCTION_INFO_V1(pg_cluster_size);
|
||||
PG_FUNCTION_INFO_V1(backpressure_lsns);
|
||||
PG_FUNCTION_INFO_V1(backpressure_throttling_time);
|
||||
PG_FUNCTION_INFO_V1(copy_from);
|
||||
|
||||
Datum
|
||||
pg_cluster_size(PG_FUNCTION_ARGS)
|
||||
@@ -91,3 +104,281 @@ backpressure_throttling_time(PG_FUNCTION_ARGS)
|
||||
{
|
||||
PG_RETURN_UINT64(BackpressureThrottlingTime());
|
||||
}
|
||||
|
||||
|
||||
#define N_RAW_PAGE_COLUMNS 4
|
||||
#define COPY_FETCH_COUNT 16
|
||||
|
||||
|
||||
static void
|
||||
report_error(int elevel, PGresult *res, PGconn *conn,
|
||||
bool clear, const char *sql)
|
||||
{
|
||||
/* If requested, PGresult must be released before leaving this function. */
|
||||
PG_TRY();
|
||||
{
|
||||
char *diag_sqlstate = PQresultErrorField(res, PG_DIAG_SQLSTATE);
|
||||
char *message_primary = PQresultErrorField(res, PG_DIAG_MESSAGE_PRIMARY);
|
||||
char *message_detail = PQresultErrorField(res, PG_DIAG_MESSAGE_DETAIL);
|
||||
char *message_hint = PQresultErrorField(res, PG_DIAG_MESSAGE_HINT);
|
||||
char *message_context = PQresultErrorField(res, PG_DIAG_CONTEXT);
|
||||
int sqlstate;
|
||||
|
||||
if (diag_sqlstate)
|
||||
sqlstate = MAKE_SQLSTATE(diag_sqlstate[0],
|
||||
diag_sqlstate[1],
|
||||
diag_sqlstate[2],
|
||||
diag_sqlstate[3],
|
||||
diag_sqlstate[4]);
|
||||
else
|
||||
sqlstate = ERRCODE_CONNECTION_FAILURE;
|
||||
|
||||
/*
|
||||
* If we don't get a message from the PGresult, try the PGconn. This
|
||||
* is needed because for connection-level failures, PQexec may just
|
||||
* return NULL, not a PGresult at all.
|
||||
*/
|
||||
if (message_primary == NULL)
|
||||
message_primary = pchomp(PQerrorMessage(conn));
|
||||
|
||||
ereport(elevel,
|
||||
(errcode(sqlstate),
|
||||
(message_primary != NULL && message_primary[0] != '\0') ?
|
||||
errmsg_internal("%s", message_primary) :
|
||||
errmsg("could not obtain message string for remote error"),
|
||||
message_detail ? errdetail_internal("%s", message_detail) : 0,
|
||||
message_hint ? errhint("%s", message_hint) : 0,
|
||||
message_context ? errcontext("%s", message_context) : 0,
|
||||
sql ? errcontext("remote SQL command: %s", sql) : 0));
|
||||
}
|
||||
PG_FINALLY();
|
||||
{
|
||||
if (clear)
|
||||
PQclear(res);
|
||||
}
|
||||
PG_END_TRY();
|
||||
}
|
||||
|
||||
static PGresult *
|
||||
get_result(PGconn *conn, const char *query)
|
||||
{
|
||||
PGresult *volatile last_res = NULL;
|
||||
|
||||
/* In what follows, do not leak any PGresults on an error. */
|
||||
PG_TRY();
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
PGresult *res;
|
||||
|
||||
while (PQisBusy(conn))
|
||||
{
|
||||
int wc;
|
||||
|
||||
/* Sleep until there's something to do */
|
||||
wc = WaitLatchOrSocket(MyLatch,
|
||||
WL_LATCH_SET | WL_SOCKET_READABLE |
|
||||
WL_EXIT_ON_PM_DEATH,
|
||||
PQsocket(conn),
|
||||
-1L, PG_WAIT_EXTENSION);
|
||||
ResetLatch(MyLatch);
|
||||
|
||||
CHECK_FOR_INTERRUPTS();
|
||||
|
||||
/* Data available in socket? */
|
||||
if (wc & WL_SOCKET_READABLE)
|
||||
{
|
||||
if (!PQconsumeInput(conn))
|
||||
report_error(ERROR, NULL, conn, false, query);
|
||||
}
|
||||
}
|
||||
|
||||
res = PQgetResult(conn);
|
||||
if (res == NULL)
|
||||
break; /* query is complete */
|
||||
|
||||
PQclear(last_res);
|
||||
last_res = res;
|
||||
}
|
||||
}
|
||||
PG_CATCH();
|
||||
{
|
||||
PQclear(last_res);
|
||||
PG_RE_THROW();
|
||||
}
|
||||
PG_END_TRY();
|
||||
|
||||
return last_res;
|
||||
}
|
||||
|
||||
#define CREATE_COPYDATA_FUNC "\
|
||||
create or replace function copydata() returns setof record as $$ \
|
||||
declare \
|
||||
relsize integer; \
|
||||
total_relsize integer; \
|
||||
content bytea; \
|
||||
r record; \
|
||||
fork text; \
|
||||
relname text; \
|
||||
pagesize integer; \
|
||||
begin \
|
||||
pagesize = current_setting('block_size'); \
|
||||
for r in select oid,reltoastrelid from pg_class where relnamespace not in (select oid from pg_namespace where nspname in ('pg_catalog','pg_toast','information_schema')) \
|
||||
loop \
|
||||
relname = r.oid::regclass::text; \
|
||||
total_relsize = 0; \
|
||||
foreach fork in array array['main','vm','fsm'] \
|
||||
loop \
|
||||
relsize = pg_relation_size(r.oid, fork)/pagesize; \
|
||||
total_relsize = total_relsize + relsize; \
|
||||
for p in 1..relsize \
|
||||
loop \
|
||||
content = get_raw_page(relname, fork, p-1); \
|
||||
return next row(relname,fork,p-1,content); \
|
||||
end loop; \
|
||||
end loop; \
|
||||
if total_relsize <> 0 and r.reltoastrelid <> 0 then \
|
||||
foreach relname in array array ['pg_toast.pg_toast_'||r.oid, 'pg_toast.pg_toast_'||r.oid||'_index'] \
|
||||
loop \
|
||||
foreach fork in array array['main','vm','fsm'] \
|
||||
loop \
|
||||
relsize = pg_relation_size(relname, fork)/pagesize; \
|
||||
for p in 1..relsize \
|
||||
loop \
|
||||
content = get_raw_page(relname, fork, p-1); \
|
||||
return next row(relname,fork,p-1,content); \
|
||||
end loop; \
|
||||
end loop; \
|
||||
end loop; \
|
||||
end if; \
|
||||
end loop; \
|
||||
end; \
|
||||
$$ language plpgsql"
|
||||
|
||||
Datum
|
||||
copy_from(PG_FUNCTION_ARGS)
|
||||
{
|
||||
char const* conninfo = PG_GETARG_CSTRING(0);
|
||||
PGconn* conn;
|
||||
char const* declare_cursor = "declare copy_data_cursor no scroll cursor for select * from copydata() as raw_page(relid text, fork text, blkno integer, content bytea)";
|
||||
char* fetch_cursor = psprintf("fetch forward %d copy_data_cursor", COPY_FETCH_COUNT);
|
||||
char const* close_cursor = "close copy_data_cursor";
|
||||
char const* vacuum_freeze = "vacuum freeze";
|
||||
char *content;
|
||||
char const* relname;
|
||||
BlockNumber blkno;
|
||||
ForkNumber forknum;
|
||||
BlockNumber prev_blkno = InvalidBlockNumber;
|
||||
RangeVar *relrv;
|
||||
Relation rel = NULL;
|
||||
BlockNumber rel_size;
|
||||
int64_t total = 0;
|
||||
PGresult *res;
|
||||
char blkno_buf[4];
|
||||
int n_tuples;
|
||||
Buffer buf;
|
||||
char* toast_rel_name;
|
||||
Oid relid = InvalidOid;
|
||||
|
||||
/* Connect to the source database */
|
||||
conn = PQconnectdb(conninfo);
|
||||
if (!conn || PQstatus(conn) != CONNECTION_OK)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION),
|
||||
errmsg("could not connect to server \"%s\"",
|
||||
conninfo),
|
||||
errdetail_internal("%s", pchomp(PQerrorMessage(conn)))));
|
||||
|
||||
/* First create store procedure (assumes that pageinspector extension is already installed) */
|
||||
res = PQexec(conn, CREATE_COPYDATA_FUNC);
|
||||
if (res == NULL || PQresultStatus(res) != PGRES_COMMAND_OK)
|
||||
report_error(ERROR, res, conn, true, CREATE_COPYDATA_FUNC);
|
||||
PQclear(res);
|
||||
|
||||
/* Freeze all tables to prevent problems with XID mapping */
|
||||
res = PQexec(conn, vacuum_freeze);
|
||||
if (res == NULL || PQresultStatus(res) != PGRES_COMMAND_OK)
|
||||
report_error(ERROR, res, conn, true, vacuum_freeze);
|
||||
PQclear(res);
|
||||
|
||||
/* Start transaction to use cursor */
|
||||
res = PQexec(conn, "BEGIN");
|
||||
if (res == NULL || PQresultStatus(res) != PGRES_COMMAND_OK)
|
||||
report_error(ERROR, res, conn, true, "BEGIN");
|
||||
PQclear(res);
|
||||
|
||||
/* Declare cursor (we have to use cursor to avoid materializing all database in memory) */
|
||||
res = PQexec(conn, declare_cursor);
|
||||
if (res == NULL || PQresultStatus(res) != PGRES_COMMAND_OK)
|
||||
report_error(ERROR, res, conn, true, declare_cursor);
|
||||
PQclear(res);
|
||||
|
||||
/* Get database data */
|
||||
while ((res = PQexecParams(conn, fetch_cursor, 0, NULL, NULL, NULL, NULL, 1)) != NULL)
|
||||
{
|
||||
if (PQresultStatus(res) != PGRES_TUPLES_OK)
|
||||
report_error(ERROR, res, conn, true, fetch_cursor);
|
||||
|
||||
n_tuples = PQntuples(res);
|
||||
if (PQnfields(res) != 4)
|
||||
elog(ERROR, "unexpected result from copydata()");
|
||||
|
||||
for (int i = 0; i < n_tuples; i++)
|
||||
{
|
||||
relname = PQgetvalue(res, i, 0);
|
||||
forknum = forkname_to_number(PQgetvalue(res, i, 1));
|
||||
memcpy(&blkno, PQgetvalue(res, i, 2), sizeof(BlockNumber));
|
||||
blkno = pg_ntoh32(blkno);
|
||||
content = (char*)PQgetvalue(res, i, 3);
|
||||
|
||||
if (blkno <= prev_blkno)
|
||||
{
|
||||
if (forknum == MAIN_FORKNUM)
|
||||
{
|
||||
char* dst_rel_name = strncmp(relname, "pg_toast.", 9) == 0
|
||||
/* Construct correct TOAST table name */
|
||||
? psprintf("pg_toast.pg_toast_%u%s",
|
||||
relid,
|
||||
strcmp(relname + strlen(relname) - 5, "index") == 0 ? "_index" : "")
|
||||
: (char*)relname;
|
||||
if (rel)
|
||||
relation_close(rel, AccessExclusiveLock);
|
||||
relrv = makeRangeVarFromNameList(textToQualifiedNameList(cstring_to_text(dst_rel_name)));
|
||||
rel = relation_openrv(relrv, AccessExclusiveLock);
|
||||
if (dst_rel_name != relname)
|
||||
pfree(dst_rel_name);
|
||||
else
|
||||
relid = RelationGetRelid(rel);
|
||||
}
|
||||
rel_size = RelationGetNumberOfBlocksInFork(rel, forknum);
|
||||
}
|
||||
buf = ReadBufferExtended(rel, forknum, blkno < rel_size ? blkno : P_NEW, RBM_ZERO_AND_LOCK, NULL);
|
||||
MarkBufferDirty(buf);
|
||||
memcpy(BufferGetPage(buf), content, BLCKSZ);
|
||||
log_newpage_buffer(buf, forknum == MAIN_FORKNUM);
|
||||
UnlockReleaseBuffer(buf);
|
||||
|
||||
total += 1;
|
||||
prev_blkno = blkno;
|
||||
}
|
||||
PQclear(res);
|
||||
if (n_tuples < COPY_FETCH_COUNT)
|
||||
break;
|
||||
}
|
||||
res = PQexec(conn, close_cursor);
|
||||
if (res == NULL || PQresultStatus(res) != PGRES_COMMAND_OK)
|
||||
report_error(ERROR, res, conn, true, close_cursor);
|
||||
PQclear(res);
|
||||
|
||||
if (rel)
|
||||
relation_close(rel, AccessExclusiveLock);
|
||||
|
||||
/* Complete transaction */
|
||||
res = PQexec(conn, "END");
|
||||
if (res == NULL || PQresultStatus(res) != PGRES_COMMAND_OK)
|
||||
report_error(ERROR, res, conn, true, "END");
|
||||
PQclear(res);
|
||||
|
||||
PQfinish(conn);
|
||||
PG_RETURN_INT64(total);
|
||||
}
|
||||
|
||||
@@ -61,6 +61,8 @@ PAGESERVER_GLOBAL_METRICS: Tuple[str, ...] = (
|
||||
"pageserver_materialized_cache_hits_direct_total",
|
||||
"pageserver_page_cache_read_hits_total",
|
||||
"pageserver_page_cache_read_accesses_total",
|
||||
"pageserver_page_cache_size_current_bytes",
|
||||
"pageserver_page_cache_size_max_bytes",
|
||||
"pageserver_getpage_reconstruct_seconds_bucket",
|
||||
"pageserver_getpage_reconstruct_seconds_count",
|
||||
"pageserver_getpage_reconstruct_seconds_sum",
|
||||
|
||||
Vendored
+1
-1
Submodule vendor/postgres-v14 updated: 1144aee166...a2daebc6b4
Vendored
+1
-1
Submodule vendor/postgres-v15 updated: 1984832c74...2df2ce3744
Reference in New Issue
Block a user