mirror of
https://github.com/neondatabase/neon.git
synced 2026-01-05 20:42:54 +00:00
pageserver: return 412 PreconditionFailed in get_timestamp_of_lsn if timestamp is not found (#11491)
## Problem Now `get_timestamp_of_lsn` returns `404 NotFound` if there is no clog pages for given LSN, and it's difficult to distinguish from other 404 errors. A separate status code for this error will allow the control plane to handle this case. - Closes: https://github.com/neondatabase/neon/issues/11439 - Corresponding PR in control plane: https://github.com/neondatabase/cloud/pull/27125 ## Summary of changes - Return `412 PreconditionFailed` instead of `404 NotFound` if no timestamp is fond for given LSN. I looked briefly through the current error handling code in cloud.git and the status code change should not affect anything for the existing code. Change from the corresponding PR also looks fine and should work with the current PS status code. Additionally, here is OK to merge it from control plane team: https://github.com/neondatabase/neon/issues/11439#issuecomment-2789327552 --------- Co-authored-by: John Spray <john@neon.tech>
This commit is contained in:
@@ -212,6 +212,12 @@ paths:
|
||||
schema:
|
||||
type: string
|
||||
format: date-time
|
||||
"412":
|
||||
description: No timestamp is found for given LSN, e.g. if there had been no commits till LSN
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/PreconditionFailedError"
|
||||
|
||||
/v1/tenant/{tenant_id}/timeline/{timeline_id}/get_lsn_by_timestamp:
|
||||
parameters:
|
||||
|
||||
@@ -989,7 +989,7 @@ async fn get_lsn_by_timestamp_handler(
|
||||
if !tenant_shard_id.is_shard_zero() {
|
||||
// Requires SLRU contents, which are only stored on shard zero
|
||||
return Err(ApiError::BadRequest(anyhow!(
|
||||
"Size calculations are only available on shard zero"
|
||||
"Lsn calculations by timestamp are only available on shard zero"
|
||||
)));
|
||||
}
|
||||
|
||||
@@ -1064,7 +1064,7 @@ async fn get_timestamp_of_lsn_handler(
|
||||
if !tenant_shard_id.is_shard_zero() {
|
||||
// Requires SLRU contents, which are only stored on shard zero
|
||||
return Err(ApiError::BadRequest(anyhow!(
|
||||
"Size calculations are only available on shard zero"
|
||||
"Timestamp calculations by lsn are only available on shard zero"
|
||||
)));
|
||||
}
|
||||
|
||||
@@ -1090,8 +1090,8 @@ async fn get_timestamp_of_lsn_handler(
|
||||
.to_string();
|
||||
json_response(StatusCode::OK, time)
|
||||
}
|
||||
None => Err(ApiError::NotFound(
|
||||
anyhow::anyhow!("Timestamp for lsn {} not found", lsn).into(),
|
||||
None => Err(ApiError::PreconditionFailed(
|
||||
format!("Timestamp for lsn {} not found", lsn).into(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -691,7 +691,7 @@ impl Timeline {
|
||||
Ok(buf.get_u32_le())
|
||||
}
|
||||
|
||||
/// Get size of an SLRU segment
|
||||
/// Does the slru segment exist?
|
||||
pub(crate) async fn get_slru_segment_exists(
|
||||
&self,
|
||||
kind: SlruKind,
|
||||
@@ -844,9 +844,9 @@ impl Timeline {
|
||||
.await
|
||||
}
|
||||
|
||||
/// Obtain the possible timestamp range for the given lsn.
|
||||
/// Obtain the timestamp for the given lsn.
|
||||
///
|
||||
/// If the lsn has no timestamps, returns None. returns `(min, max, median)` if it has timestamps.
|
||||
/// If the lsn has no timestamps (e.g. no commits), returns None.
|
||||
pub(crate) async fn get_timestamp_for_lsn(
|
||||
&self,
|
||||
probe_lsn: Lsn,
|
||||
|
||||
@@ -276,3 +276,34 @@ def test_ts_of_lsn_api(neon_env_builder: NeonEnvBuilder):
|
||||
if i > 1:
|
||||
before_timestamp = tbl[i - step_size][1]
|
||||
assert timestamp >= before_timestamp, "before_timestamp before timestamp"
|
||||
|
||||
|
||||
def test_timestamp_of_lsn_empty_branch(neon_env_builder: NeonEnvBuilder):
|
||||
"""
|
||||
Test that getting the timestamp of the head LSN of a newly created branch works.
|
||||
This verifies that we don't get a 404 error when trying to get the timestamp
|
||||
of the head LSN of a branch that was just created.
|
||||
We now return a special status code 412 to indicate if there is no timestamp found for lsn.
|
||||
|
||||
Reproducer for https://github.com/neondatabase/neon/issues/11439
|
||||
"""
|
||||
env = neon_env_builder.init_start()
|
||||
|
||||
# Create a new branch
|
||||
new_timeline_id = env.create_branch("test_timestamp_of_lsn_empty_branch")
|
||||
|
||||
# Retrieve the commit LSN of the empty branch, which we have never run postgres on
|
||||
detail = env.pageserver.http_client().timeline_detail(
|
||||
tenant_id=env.initial_tenant, timeline_id=new_timeline_id
|
||||
)
|
||||
head_lsn = detail["last_record_lsn"]
|
||||
|
||||
# Verify that we get 412 status code
|
||||
with env.pageserver.http_client() as client:
|
||||
with pytest.raises(PageserverApiException) as err:
|
||||
client.timeline_get_timestamp_of_lsn(
|
||||
env.initial_tenant,
|
||||
new_timeline_id,
|
||||
head_lsn,
|
||||
)
|
||||
assert err.value.status_code == 412
|
||||
|
||||
Reference in New Issue
Block a user