diff --git a/control_plane/src/pageserver.rs b/control_plane/src/pageserver.rs index 5b5828c6ed..8df0a714ec 100644 --- a/control_plane/src/pageserver.rs +++ b/control_plane/src/pageserver.rs @@ -17,7 +17,7 @@ use std::time::Duration; use anyhow::{bail, Context}; use camino::Utf8PathBuf; -use pageserver_api::models::{self, AuxFilePolicy, TenantInfo, TimelineInfo}; +use pageserver_api::models::{self, TenantInfo, TimelineInfo}; use pageserver_api::shard::TenantShardId; use pageserver_client::mgmt_api; use postgres_backend::AuthType; @@ -399,11 +399,6 @@ impl PageServerNode { .map(serde_json::from_str) .transpose() .context("parse `timeline_get_throttle` from json")?, - switch_aux_file_policy: settings - .remove("switch_aux_file_policy") - .map(|x| x.parse::()) - .transpose() - .context("Failed to parse 'switch_aux_file_policy'")?, lsn_lease_length: settings.remove("lsn_lease_length").map(|x| x.to_string()), lsn_lease_length_for_ts: settings .remove("lsn_lease_length_for_ts") @@ -499,11 +494,6 @@ impl PageServerNode { .map(serde_json::from_str) .transpose() .context("parse `timeline_get_throttle` from json")?, - switch_aux_file_policy: settings - .remove("switch_aux_file_policy") - .map(|x| x.parse::()) - .transpose() - .context("Failed to parse 'switch_aux_file_policy'")?, lsn_lease_length: settings.remove("lsn_lease_length").map(|x| x.to_string()), lsn_lease_length_for_ts: settings .remove("lsn_lease_length_for_ts") diff --git a/libs/pageserver_api/src/config.rs b/libs/pageserver_api/src/config.rs index 896a5d8069..6b2d6cf625 100644 --- a/libs/pageserver_api/src/config.rs +++ b/libs/pageserver_api/src/config.rs @@ -250,12 +250,6 @@ pub struct TenantConfigToml { // Expresed in multiples of checkpoint distance. pub image_layer_creation_check_threshold: u8, - /// Switch to a new aux file policy. Switching this flag requires the user has not written any aux file into - /// the storage before, and this flag cannot be switched back. Otherwise there will be data corruptions. - /// There is a `last_aux_file_policy` flag which gets persisted in `index_part.json` once the first aux - /// file is written. - pub switch_aux_file_policy: crate::models::AuxFilePolicy, - /// The length for an explicit LSN lease request. /// Layers needed to reconstruct pages at LSN will not be GC-ed during this interval. #[serde(with = "humantime_serde")] @@ -475,7 +469,6 @@ impl Default for TenantConfigToml { lazy_slru_download: false, timeline_get_throttle: crate::models::ThrottleConfig::disabled(), image_layer_creation_check_threshold: DEFAULT_IMAGE_LAYER_CREATION_CHECK_THRESHOLD, - switch_aux_file_policy: crate::models::AuxFilePolicy::default_tenant_config(), lsn_lease_length: LsnLease::DEFAULT_LENGTH, lsn_lease_length_for_ts: LsnLease::DEFAULT_LENGTH_FOR_TS, } diff --git a/libs/pageserver_api/src/models.rs b/libs/pageserver_api/src/models.rs index d37f62185c..0a4992aea4 100644 --- a/libs/pageserver_api/src/models.rs +++ b/libs/pageserver_api/src/models.rs @@ -10,7 +10,6 @@ use std::{ io::{BufRead, Read}, num::{NonZeroU32, NonZeroU64, NonZeroUsize}, str::FromStr, - sync::atomic::AtomicUsize, time::{Duration, SystemTime}, }; @@ -309,7 +308,6 @@ pub struct TenantConfig { pub lazy_slru_download: Option, pub timeline_get_throttle: Option, pub image_layer_creation_check_threshold: Option, - pub switch_aux_file_policy: Option, pub lsn_lease_length: Option, pub lsn_lease_length_for_ts: Option, } @@ -350,68 +348,6 @@ pub enum AuxFilePolicy { CrossValidation, } -impl AuxFilePolicy { - pub fn is_valid_migration_path(from: Option, to: Self) -> bool { - matches!( - (from, to), - (None, _) | (Some(AuxFilePolicy::CrossValidation), AuxFilePolicy::V2) - ) - } - - /// If a tenant writes aux files without setting `switch_aux_policy`, this value will be used. - pub fn default_tenant_config() -> Self { - Self::V2 - } -} - -/// The aux file policy memory flag. Users can store `Option` into this atomic flag. 0 == unspecified. -pub struct AtomicAuxFilePolicy(AtomicUsize); - -impl AtomicAuxFilePolicy { - pub fn new(policy: Option) -> Self { - Self(AtomicUsize::new( - policy.map(AuxFilePolicy::to_usize).unwrap_or_default(), - )) - } - - pub fn load(&self) -> Option { - match self.0.load(std::sync::atomic::Ordering::Acquire) { - 0 => None, - other => Some(AuxFilePolicy::from_usize(other)), - } - } - - pub fn store(&self, policy: Option) { - self.0.store( - policy.map(AuxFilePolicy::to_usize).unwrap_or_default(), - std::sync::atomic::Ordering::Release, - ); - } -} - -impl AuxFilePolicy { - pub fn to_usize(self) -> usize { - match self { - Self::V1 => 1, - Self::CrossValidation => 2, - Self::V2 => 3, - } - } - - pub fn try_from_usize(this: usize) -> Option { - match this { - 1 => Some(Self::V1), - 2 => Some(Self::CrossValidation), - 3 => Some(Self::V2), - _ => None, - } - } - - pub fn from_usize(this: usize) -> Self { - Self::try_from_usize(this).unwrap() - } -} - #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(tag = "kind")] pub enum EvictionPolicy { @@ -1633,71 +1569,6 @@ mod tests { } } - #[test] - fn test_aux_file_migration_path() { - assert!(AuxFilePolicy::is_valid_migration_path( - None, - AuxFilePolicy::V1 - )); - assert!(AuxFilePolicy::is_valid_migration_path( - None, - AuxFilePolicy::V2 - )); - assert!(AuxFilePolicy::is_valid_migration_path( - None, - AuxFilePolicy::CrossValidation - )); - // Self-migration is not a valid migration path, and the caller should handle it by itself. - assert!(!AuxFilePolicy::is_valid_migration_path( - Some(AuxFilePolicy::V1), - AuxFilePolicy::V1 - )); - assert!(!AuxFilePolicy::is_valid_migration_path( - Some(AuxFilePolicy::V2), - AuxFilePolicy::V2 - )); - assert!(!AuxFilePolicy::is_valid_migration_path( - Some(AuxFilePolicy::CrossValidation), - AuxFilePolicy::CrossValidation - )); - // Migrations not allowed - assert!(!AuxFilePolicy::is_valid_migration_path( - Some(AuxFilePolicy::CrossValidation), - AuxFilePolicy::V1 - )); - assert!(!AuxFilePolicy::is_valid_migration_path( - Some(AuxFilePolicy::V1), - AuxFilePolicy::V2 - )); - assert!(!AuxFilePolicy::is_valid_migration_path( - Some(AuxFilePolicy::V2), - AuxFilePolicy::V1 - )); - assert!(!AuxFilePolicy::is_valid_migration_path( - Some(AuxFilePolicy::V2), - AuxFilePolicy::CrossValidation - )); - assert!(!AuxFilePolicy::is_valid_migration_path( - Some(AuxFilePolicy::V1), - AuxFilePolicy::CrossValidation - )); - // Migrations allowed - assert!(AuxFilePolicy::is_valid_migration_path( - Some(AuxFilePolicy::CrossValidation), - AuxFilePolicy::V2 - )); - } - - #[test] - fn test_aux_parse() { - assert_eq!(AuxFilePolicy::from_str("V2").unwrap(), AuxFilePolicy::V2); - assert_eq!(AuxFilePolicy::from_str("v2").unwrap(), AuxFilePolicy::V2); - assert_eq!( - AuxFilePolicy::from_str("cross-validation").unwrap(), - AuxFilePolicy::CrossValidation - ); - } - #[test] fn test_image_compression_algorithm_parsing() { use ImageCompressionAlgorithm::*; diff --git a/pageserver/pagebench/src/cmd/aux_files.rs b/pageserver/pagebench/src/cmd/aux_files.rs index bce3285606..923a7f1f18 100644 --- a/pageserver/pagebench/src/cmd/aux_files.rs +++ b/pageserver/pagebench/src/cmd/aux_files.rs @@ -1,4 +1,4 @@ -use pageserver_api::models::{AuxFilePolicy, TenantConfig, TenantConfigRequest}; +use pageserver_api::models::{TenantConfig, TenantConfigRequest}; use pageserver_api::shard::TenantShardId; use utils::id::TenantTimelineId; use utils::lsn::Lsn; @@ -66,10 +66,7 @@ async fn main_impl(args: Args) -> anyhow::Result<()> { mgmt_api_client .tenant_config(&TenantConfigRequest { tenant_id: timeline.tenant_id, - config: TenantConfig { - switch_aux_file_policy: Some(AuxFilePolicy::V2), - ..Default::default() - }, + config: TenantConfig::default(), }) .await?; diff --git a/pageserver/src/tenant.rs b/pageserver/src/tenant.rs index f846e145c5..64e871cada 100644 --- a/pageserver/src/tenant.rs +++ b/pageserver/src/tenant.rs @@ -4853,7 +4853,6 @@ pub(crate) mod harness { image_layer_creation_check_threshold: Some( tenant_conf.image_layer_creation_check_threshold, ), - switch_aux_file_policy: Some(tenant_conf.switch_aux_file_policy), lsn_lease_length: Some(tenant_conf.lsn_lease_length), lsn_lease_length_for_ts: Some(tenant_conf.lsn_lease_length_for_ts), } diff --git a/pageserver/src/tenant/config.rs b/pageserver/src/tenant/config.rs index 502cb62fe8..ce686c89ef 100644 --- a/pageserver/src/tenant/config.rs +++ b/pageserver/src/tenant/config.rs @@ -9,7 +9,6 @@ //! may lead to a data loss. //! pub(crate) use pageserver_api::config::TenantConfigToml as TenantConf; -use pageserver_api::models::AuxFilePolicy; use pageserver_api::models::CompactionAlgorithmSettings; use pageserver_api::models::EvictionPolicy; use pageserver_api::models::{self, ThrottleConfig}; @@ -341,10 +340,6 @@ pub struct TenantConfOpt { #[serde(skip_serializing_if = "Option::is_none")] pub image_layer_creation_check_threshold: Option, - #[serde(skip_serializing_if = "Option::is_none")] - #[serde(default)] - pub switch_aux_file_policy: Option, - #[serde(skip_serializing_if = "Option::is_none")] #[serde(with = "humantime_serde")] #[serde(default)] @@ -410,9 +405,6 @@ impl TenantConfOpt { image_layer_creation_check_threshold: self .image_layer_creation_check_threshold .unwrap_or(global_conf.image_layer_creation_check_threshold), - switch_aux_file_policy: self - .switch_aux_file_policy - .unwrap_or(global_conf.switch_aux_file_policy), lsn_lease_length: self .lsn_lease_length .unwrap_or(global_conf.lsn_lease_length), @@ -470,7 +462,6 @@ impl From for models::TenantConfig { lazy_slru_download: value.lazy_slru_download, timeline_get_throttle: value.timeline_get_throttle.map(ThrottleConfig::from), image_layer_creation_check_threshold: value.image_layer_creation_check_threshold, - switch_aux_file_policy: value.switch_aux_file_policy, lsn_lease_length: value.lsn_lease_length.map(humantime), lsn_lease_length_for_ts: value.lsn_lease_length_for_ts.map(humantime), } diff --git a/storage_controller/src/service.rs b/storage_controller/src/service.rs index 32029c1232..3f6cbfef59 100644 --- a/storage_controller/src/service.rs +++ b/storage_controller/src/service.rs @@ -4958,16 +4958,7 @@ impl Service { stripe_size, }, placement_policy: Some(PlacementPolicy::Attached(0)), // No secondaries, for convenient debug/hacking - - // There is no way to know what the tenant's config was: revert to defaults - // - // TODO: remove `switch_aux_file_policy` once we finish auxv2 migration - // - // we write to both v1+v2 storage, so that the test case can use either storage format for testing - config: TenantConfig { - switch_aux_file_policy: Some(models::AuxFilePolicy::CrossValidation), - ..TenantConfig::default() - }, + config: TenantConfig::default(), }) .await?; diff --git a/test_runner/fixtures/neon_cli.py b/test_runner/fixtures/neon_cli.py index 1b2767e296..d220ea57a2 100644 --- a/test_runner/fixtures/neon_cli.py +++ b/test_runner/fixtures/neon_cli.py @@ -16,7 +16,6 @@ from fixtures.common_types import Lsn, TenantId, TimelineId from fixtures.log_helper import log from fixtures.pageserver.common_types import IndexPartDump from fixtures.pg_version import PgVersion -from fixtures.utils import AuxFileStore if TYPE_CHECKING: from typing import ( @@ -201,7 +200,6 @@ class NeonLocalCli(AbstractNeonCli): shard_stripe_size: Optional[int] = None, placement_policy: Optional[str] = None, set_default: bool = False, - aux_file_policy: Optional[AuxFileStore] = None, ): """ Creates a new tenant, returns its id and its initial timeline's id. @@ -223,13 +221,6 @@ class NeonLocalCli(AbstractNeonCli): ) ) - if aux_file_policy is AuxFileStore.V2: - args.extend(["-c", "switch_aux_file_policy:v2"]) - elif aux_file_policy is AuxFileStore.V1: - args.extend(["-c", "switch_aux_file_policy:v1"]) - elif aux_file_policy is AuxFileStore.CrossValidation: - args.extend(["-c", "switch_aux_file_policy:cross-validation"]) - if set_default: args.append("--set-default") diff --git a/test_runner/fixtures/neon_fixtures.py b/test_runner/fixtures/neon_fixtures.py index 6491069f20..a8ec144fe9 100644 --- a/test_runner/fixtures/neon_fixtures.py +++ b/test_runner/fixtures/neon_fixtures.py @@ -94,7 +94,6 @@ from fixtures.utils import ( subprocess_capture, wait_until, ) -from fixtures.utils import AuxFileStore as AuxFileStore # reexport from .neon_api import NeonAPI, NeonApiEndpoint @@ -353,7 +352,6 @@ class NeonEnvBuilder: initial_tenant: Optional[TenantId] = None, initial_timeline: Optional[TimelineId] = None, pageserver_virtual_file_io_engine: Optional[str] = None, - pageserver_aux_file_policy: Optional[AuxFileStore] = None, pageserver_default_tenant_config_compaction_algorithm: Optional[dict[str, Any]] = None, safekeeper_extra_opts: Optional[list[str]] = None, storage_controller_port_override: Optional[int] = None, @@ -405,8 +403,6 @@ class NeonEnvBuilder: f"Overriding pageserver default compaction algorithm to {self.pageserver_default_tenant_config_compaction_algorithm}" ) - self.pageserver_aux_file_policy = pageserver_aux_file_policy - self.safekeeper_extra_opts = safekeeper_extra_opts self.storage_controller_port_override = storage_controller_port_override @@ -467,7 +463,6 @@ class NeonEnvBuilder: timeline_id=env.initial_timeline, shard_count=initial_tenant_shard_count, shard_stripe_size=initial_tenant_shard_stripe_size, - aux_file_policy=self.pageserver_aux_file_policy, ) assert env.initial_tenant == initial_tenant assert env.initial_timeline == initial_timeline @@ -1027,7 +1022,6 @@ class NeonEnv: self.control_plane_compute_hook_api = config.control_plane_compute_hook_api self.pageserver_virtual_file_io_engine = config.pageserver_virtual_file_io_engine - self.pageserver_aux_file_policy = config.pageserver_aux_file_policy self.pageserver_virtual_file_io_mode = config.pageserver_virtual_file_io_mode # Create the neon_local's `NeonLocalInitConf` @@ -1323,7 +1317,6 @@ class NeonEnv: shard_stripe_size: Optional[int] = None, placement_policy: Optional[str] = None, set_default: bool = False, - aux_file_policy: Optional[AuxFileStore] = None, ) -> tuple[TenantId, TimelineId]: """ Creates a new tenant, returns its id and its initial timeline's id. @@ -1340,7 +1333,6 @@ class NeonEnv: shard_stripe_size=shard_stripe_size, placement_policy=placement_policy, set_default=set_default, - aux_file_policy=aux_file_policy, ) return tenant_id, timeline_id @@ -1398,7 +1390,6 @@ def neon_simple_env( compatibility_pg_distrib_dir: Path, pg_version: PgVersion, pageserver_virtual_file_io_engine: str, - pageserver_aux_file_policy: Optional[AuxFileStore], pageserver_default_tenant_config_compaction_algorithm: Optional[dict[str, Any]], pageserver_virtual_file_io_mode: Optional[str], ) -> Iterator[NeonEnv]: @@ -1431,7 +1422,6 @@ def neon_simple_env( test_name=request.node.name, test_output_dir=test_output_dir, pageserver_virtual_file_io_engine=pageserver_virtual_file_io_engine, - pageserver_aux_file_policy=pageserver_aux_file_policy, pageserver_default_tenant_config_compaction_algorithm=pageserver_default_tenant_config_compaction_algorithm, pageserver_virtual_file_io_mode=pageserver_virtual_file_io_mode, combination=combination, @@ -1458,7 +1448,6 @@ def neon_env_builder( top_output_dir: Path, pageserver_virtual_file_io_engine: str, pageserver_default_tenant_config_compaction_algorithm: Optional[dict[str, Any]], - pageserver_aux_file_policy: Optional[AuxFileStore], record_property: Callable[[str, object], None], pageserver_virtual_file_io_mode: Optional[str], ) -> Iterator[NeonEnvBuilder]: @@ -1501,7 +1490,6 @@ def neon_env_builder( test_name=request.node.name, test_output_dir=test_output_dir, test_overlay_dir=test_overlay_dir, - pageserver_aux_file_policy=pageserver_aux_file_policy, pageserver_default_tenant_config_compaction_algorithm=pageserver_default_tenant_config_compaction_algorithm, pageserver_virtual_file_io_mode=pageserver_virtual_file_io_mode, ) as builder: diff --git a/test_runner/fixtures/parametrize.py b/test_runner/fixtures/parametrize.py index 4114c2fcb3..1131bf090f 100644 --- a/test_runner/fixtures/parametrize.py +++ b/test_runner/fixtures/parametrize.py @@ -10,12 +10,6 @@ from _pytest.python import Metafunc from fixtures.pg_version import PgVersion -if TYPE_CHECKING: - from typing import Any, Optional - - from fixtures.utils import AuxFileStore - - if TYPE_CHECKING: from typing import Any, Optional @@ -50,11 +44,6 @@ def pageserver_virtual_file_io_mode() -> Optional[str]: return os.getenv("PAGESERVER_VIRTUAL_FILE_IO_MODE") -@pytest.fixture(scope="function", autouse=True) -def pageserver_aux_file_policy() -> Optional[AuxFileStore]: - return None - - def get_pageserver_default_tenant_config_compaction_algorithm() -> Optional[dict[str, Any]]: toml_table = os.getenv("PAGESERVER_DEFAULT_TENANT_CONFIG_COMPACTION_ALGORITHM") if toml_table is None: diff --git a/test_runner/fixtures/utils.py b/test_runner/fixtures/utils.py index d12fa59abc..01b7cf1026 100644 --- a/test_runner/fixtures/utils.py +++ b/test_runner/fixtures/utils.py @@ -1,7 +1,6 @@ from __future__ import annotations import contextlib -import enum import json import os import re @@ -515,21 +514,6 @@ def assert_no_errors(log_file: Path, service: str, allowed_errors: list[str]): assert not errors, f"First log error on {service}: {errors[0]}\nHint: use scripts/check_allowed_errors.sh to test any new allowed_error you add" -@enum.unique -class AuxFileStore(str, enum.Enum): - V1 = "v1" - V2 = "v2" - CrossValidation = "cross-validation" - - @override - def __repr__(self) -> str: - return f"'aux-{self.value}'" - - @override - def __str__(self) -> str: - return f"'aux-{self.value}'" - - def assert_pageserver_backups_equal(left: Path, right: Path, skip_files: set[str]): """ This is essentially: diff --git a/test_runner/performance/test_logical_replication.py b/test_runner/performance/test_logical_replication.py index 815d186ab9..8b2a296bdd 100644 --- a/test_runner/performance/test_logical_replication.py +++ b/test_runner/performance/test_logical_replication.py @@ -9,7 +9,7 @@ import pytest from fixtures.benchmark_fixture import MetricReport from fixtures.common_types import Lsn from fixtures.log_helper import log -from fixtures.neon_fixtures import AuxFileStore, logical_replication_sync +from fixtures.neon_fixtures import logical_replication_sync if TYPE_CHECKING: from fixtures.benchmark_fixture import NeonBenchmarker @@ -17,7 +17,6 @@ if TYPE_CHECKING: from fixtures.neon_fixtures import NeonEnv, PgBin -@pytest.mark.parametrize("pageserver_aux_file_policy", [AuxFileStore.V2]) @pytest.mark.timeout(1000) def test_logical_replication(neon_simple_env: NeonEnv, pg_bin: PgBin, vanilla_pg): env = neon_simple_env diff --git a/test_runner/regress/test_attach_tenant_config.py b/test_runner/regress/test_attach_tenant_config.py index 4a7017994d..83d003a5cc 100644 --- a/test_runner/regress/test_attach_tenant_config.py +++ b/test_runner/regress/test_attach_tenant_config.py @@ -172,7 +172,6 @@ def test_fully_custom_config(positive_env: NeonEnv): }, "walreceiver_connect_timeout": "13m", "image_layer_creation_check_threshold": 1, - "switch_aux_file_policy": "cross-validation", "lsn_lease_length": "1m", "lsn_lease_length_for_ts": "5s", } diff --git a/test_runner/regress/test_logical_replication.py b/test_runner/regress/test_logical_replication.py index c26bf058e2..30027463df 100644 --- a/test_runner/regress/test_logical_replication.py +++ b/test_runner/regress/test_logical_replication.py @@ -5,11 +5,9 @@ from functools import partial from random import choice from string import ascii_lowercase -import pytest from fixtures.common_types import Lsn from fixtures.log_helper import log from fixtures.neon_fixtures import ( - AuxFileStore, NeonEnv, NeonEnvBuilder, PgProtocol, @@ -23,17 +21,6 @@ def random_string(n: int): return "".join([choice(ascii_lowercase) for _ in range(n)]) -@pytest.mark.parametrize( - "pageserver_aux_file_policy", [AuxFileStore.V2, AuxFileStore.CrossValidation] -) -def test_aux_file_v2_flag(neon_simple_env: NeonEnv, pageserver_aux_file_policy: AuxFileStore): - env = neon_simple_env - with env.pageserver.http_client() as client: - tenant_config = client.tenant_config(env.initial_tenant).effective_config - assert pageserver_aux_file_policy == tenant_config["switch_aux_file_policy"] - - -@pytest.mark.parametrize("pageserver_aux_file_policy", [AuxFileStore.CrossValidation]) def test_logical_replication(neon_simple_env: NeonEnv, vanilla_pg): env = neon_simple_env @@ -173,7 +160,6 @@ COMMIT; # Test that neon.logical_replication_max_snap_files works -@pytest.mark.parametrize("pageserver_aux_file_policy", [AuxFileStore.CrossValidation]) def test_obsolete_slot_drop(neon_simple_env: NeonEnv, vanilla_pg): def slot_removed(ep): assert ( @@ -350,7 +336,6 @@ FROM generate_series(1, 16384) AS seq; -- Inserts enough rows to exceed 16MB of # # Most pages start with a contrecord, so we don't do anything special # to ensure that. -@pytest.mark.parametrize("pageserver_aux_file_policy", [AuxFileStore.CrossValidation]) def test_restart_endpoint(neon_simple_env: NeonEnv, vanilla_pg): env = neon_simple_env @@ -395,7 +380,6 @@ def test_restart_endpoint(neon_simple_env: NeonEnv, vanilla_pg): # logical replication bug as such, but without logical replication, # records passed ot the WAL redo process are never large enough to hit # the bug. -@pytest.mark.parametrize("pageserver_aux_file_policy", [AuxFileStore.CrossValidation]) def test_large_records(neon_simple_env: NeonEnv, vanilla_pg): env = neon_simple_env @@ -467,7 +451,6 @@ def test_slots_and_branching(neon_simple_env: NeonEnv): ws_cur.execute("select pg_create_logical_replication_slot('my_slot', 'pgoutput')") -@pytest.mark.parametrize("pageserver_aux_file_policy", [AuxFileStore.CrossValidation]) def test_replication_shutdown(neon_simple_env: NeonEnv): # Ensure Postgres can exit without stuck when a replication job is active + neon extension installed env = neon_simple_env