mirror of
https://github.com/neondatabase/neon.git
synced 2026-01-14 17:02:56 +00:00
Redefine the timeline_gc API to not perform a forced compaction
Previously, the /v1/tenant/:tenant_id/timeline/:timeline_id/do_gc API call performed a flush and compaction on the timeline before GC. Change it not to do that, and change all the tests that used that API to perform compaction explicitly. The compaction happens at a slightly different point now. Previously, the code performed the `refresh_gc_info_internal` step first, and only then did compaction on all the timelines. I don't think that was what was originally intended here. Presumably the idea with compaction was to make some old layer files available for GC. But if we're going to flush the current in-memory layer to disk, surely you would want to include the newly-written layer in the compaction too. I guess this didn't make any difference to the tests in practice, but in any case, the tests now perform the flush and compaction before any of the GC steps. Some of the tests might not need the compaction at all, but I didn't try hard to determine which ones might need it. I left it out from a few tests that intentionally tested calling do_gc with an invalid tenant or timeline ID, though.
This commit is contained in:
committed by
Heikki Linnakangas
parent
70ce01d84d
commit
6dec85b19d
@@ -1164,7 +1164,6 @@ impl Tenant {
|
|||||||
target_timeline_id: Option<TimelineId>,
|
target_timeline_id: Option<TimelineId>,
|
||||||
horizon: u64,
|
horizon: u64,
|
||||||
pitr: Duration,
|
pitr: Duration,
|
||||||
checkpoint_before_gc: bool,
|
|
||||||
) -> anyhow::Result<GcResult> {
|
) -> anyhow::Result<GcResult> {
|
||||||
anyhow::ensure!(
|
anyhow::ensure!(
|
||||||
self.is_active(),
|
self.is_active(),
|
||||||
@@ -1179,7 +1178,7 @@ impl Tenant {
|
|||||||
let _timer = STORAGE_TIME
|
let _timer = STORAGE_TIME
|
||||||
.with_label_values(&["gc", &self.tenant_id.to_string(), &timeline_str])
|
.with_label_values(&["gc", &self.tenant_id.to_string(), &timeline_str])
|
||||||
.start_timer();
|
.start_timer();
|
||||||
self.gc_iteration_internal(target_timeline_id, horizon, pitr, checkpoint_before_gc)
|
self.gc_iteration_internal(target_timeline_id, horizon, pitr)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1778,7 +1777,6 @@ impl Tenant {
|
|||||||
target_timeline_id: Option<TimelineId>,
|
target_timeline_id: Option<TimelineId>,
|
||||||
horizon: u64,
|
horizon: u64,
|
||||||
pitr: Duration,
|
pitr: Duration,
|
||||||
checkpoint_before_gc: bool,
|
|
||||||
) -> anyhow::Result<GcResult> {
|
) -> anyhow::Result<GcResult> {
|
||||||
let mut totals: GcResult = Default::default();
|
let mut totals: GcResult = Default::default();
|
||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
@@ -1805,18 +1803,6 @@ impl Tenant {
|
|||||||
// made.
|
// made.
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If requested, force flush all in-memory layers to disk first,
|
|
||||||
// so that they too can be garbage collected. That's
|
|
||||||
// used in tests, so we want as deterministic results as possible.
|
|
||||||
if checkpoint_before_gc {
|
|
||||||
timeline.checkpoint(CheckpointConfig::Forced).await?;
|
|
||||||
info!(
|
|
||||||
"timeline {} checkpoint_before_gc done",
|
|
||||||
timeline.timeline_id
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let result = timeline.gc().await?;
|
let result = timeline.gc().await?;
|
||||||
totals += result;
|
totals += result;
|
||||||
}
|
}
|
||||||
@@ -2877,7 +2863,7 @@ mod tests {
|
|||||||
// and compaction works. But it does set the 'cutoff' point so that the cross check
|
// and compaction works. But it does set the 'cutoff' point so that the cross check
|
||||||
// below should fail.
|
// below should fail.
|
||||||
tenant
|
tenant
|
||||||
.gc_iteration(Some(TIMELINE_ID), 0x10, Duration::ZERO, false)
|
.gc_iteration(Some(TIMELINE_ID), 0x10, Duration::ZERO)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// try to branch at lsn 25, should fail because we already garbage collected the data
|
// try to branch at lsn 25, should fail because we already garbage collected the data
|
||||||
@@ -2933,7 +2919,7 @@ mod tests {
|
|||||||
let tline = repo.create_empty_timeline(TIMELINE_ID, Lsn(0), DEFAULT_PG_VERSION)?;
|
let tline = repo.create_empty_timeline(TIMELINE_ID, Lsn(0), DEFAULT_PG_VERSION)?;
|
||||||
make_some_layers(tline.as_ref(), Lsn(0x20)).await?;
|
make_some_layers(tline.as_ref(), Lsn(0x20)).await?;
|
||||||
|
|
||||||
repo.gc_iteration(Some(TIMELINE_ID), 0x10, Duration::ZERO, false)?;
|
repo.gc_iteration(Some(TIMELINE_ID), 0x10, Duration::ZERO)?;
|
||||||
let latest_gc_cutoff_lsn = tline.get_latest_gc_cutoff_lsn();
|
let latest_gc_cutoff_lsn = tline.get_latest_gc_cutoff_lsn();
|
||||||
assert!(*latest_gc_cutoff_lsn > Lsn(0x25));
|
assert!(*latest_gc_cutoff_lsn > Lsn(0x25));
|
||||||
match tline.get(*TEST_KEY, Lsn(0x25)) {
|
match tline.get(*TEST_KEY, Lsn(0x25)) {
|
||||||
@@ -2960,7 +2946,7 @@ mod tests {
|
|||||||
.expect("Should have a local timeline");
|
.expect("Should have a local timeline");
|
||||||
// this removes layers before lsn 40 (50 minus 10), so there are two remaining layers, image and delta for 31-50
|
// this removes layers before lsn 40 (50 minus 10), so there are two remaining layers, image and delta for 31-50
|
||||||
tenant
|
tenant
|
||||||
.gc_iteration(Some(TIMELINE_ID), 0x10, Duration::ZERO, false)
|
.gc_iteration(Some(TIMELINE_ID), 0x10, Duration::ZERO)
|
||||||
.await?;
|
.await?;
|
||||||
assert!(newtline.get(*TEST_KEY, Lsn(0x25)).is_ok());
|
assert!(newtline.get(*TEST_KEY, Lsn(0x25)).is_ok());
|
||||||
|
|
||||||
@@ -2985,7 +2971,7 @@ mod tests {
|
|||||||
|
|
||||||
// run gc on parent
|
// run gc on parent
|
||||||
tenant
|
tenant
|
||||||
.gc_iteration(Some(TIMELINE_ID), 0x10, Duration::ZERO, false)
|
.gc_iteration(Some(TIMELINE_ID), 0x10, Duration::ZERO)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// Check that the data is still accessible on the branch.
|
// Check that the data is still accessible on the branch.
|
||||||
|
|||||||
@@ -496,7 +496,7 @@ pub async fn immediate_gc(
|
|||||||
async move {
|
async move {
|
||||||
fail::fail_point!("immediate_gc_task_pre");
|
fail::fail_point!("immediate_gc_task_pre");
|
||||||
let result = tenant
|
let result = tenant
|
||||||
.gc_iteration(Some(timeline_id), gc_horizon, pitr, true)
|
.gc_iteration(Some(timeline_id), gc_horizon, pitr)
|
||||||
.instrument(info_span!("manual_gc", tenant = %tenant_id, timeline = %timeline_id))
|
.instrument(info_span!("manual_gc", tenant = %tenant_id, timeline = %timeline_id))
|
||||||
.await;
|
.await;
|
||||||
// FIXME: `gc_iteration` can return an error for multiple reasons; we should handle it
|
// FIXME: `gc_iteration` can return an error for multiple reasons; we should handle it
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ async fn gc_loop(tenant_id: TenantId) {
|
|||||||
} else {
|
} else {
|
||||||
// Run gc
|
// Run gc
|
||||||
if gc_horizon > 0 {
|
if gc_horizon > 0 {
|
||||||
if let Err(e) = tenant.gc_iteration(None, gc_horizon, tenant.get_pitr_interval(), false).await
|
if let Err(e) = tenant.gc_iteration(None, gc_horizon, tenant.get_pitr_interval()).await
|
||||||
{
|
{
|
||||||
sleep_duration = wait_duration;
|
sleep_duration = wait_duration;
|
||||||
error!("Gc failed, retrying in {:?}: {e:?}", sleep_duration);
|
error!("Gc failed, retrying in {:?}: {e:?}", sleep_duration);
|
||||||
|
|||||||
@@ -115,6 +115,7 @@ class NeonCompare(PgCompare):
|
|||||||
return self._pg_bin
|
return self._pg_bin
|
||||||
|
|
||||||
def flush(self):
|
def flush(self):
|
||||||
|
self.pageserver_http.timeline_checkpoint(self.env.initial_tenant, self.timeline)
|
||||||
self.pageserver_http_client.timeline_gc(self.env.initial_tenant, self.timeline, 0)
|
self.pageserver_http_client.timeline_gc(self.env.initial_tenant, self.timeline, 0)
|
||||||
|
|
||||||
def compact(self):
|
def compact(self):
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ def test_branch_and_gc(neon_simple_env: NeonEnv):
|
|||||||
|
|
||||||
# Set the GC horizon so that lsn1 is inside the horizon, which means
|
# Set the GC horizon so that lsn1 is inside the horizon, which means
|
||||||
# we can create a new branch starting from lsn1.
|
# we can create a new branch starting from lsn1.
|
||||||
|
pageserver_http_client.timeline_checkpoint(tenant, timeline_main)
|
||||||
pageserver_http_client.timeline_gc(tenant, timeline_main, lsn2 - lsn1 + 1024)
|
pageserver_http_client.timeline_gc(tenant, timeline_main, lsn2 - lsn1 + 1024)
|
||||||
|
|
||||||
env.neon_cli.create_branch(
|
env.neon_cli.create_branch(
|
||||||
@@ -156,6 +157,7 @@ def test_branch_creation_before_gc(neon_simple_env: NeonEnv):
|
|||||||
# branch creation task but the individual timeline GC iteration happens *after*
|
# branch creation task but the individual timeline GC iteration happens *after*
|
||||||
# the branch creation task.
|
# the branch creation task.
|
||||||
pageserver_http_client.configure_failpoints(("before-timeline-gc", "sleep(2000)"))
|
pageserver_http_client.configure_failpoints(("before-timeline-gc", "sleep(2000)"))
|
||||||
|
pageserver_http_client.timeline_checkpoint(tenant, b0)
|
||||||
|
|
||||||
def do_gc():
|
def do_gc():
|
||||||
pageserver_http_client.timeline_gc(tenant, b0, 0)
|
pageserver_http_client.timeline_gc(tenant, b0, 0)
|
||||||
|
|||||||
@@ -109,6 +109,7 @@ def test_branch_behind(neon_env_builder: NeonEnvBuilder):
|
|||||||
|
|
||||||
# check that we cannot create branch based on garbage collected data
|
# check that we cannot create branch based on garbage collected data
|
||||||
with env.pageserver.http_client() as pageserver_http:
|
with env.pageserver.http_client() as pageserver_http:
|
||||||
|
pageserver_http.timeline_checkpoint(env.initial_tenant, timeline)
|
||||||
gc_result = pageserver_http.timeline_gc(env.initial_tenant, timeline, 0)
|
gc_result = pageserver_http.timeline_gc(env.initial_tenant, timeline, 0)
|
||||||
print_gc_result(gc_result)
|
print_gc_result(gc_result)
|
||||||
|
|
||||||
|
|||||||
@@ -35,12 +35,13 @@ async def gc(env: NeonEnv, timeline: TimelineId):
|
|||||||
|
|
||||||
loop = asyncio.get_running_loop()
|
loop = asyncio.get_running_loop()
|
||||||
|
|
||||||
|
def do_gc():
|
||||||
|
pageserver_http.timeline_checkpoint(env.initial_tenant, timeline)
|
||||||
|
pageserver_http.timeline_gc(env.initial_tenant, timeline, 0)
|
||||||
|
|
||||||
with concurrent.futures.ThreadPoolExecutor() as pool:
|
with concurrent.futures.ThreadPoolExecutor() as pool:
|
||||||
while updates_performed < updates_to_perform:
|
while updates_performed < updates_to_perform:
|
||||||
await loop.run_in_executor(
|
await loop.run_in_executor(pool, do_gc)
|
||||||
pool, lambda: pageserver_http.timeline_gc(env.initial_tenant, timeline, 0)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# At the same time, run UPDATEs and GC
|
# At the same time, run UPDATEs and GC
|
||||||
async def update_and_gc(env: NeonEnv, pg: Postgres, timeline: TimelineId):
|
async def update_and_gc(env: NeonEnv, pg: Postgres, timeline: TimelineId):
|
||||||
|
|||||||
@@ -306,6 +306,7 @@ def _import(
|
|||||||
|
|
||||||
# Check that gc works
|
# Check that gc works
|
||||||
pageserver_http = env.pageserver.http_client()
|
pageserver_http = env.pageserver.http_client()
|
||||||
|
pageserver_http.timeline_checkpoint(tenant, timeline)
|
||||||
pageserver_http.timeline_gc(tenant, timeline, 0)
|
pageserver_http.timeline_gc(tenant, timeline, 0)
|
||||||
|
|
||||||
return tar_output_file
|
return tar_output_file
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ def test_old_request_lsn(neon_env_builder: NeonEnvBuilder):
|
|||||||
# Make a lot of updates on a single row, generating a lot of WAL. Trigger
|
# Make a lot of updates on a single row, generating a lot of WAL. Trigger
|
||||||
# garbage collections so that the page server will remove old page versions.
|
# garbage collections so that the page server will remove old page versions.
|
||||||
for i in range(10):
|
for i in range(10):
|
||||||
|
pageserver_http.timeline_checkpoint(env.initial_tenant, timeline)
|
||||||
gc_result = pageserver_http.timeline_gc(env.initial_tenant, timeline, 0)
|
gc_result = pageserver_http.timeline_gc(env.initial_tenant, timeline, 0)
|
||||||
print_gc_result(gc_result)
|
print_gc_result(gc_result)
|
||||||
|
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ def test_pitr_gc(neon_env_builder: NeonEnvBuilder):
|
|||||||
|
|
||||||
# run GC
|
# run GC
|
||||||
with env.pageserver.http_client() as pageserver_http:
|
with env.pageserver.http_client() as pageserver_http:
|
||||||
|
pageserver_http.timeline_checkpoint(env.initial_tenant, timeline)
|
||||||
pageserver_http.timeline_compact(env.initial_tenant, timeline)
|
pageserver_http.timeline_compact(env.initial_tenant, timeline)
|
||||||
# perform aggressive GC. Data still should be kept because of the PITR setting.
|
# perform aggressive GC. Data still should be kept because of the PITR setting.
|
||||||
gc_result = pageserver_http.timeline_gc(env.initial_tenant, timeline, 0)
|
gc_result = pageserver_http.timeline_gc(env.initial_tenant, timeline, 0)
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ def do_gc_target(
|
|||||||
"""Hack to unblock main, see https://github.com/neondatabase/neon/issues/2211"""
|
"""Hack to unblock main, see https://github.com/neondatabase/neon/issues/2211"""
|
||||||
try:
|
try:
|
||||||
log.info("sending gc http request")
|
log.info("sending gc http request")
|
||||||
|
pageserver_http.timeline_checkpoint(tenant_id, timeline_id)
|
||||||
pageserver_http.timeline_gc(tenant_id, timeline_id, 0)
|
pageserver_http.timeline_gc(tenant_id, timeline_id, 0)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.error("do_gc failed: %s", e)
|
log.error("do_gc failed: %s", e)
|
||||||
|
|||||||
@@ -326,7 +326,6 @@ def test_timeline_physical_size_post_gc(neon_env_builder: NeonEnvBuilder):
|
|||||||
|
|
||||||
wait_for_last_flush_lsn(env, pg, env.initial_tenant, new_timeline_id)
|
wait_for_last_flush_lsn(env, pg, env.initial_tenant, new_timeline_id)
|
||||||
pageserver_http.timeline_checkpoint(env.initial_tenant, new_timeline_id)
|
pageserver_http.timeline_checkpoint(env.initial_tenant, new_timeline_id)
|
||||||
|
|
||||||
pageserver_http.timeline_gc(env.initial_tenant, new_timeline_id, gc_horizon=None)
|
pageserver_http.timeline_gc(env.initial_tenant, new_timeline_id, gc_horizon=None)
|
||||||
|
|
||||||
assert_physical_size(env, env.initial_tenant, new_timeline_id)
|
assert_physical_size(env, env.initial_tenant, new_timeline_id)
|
||||||
|
|||||||
Reference in New Issue
Block a user