NEON: Finish Zenith->Neon rename (#12566)

Even though we're now part of Databricks, let's at least make this part
consistent.

## Summary of changes

- PG14: https://github.com/neondatabase/postgres/pull/669
- PG15: https://github.com/neondatabase/postgres/pull/670
- PG16: https://github.com/neondatabase/postgres/pull/671
- PG17: https://github.com/neondatabase/postgres/pull/672

---------

Co-authored-by: Arpad Müller <arpad-m@users.noreply.github.com>
This commit is contained in:
Matthias van de Meent
2025-07-11 20:56:39 +02:00
committed by GitHub
parent 63ca084696
commit 4566b12a22
14 changed files with 84 additions and 53 deletions

View File

@@ -1040,6 +1040,8 @@ impl ComputeNode {
PageserverProtocol::Grpc => self.try_get_basebackup_grpc(spec, lsn)?, PageserverProtocol::Grpc => self.try_get_basebackup_grpc(spec, lsn)?,
}; };
self.fix_zenith_signal_neon_signal()?;
let mut state = self.state.lock().unwrap(); let mut state = self.state.lock().unwrap();
state.metrics.pageserver_connect_micros = state.metrics.pageserver_connect_micros =
connected.duration_since(started).as_micros() as u64; connected.duration_since(started).as_micros() as u64;
@@ -1049,6 +1051,27 @@ impl ComputeNode {
Ok(()) Ok(())
} }
/// Move the Zenith signal file to Neon signal file location.
/// This makes Compute compatible with older PageServers that don't yet
/// know about the Zenith->Neon rename.
fn fix_zenith_signal_neon_signal(&self) -> Result<()> {
let datadir = Path::new(&self.params.pgdata);
let neonsig = datadir.join("neon.signal");
if neonsig.is_file() {
return Ok(());
}
let zenithsig = datadir.join("zenith.signal");
if zenithsig.is_file() {
fs::copy(zenithsig, neonsig)?;
}
Ok(())
}
/// Fetches a basebackup via gRPC. The connstring must use grpc://. Returns the timestamp when /// Fetches a basebackup via gRPC. The connstring must use grpc://. Returns the timestamp when
/// the connection was established, and the (compressed) size of the basebackup. /// the connection was established, and the (compressed) size of the basebackup.
fn try_get_basebackup_grpc(&self, spec: &ParsedSpec, lsn: Lsn) -> Result<(Instant, usize)> { fn try_get_basebackup_grpc(&self, spec: &ParsedSpec, lsn: Lsn) -> Result<(Instant, usize)> {

View File

@@ -32,7 +32,8 @@
//! config.json - passed to `compute_ctl` //! config.json - passed to `compute_ctl`
//! pgdata/ //! pgdata/
//! postgresql.conf - copy of postgresql.conf created by `compute_ctl` //! postgresql.conf - copy of postgresql.conf created by `compute_ctl`
//! zenith.signal //! neon.signal
//! zenith.signal - copy of neon.signal, for backward compatibility
//! <other PostgreSQL files> //! <other PostgreSQL files>
//! ``` //! ```
//! //!

View File

@@ -129,9 +129,10 @@ segment to bootstrap the WAL writing, but it doesn't contain the checkpoint reco
changes in xlog.c, to allow starting the compute node without reading the last checkpoint record changes in xlog.c, to allow starting the compute node without reading the last checkpoint record
from WAL. from WAL.
This includes code to read the `zenith.signal` file, which tells the startup code the LSN to start This includes code to read the `neon.signal` (also `zenith.signal`) file, which tells the startup
at. When the `zenith.signal` file is present, the startup uses that LSN instead of the last code the LSN to start at. When the `neon.signal` file is present, the startup uses that LSN
checkpoint's LSN. The system is known to be consistent at that LSN, without any WAL redo. instead of the last checkpoint's LSN. The system is known to be consistent at that LSN, without
any WAL redo.
### How to get rid of the patch ### How to get rid of the patch

View File

@@ -114,7 +114,7 @@ where
// Compute postgres doesn't have any previous WAL files, but the first // Compute postgres doesn't have any previous WAL files, but the first
// record that it's going to write needs to include the LSN of the // record that it's going to write needs to include the LSN of the
// previous record (xl_prev). We include prev_record_lsn in the // previous record (xl_prev). We include prev_record_lsn in the
// "zenith.signal" file, so that postgres can read it during startup. // "neon.signal" file, so that postgres can read it during startup.
// //
// We don't keep full history of record boundaries in the page server, // We don't keep full history of record boundaries in the page server,
// however, only the predecessor of the latest record on each // however, only the predecessor of the latest record on each
@@ -751,34 +751,39 @@ where
// //
// Add generated pg_control file and bootstrap WAL segment. // Add generated pg_control file and bootstrap WAL segment.
// Also send zenith.signal file with extra bootstrap data. // Also send neon.signal and zenith.signal file with extra bootstrap data.
// //
async fn add_pgcontrol_file( async fn add_pgcontrol_file(
&mut self, &mut self,
pg_control_bytes: Bytes, pg_control_bytes: Bytes,
system_identifier: u64, system_identifier: u64,
) -> Result<(), BasebackupError> { ) -> Result<(), BasebackupError> {
// add zenith.signal file // add neon.signal file
let mut zenith_signal = String::new(); let mut neon_signal = String::new();
if self.prev_record_lsn == Lsn(0) { if self.prev_record_lsn == Lsn(0) {
if self.timeline.is_ancestor_lsn(self.lsn) { if self.timeline.is_ancestor_lsn(self.lsn) {
write!(zenith_signal, "PREV LSN: none") write!(neon_signal, "PREV LSN: none")
.map_err(|e| BasebackupError::Server(e.into()))?; .map_err(|e| BasebackupError::Server(e.into()))?;
} else { } else {
write!(zenith_signal, "PREV LSN: invalid") write!(neon_signal, "PREV LSN: invalid")
.map_err(|e| BasebackupError::Server(e.into()))?; .map_err(|e| BasebackupError::Server(e.into()))?;
} }
} else { } else {
write!(zenith_signal, "PREV LSN: {}", self.prev_record_lsn) write!(neon_signal, "PREV LSN: {}", self.prev_record_lsn)
.map_err(|e| BasebackupError::Server(e.into()))?; .map_err(|e| BasebackupError::Server(e.into()))?;
} }
self.ar
.append( // TODO: Remove zenith.signal once all historical computes have been replaced
&new_tar_header("zenith.signal", zenith_signal.len() as u64)?, // ... and thus support the neon.signal file.
zenith_signal.as_bytes(), for signalfilename in ["neon.signal", "zenith.signal"] {
) self.ar
.await .append(
.map_err(|e| BasebackupError::Client(e, "add_pgcontrol_file,zenith.signal"))?; &new_tar_header(signalfilename, neon_signal.len() as u64)?,
neon_signal.as_bytes(),
)
.await
.map_err(|e| BasebackupError::Client(e, "add_pgcontrol_file,neon.signal"))?;
}
//send pg_control //send pg_control
let header = new_tar_header("global/pg_control", pg_control_bytes.len() as u64)?; let header = new_tar_header("global/pg_control", pg_control_bytes.len() as u64)?;

View File

@@ -610,13 +610,13 @@ async fn import_file(
debug!("imported twophase file"); debug!("imported twophase file");
} else if file_path.starts_with("pg_wal") { } else if file_path.starts_with("pg_wal") {
debug!("found wal file in base section. ignore it"); debug!("found wal file in base section. ignore it");
} else if file_path.starts_with("zenith.signal") { } else if file_path.starts_with("zenith.signal") || file_path.starts_with("neon.signal") {
// Parse zenith signal file to set correct previous LSN // Parse zenith signal file to set correct previous LSN
let bytes = read_all_bytes(reader).await?; let bytes = read_all_bytes(reader).await?;
// zenith.signal format is "PREV LSN: prev_lsn" // neon.signal format is "PREV LSN: prev_lsn"
// TODO write serialization and deserialization in the same place. // TODO write serialization and deserialization in the same place.
let zenith_signal = std::str::from_utf8(&bytes)?.trim(); let neon_signal = std::str::from_utf8(&bytes)?.trim();
let prev_lsn = match zenith_signal { let prev_lsn = match neon_signal {
"PREV LSN: none" => Lsn(0), "PREV LSN: none" => Lsn(0),
"PREV LSN: invalid" => Lsn(0), "PREV LSN: invalid" => Lsn(0),
other => { other => {
@@ -624,17 +624,17 @@ async fn import_file(
split[1] split[1]
.trim() .trim()
.parse::<Lsn>() .parse::<Lsn>()
.context("can't parse zenith.signal")? .context("can't parse neon.signal")?
} }
}; };
// zenith.signal is not necessarily the last file, that we handle // neon.signal is not necessarily the last file, that we handle
// but it is ok to call `finish_write()`, because final `modification.commit()` // but it is ok to call `finish_write()`, because final `modification.commit()`
// will update lsn once more to the final one. // will update lsn once more to the final one.
let writer = modification.tline.writer().await; let writer = modification.tline.writer().await;
writer.finish_write(prev_lsn); writer.finish_write(prev_lsn);
debug!("imported zenith signal {}", prev_lsn); debug!("imported neon signal {}", prev_lsn);
} else if file_path.starts_with("pg_tblspc") { } else if file_path.starts_with("pg_tblspc") {
// TODO Backups exported from neon won't have pg_tblspc, but we will need // TODO Backups exported from neon won't have pg_tblspc, but we will need
// this to import arbitrary postgres databases. // this to import arbitrary postgres databases.

View File

@@ -236,13 +236,13 @@ clear_buffer_cache(PG_FUNCTION_ARGS)
bool save_neon_test_evict; bool save_neon_test_evict;
/* /*
* Temporarily set the zenith_test_evict GUC, so that when we pin and * Temporarily set the neon_test_evict GUC, so that when we pin and
* unpin a buffer, the buffer is evicted. We use that hack to evict all * unpin a buffer, the buffer is evicted. We use that hack to evict all
* buffers, as there is no explicit "evict this buffer" function in the * buffers, as there is no explicit "evict this buffer" function in the
* buffer manager. * buffer manager.
*/ */
save_neon_test_evict = zenith_test_evict; save_neon_test_evict = neon_test_evict;
zenith_test_evict = true; neon_test_evict = true;
PG_TRY(); PG_TRY();
{ {
/* Scan through all the buffers */ /* Scan through all the buffers */
@@ -273,7 +273,7 @@ clear_buffer_cache(PG_FUNCTION_ARGS)
/* /*
* Pin the buffer, and release it again. Because we have * Pin the buffer, and release it again. Because we have
* zenith_test_evict==true, this will evict the page from the * neon_test_evict==true, this will evict the page from the
* buffer cache if no one else is holding a pin on it. * buffer cache if no one else is holding a pin on it.
*/ */
if (isvalid) if (isvalid)
@@ -286,7 +286,7 @@ clear_buffer_cache(PG_FUNCTION_ARGS)
PG_FINALLY(); PG_FINALLY();
{ {
/* restore the GUC */ /* restore the GUC */
zenith_test_evict = save_neon_test_evict; neon_test_evict = save_neon_test_evict;
} }
PG_END_TRY(); PG_END_TRY();

View File

@@ -2953,17 +2953,17 @@ XmlTableBuilderData
YYLTYPE YYLTYPE
YYSTYPE YYSTYPE
YY_BUFFER_STATE YY_BUFFER_STATE
ZenithErrorResponse NeonErrorResponse
ZenithExistsRequest NeonExistsRequest
ZenithExistsResponse NeonExistsResponse
ZenithGetPageRequest NeonGetPageRequest
ZenithGetPageResponse NeonGetPageResponse
ZenithMessage NeonMessage
ZenithMessageTag NeonMessageTag
ZenithNblocksRequest NeonNblocksRequest
ZenithNblocksResponse NeonNblocksResponse
ZenithRequest NeonRequest
ZenithResponse NeonResponse
_SPI_connection _SPI_connection
_SPI_plan _SPI_plan
__AssignProcessToJobObject __AssignProcessToJobObject

View File

@@ -5409,6 +5409,7 @@ SKIP_FILES = frozenset(
( (
"pg_internal.init", "pg_internal.init",
"pg.log", "pg.log",
"neon.signal",
"zenith.signal", "zenith.signal",
"pg_hba.conf", "pg_hba.conf",
"postgresql.conf", "postgresql.conf",

View File

@@ -209,9 +209,9 @@ def test_ancestor_detach_branched_from(
client.timeline_delete(env.initial_tenant, env.initial_timeline) client.timeline_delete(env.initial_tenant, env.initial_timeline)
wait_timeline_detail_404(client, env.initial_tenant, env.initial_timeline) wait_timeline_detail_404(client, env.initial_tenant, env.initial_timeline)
# because we do the fullbackup from ancestor at the branch_lsn, the zenith.signal is always different # because we do the fullbackup from ancestor at the branch_lsn, the neon.signal and/or zenith.signal is always
# as there is always "PREV_LSN: invalid" for "before" # different as there is always "PREV_LSN: invalid" for "before"
skip_files = {"zenith.signal"} skip_files = {"zenith.signal", "neon.signal"}
assert_pageserver_backups_equal(fullbackup_before, fullbackup_after, skip_files) assert_pageserver_backups_equal(fullbackup_before, fullbackup_after, skip_files)
@@ -767,7 +767,7 @@ def test_compaction_induced_by_detaches_in_history(
env.pageserver, env.initial_tenant, branch_timeline_id, branch_lsn, fullbackup_after env.pageserver, env.initial_tenant, branch_timeline_id, branch_lsn, fullbackup_after
) )
# we don't need to skip any files, because zenith.signal will be identical # we don't need to skip any files, because neon.signal will be identical
assert_pageserver_backups_equal(fullbackup_before, fullbackup_after, set()) assert_pageserver_backups_equal(fullbackup_before, fullbackup_after, set())

View File

@@ -1,18 +1,18 @@
{ {
"v17": [ "v17": [
"17.5", "17.5",
"db424d42d748f8ad91ac00e28db2c7f2efa42f7f" "353c725b0c76cc82b15af21d8360d03391dc6814"
], ],
"v16": [ "v16": [
"16.9", "16.9",
"7a4c0eacaeb9b97416542fa19103061c166460b1" "e08c8d5f1576ca0487d14d154510499c5f12adfb"
], ],
"v15": [ "v15": [
"15.13", "15.13",
"8c3249f36c7df6ac0efb8ee9f1baf4aa1b83e5c9" "afd46987f3da50c9146a8aa59380052df0862c06"
], ],
"v14": [ "v14": [
"14.18", "14.18",
"9085654ee8022d5cc4ca719380a1dc53e5e3246f" "8ce1f52303aec29e098309347b57c01a1962e221"
] ]
} }