diff --git a/control_plane/src/storage.rs b/control_plane/src/storage.rs index f1eaa99904..2891ef5cbb 100644 --- a/control_plane/src/storage.rs +++ b/control_plane/src/storage.rs @@ -427,6 +427,7 @@ impl PageServerNode { .map(|x| x.parse::()) .transpose() .context("Failed to parse 'max_lsn_wal_lag' as non zero integer")?, + data_checksums: Some(true), }) .send()? .error_from_body()? @@ -436,7 +437,7 @@ impl PageServerNode { .map(|id| { id.parse().with_context(|| { format!( - "Failed to parse tennat creation response as tenant id: {}", + "Failed to parse tenant creation response as tenant id: {}", id ) }) diff --git a/pageserver/src/http/models.rs b/pageserver/src/http/models.rs index c947cebcb6..be2b6e1dc6 100644 --- a/pageserver/src/http/models.rs +++ b/pageserver/src/http/models.rs @@ -38,6 +38,7 @@ pub struct TenantCreateRequest { pub walreceiver_connect_timeout: Option, pub lagging_wal_timeout: Option, pub max_lsn_wal_lag: Option, + pub data_checksums: Option, } #[serde_as] diff --git a/pageserver/src/http/openapi_spec.yml b/pageserver/src/http/openapi_spec.yml index 55f7b3c5a7..1541042efa 100644 --- a/pageserver/src/http/openapi_spec.yml +++ b/pageserver/src/http/openapi_spec.yml @@ -494,6 +494,8 @@ components: type: string compaction_threshold: type: string + data_checksums: + type: boolean TenantConfigInfo: type: object properties: diff --git a/pageserver/src/http/routes.rs b/pageserver/src/http/routes.rs index a1198051a8..158c47c679 100644 --- a/pageserver/src/http/routes.rs +++ b/pageserver/src/http/routes.rs @@ -412,6 +412,9 @@ async fn tenant_create_handler(mut request: Request) -> Result, pub max_lsn_wal_lag: Option, + pub data_checksums: Option, } impl TenantConfOpt { @@ -135,6 +141,7 @@ impl TenantConfOpt { .lagging_wal_timeout .unwrap_or(global_conf.lagging_wal_timeout), max_lsn_wal_lag: self.max_lsn_wal_lag.unwrap_or(global_conf.max_lsn_wal_lag), + data_checksums: self.data_checksums.unwrap_or(global_conf.data_checksums), } } @@ -172,6 +179,9 @@ impl TenantConfOpt { if let Some(max_lsn_wal_lag) = other.max_lsn_wal_lag { self.max_lsn_wal_lag = Some(max_lsn_wal_lag); } + if let Some(data_checksums) = other.data_checksums { + self.data_checksums = Some(data_checksums); + } } } @@ -199,6 +209,7 @@ impl TenantConf { .expect("cannot parse default walreceiver lagging wal timeout"), max_lsn_wal_lag: NonZeroU64::new(DEFAULT_MAX_WALRECEIVER_LSN_WAL_LAG) .expect("cannot parse default max walreceiver Lsn wal lag"), + data_checksums: DEFAULT_DATA_CHECKSUMS, } } @@ -229,6 +240,7 @@ impl TenantConf { .unwrap(), max_lsn_wal_lag: NonZeroU64::new(defaults::DEFAULT_MAX_WALRECEIVER_LSN_WAL_LAG) .unwrap(), + data_checksums: defaults::DEFAULT_DATA_CHECKSUMS, } } } diff --git a/pageserver/src/tenant_mgr.rs b/pageserver/src/tenant_mgr.rs index c73fed140a..84686c85a6 100644 --- a/pageserver/src/tenant_mgr.rs +++ b/pageserver/src/tenant_mgr.rs @@ -11,7 +11,7 @@ use crate::tenant_config::TenantConfOpt; use crate::thread_mgr::ThreadKind; use crate::timelines::CreateRepo; use crate::walredo::PostgresRedoManager; -use crate::{thread_mgr, timelines, walreceiver}; +use crate::{tenant_config, thread_mgr, timelines, walreceiver}; use crate::{DatadirTimelineImpl, RepositoryImpl}; use anyhow::{bail, Context}; use serde::{Deserialize, Serialize}; @@ -266,7 +266,11 @@ pub fn create_tenant_repository( Ok(None) } Entry::Vacant(v) => { - let wal_redo_manager = Arc::new(PostgresRedoManager::new(conf, tenant_id)); + let data_checksums = tenant_conf + .data_checksums + .unwrap_or(tenant_config::defaults::DEFAULT_DATA_CHECKSUMS); + let wal_redo_manager = + Arc::new(PostgresRedoManager::new(conf, data_checksums, tenant_id)); let repo = timelines::create_repo( conf, tenant_conf, @@ -567,10 +571,16 @@ fn load_local_repo( tenant_id: ZTenantId, remote_index: &RemoteIndex, ) -> anyhow::Result> { + // Restore tenant config + let tenant_conf = LayeredRepository::load_tenant_config(conf, tenant_id)?; + let mut m = tenants_state::write_tenants(); let tenant = m.entry(tenant_id).or_insert_with(|| { + let data_checksums = tenant_conf + .data_checksums + .unwrap_or(tenant_config::defaults::DEFAULT_DATA_CHECKSUMS); // Set up a WAL redo manager, for applying WAL records. - let walredo_mgr = PostgresRedoManager::new(conf, tenant_id); + let walredo_mgr = PostgresRedoManager::new(conf, data_checksums, tenant_id); // Set up an object repository, for actual data storage. let repo: Arc = Arc::new(LayeredRepository::new( @@ -588,8 +598,6 @@ fn load_local_repo( } }); - // Restore tenant config - let tenant_conf = LayeredRepository::load_tenant_config(conf, tenant_id)?; tenant.repo.update_tenant_config(tenant_conf)?; Ok(Arc::clone(&tenant.repo)) diff --git a/pageserver/src/walredo.rs b/pageserver/src/walredo.rs index 05669b851f..0ddd99b261 100644 --- a/pageserver/src/walredo.rs +++ b/pageserver/src/walredo.rs @@ -132,6 +132,7 @@ lazy_static! { pub struct PostgresRedoManager { tenantid: ZTenantId, conf: &'static PageServerConf, + data_checksums: bool, process: Mutex>, } @@ -230,11 +231,16 @@ impl PostgresRedoManager { /// /// Create a new PostgresRedoManager. /// - pub fn new(conf: &'static PageServerConf, tenantid: ZTenantId) -> PostgresRedoManager { + pub fn new( + conf: &'static PageServerConf, + data_checksums: bool, + tenantid: ZTenantId, + ) -> PostgresRedoManager { // The actual process is launched lazily, on first request. PostgresRedoManager { tenantid, conf, + data_checksums, process: Mutex::new(None), } } @@ -269,7 +275,13 @@ impl PostgresRedoManager { // Relational WAL records are applied using wal-redo-postgres let buf_tag = BufferTag { rel, blknum }; let result = process - .apply_wal_records(buf_tag, base_img, records, wal_redo_timeout) + .apply_wal_records( + buf_tag, + base_img, + records, + wal_redo_timeout, + self.data_checksums, + ) .map_err(WalRedoError::IoError); let end_time = Instant::now(); @@ -718,6 +730,7 @@ impl PostgresRedoProcess { base_img: Option, records: &[(Lsn, ZenithWalRecord)], wal_redo_timeout: Duration, + data_checksums: bool, ) -> Result { // Serialize all the messages to send the WAL redo process first. // @@ -727,7 +740,9 @@ impl PostgresRedoProcess { let mut writebuf: Vec = Vec::new(); build_begin_redo_for_block_msg(tag, &mut writebuf); if let Some(img) = base_img { - if !page_verify_checksum(&img, tag.blknum) { + // Checksums could be not stamped for old tenants, so check them only if they + // are enabled (this is controlled by per-tenant config). + if data_checksums && !page_verify_checksum(&img, tag.blknum) { return Err(std::io::Error::new( std::io::ErrorKind::InvalidData, format!("block {} of relation {} is invalid", tag.blknum, tag.rel), @@ -751,6 +766,8 @@ impl PostgresRedoProcess { ), ) })?; + // WAL records always have a checksum, check it before sending to redo process. + // It doesn't do these checks itself. if !wal_record_verify_checksum(&xlogrec, postgres_rec) { return Err(std::io::Error::new( std::io::ErrorKind::InvalidData,