mirror of
https://github.com/neondatabase/neon.git
synced 2026-08-12 17:23:42 +00:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 61af437087 | |||
| e986896676 | |||
| f7ab3ffcb7 | |||
| 2f8d548a12 | |||
| 66db381dc9 | |||
| 6744ed19d8 | |||
| ae63ac7488 | |||
| 6eb638f4b3 | |||
| 7a485b599b |
@@ -441,6 +441,11 @@ WAL-log them periodically, from a backgound worker.
|
||||
|
||||
Similarly to replications snapshot files, the CID mapping files generated during VACUUM FULL of a catalog table are WAL-logged
|
||||
|
||||
FIXME: But they're not, AFAICS?
|
||||
|
||||
FIXME: However, we do WAL-log the file in pg_logical/mappings. But AFAICS that's WAL-logged
|
||||
by PostgreSQL too. Why do we need separate WAL-logging for that? See changes in rewriteheap.c
|
||||
|
||||
### How to get rid of the patch
|
||||
|
||||
WAL-log them periodically, from a backgound worker.
|
||||
|
||||
@@ -348,7 +348,7 @@ impl AuxFilePolicy {
|
||||
|
||||
/// If a tenant writes aux files without setting `switch_aux_policy`, this value will be used.
|
||||
pub fn default_tenant_config() -> Self {
|
||||
Self::V1
|
||||
Self::V2
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -95,6 +95,7 @@ fn main() -> anyhow::Result<()> {
|
||||
.allowlist_var("ERROR")
|
||||
.allowlist_var("FATAL")
|
||||
.allowlist_var("PANIC")
|
||||
.allowlist_var("PG_VERSION_NUM")
|
||||
.allowlist_var("WPEVENT")
|
||||
.allowlist_var("WL_LATCH_SET")
|
||||
.allowlist_var("WL_SOCKET_READABLE")
|
||||
|
||||
@@ -282,7 +282,11 @@ mod tests {
|
||||
use std::cell::UnsafeCell;
|
||||
use utils::id::TenantTimelineId;
|
||||
|
||||
use crate::{api_bindings::Level, bindings::NeonWALReadResult, walproposer::Wrapper};
|
||||
use crate::{
|
||||
api_bindings::Level,
|
||||
bindings::{NeonWALReadResult, PG_VERSION_NUM},
|
||||
walproposer::Wrapper,
|
||||
};
|
||||
|
||||
use super::ApiImpl;
|
||||
|
||||
@@ -489,41 +493,79 @@ mod tests {
|
||||
|
||||
let (sender, receiver) = sync_channel(1);
|
||||
|
||||
// Messages definitions are at walproposer.h
|
||||
// xxx: it would be better to extract them from safekeeper crate and
|
||||
// use serialization/deserialization here.
|
||||
let greeting_tag = (b'g' as u64).to_ne_bytes();
|
||||
let proto_version = 2_u32.to_ne_bytes();
|
||||
let pg_version: [u8; 4] = PG_VERSION_NUM.to_ne_bytes();
|
||||
let proposer_id = [0; 16];
|
||||
let system_id = 0_u64.to_ne_bytes();
|
||||
let tenant_id = ttid.tenant_id.as_arr();
|
||||
let timeline_id = ttid.timeline_id.as_arr();
|
||||
let pg_tli = 1_u32.to_ne_bytes();
|
||||
let wal_seg_size = 16777216_u32.to_ne_bytes();
|
||||
let proposer_greeting = [
|
||||
greeting_tag.as_slice(),
|
||||
proto_version.as_slice(),
|
||||
pg_version.as_slice(),
|
||||
proposer_id.as_slice(),
|
||||
system_id.as_slice(),
|
||||
tenant_id.as_slice(),
|
||||
timeline_id.as_slice(),
|
||||
pg_tli.as_slice(),
|
||||
wal_seg_size.as_slice(),
|
||||
]
|
||||
.concat();
|
||||
|
||||
let voting_tag = (b'v' as u64).to_ne_bytes();
|
||||
let vote_request_term = 3_u64.to_ne_bytes();
|
||||
let proposer_id = [0; 16];
|
||||
let vote_request = [
|
||||
voting_tag.as_slice(),
|
||||
vote_request_term.as_slice(),
|
||||
proposer_id.as_slice(),
|
||||
]
|
||||
.concat();
|
||||
|
||||
let acceptor_greeting_term = 2_u64.to_ne_bytes();
|
||||
let acceptor_greeting_node_id = 1_u64.to_ne_bytes();
|
||||
let acceptor_greeting = [
|
||||
greeting_tag.as_slice(),
|
||||
acceptor_greeting_term.as_slice(),
|
||||
acceptor_greeting_node_id.as_slice(),
|
||||
]
|
||||
.concat();
|
||||
|
||||
let vote_response_term = 3_u64.to_ne_bytes();
|
||||
let vote_given = 1_u64.to_ne_bytes();
|
||||
let flush_lsn = 0x539_u64.to_ne_bytes();
|
||||
let truncate_lsn = 0x539_u64.to_ne_bytes();
|
||||
let th_len = 1_u32.to_ne_bytes();
|
||||
let th_term = 2_u64.to_ne_bytes();
|
||||
let th_lsn = 0x539_u64.to_ne_bytes();
|
||||
let timeline_start_lsn = 0x539_u64.to_ne_bytes();
|
||||
let vote_response = [
|
||||
voting_tag.as_slice(),
|
||||
vote_response_term.as_slice(),
|
||||
vote_given.as_slice(),
|
||||
flush_lsn.as_slice(),
|
||||
truncate_lsn.as_slice(),
|
||||
th_len.as_slice(),
|
||||
th_term.as_slice(),
|
||||
th_lsn.as_slice(),
|
||||
timeline_start_lsn.as_slice(),
|
||||
]
|
||||
.concat();
|
||||
|
||||
let my_impl: Box<dyn ApiImpl> = Box::new(MockImpl {
|
||||
wait_events: Cell::new(WaitEventsData {
|
||||
sk: std::ptr::null_mut(),
|
||||
event_mask: 0,
|
||||
}),
|
||||
expected_messages: vec![
|
||||
// TODO: When updating Postgres versions, this test will cause
|
||||
// problems. Postgres version in message needs updating.
|
||||
//
|
||||
// Greeting(ProposerGreeting { protocol_version: 2, pg_version: 160003, proposer_id: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], system_id: 0, timeline_id: 9e4c8f36063c6c6e93bc20d65a820f3d, tenant_id: 9e4c8f36063c6c6e93bc20d65a820f3d, tli: 1, wal_seg_size: 16777216 })
|
||||
vec![
|
||||
103, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 3, 113, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 158, 76, 143, 54, 6, 60, 108, 110,
|
||||
147, 188, 32, 214, 90, 130, 15, 61, 158, 76, 143, 54, 6, 60, 108, 110, 147,
|
||||
188, 32, 214, 90, 130, 15, 61, 1, 0, 0, 0, 0, 0, 0, 1,
|
||||
],
|
||||
// VoteRequest(VoteRequest { term: 3 })
|
||||
vec![
|
||||
118, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
],
|
||||
],
|
||||
expected_messages: vec![proposer_greeting, vote_request],
|
||||
expected_ptr: AtomicUsize::new(0),
|
||||
safekeeper_replies: vec![
|
||||
// Greeting(AcceptorGreeting { term: 2, node_id: NodeId(1) })
|
||||
vec![
|
||||
103, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
|
||||
],
|
||||
// VoteResponse(VoteResponse { term: 3, vote_given: 1, flush_lsn: 0/539, truncate_lsn: 0/539, term_history: [(2, 0/539)], timeline_start_lsn: 0/539 })
|
||||
vec![
|
||||
118, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 57,
|
||||
5, 0, 0, 0, 0, 0, 0, 57, 5, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0,
|
||||
0, 57, 5, 0, 0, 0, 0, 0, 0, 57, 5, 0, 0, 0, 0, 0, 0,
|
||||
],
|
||||
],
|
||||
safekeeper_replies: vec![acceptor_greeting, vote_response],
|
||||
replies_ptr: AtomicUsize::new(0),
|
||||
sync_channel: sender,
|
||||
shmem: UnsafeCell::new(crate::api_bindings::empty_shmem()),
|
||||
|
||||
@@ -726,7 +726,17 @@ impl Timeline {
|
||||
) -> Result<HashMap<String, Bytes>, PageReconstructError> {
|
||||
let current_policy = self.last_aux_file_policy.load();
|
||||
match current_policy {
|
||||
Some(AuxFilePolicy::V1) | None => self.list_aux_files_v1(lsn, ctx).await,
|
||||
Some(AuxFilePolicy::V1) => {
|
||||
warn!("this timeline is using deprecated aux file policy V1 (policy=V1)");
|
||||
self.list_aux_files_v1(lsn, ctx).await
|
||||
}
|
||||
None => {
|
||||
let res = self.list_aux_files_v1(lsn, ctx).await?;
|
||||
if !res.is_empty() {
|
||||
warn!("this timeline is using deprecated aux file policy V1 (policy=None)");
|
||||
}
|
||||
Ok(res)
|
||||
}
|
||||
Some(AuxFilePolicy::V2) => self.list_aux_files_v2(lsn, ctx).await,
|
||||
Some(AuxFilePolicy::CrossValidation) => {
|
||||
let v1_result = self.list_aux_files_v1(lsn, ctx).await;
|
||||
@@ -1587,6 +1597,7 @@ impl<'a> DatadirModification<'a> {
|
||||
if aux_files_key_v1.is_empty() {
|
||||
None
|
||||
} else {
|
||||
warn!("this timeline is using deprecated aux file policy V1");
|
||||
self.tline.do_switch_aux_policy(AuxFilePolicy::V1)?;
|
||||
Some(AuxFilePolicy::V1)
|
||||
}
|
||||
@@ -2048,7 +2059,7 @@ mod tests {
|
||||
|
||||
let (tenant, ctx) = harness.load().await;
|
||||
let tline = tenant
|
||||
.create_empty_timeline(TIMELINE_ID, Lsn(0), DEFAULT_PG_VERSION, &ctx)
|
||||
.create_empty_timeline(TIMELINE_ID, Lsn(0x10), DEFAULT_PG_VERSION, &ctx)
|
||||
.await?;
|
||||
let tline = tline.raw_timeline().unwrap();
|
||||
|
||||
|
||||
@@ -5932,10 +5932,10 @@ mod tests {
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// the default aux file policy to switch is v1 if not set by the admins
|
||||
// the default aux file policy to switch is v2 if not set by the admins
|
||||
assert_eq!(
|
||||
harness.tenant_conf.switch_aux_file_policy,
|
||||
AuxFilePolicy::V1
|
||||
AuxFilePolicy::default_tenant_config()
|
||||
);
|
||||
let (tenant, ctx) = harness.load().await;
|
||||
|
||||
@@ -5979,8 +5979,8 @@ mod tests {
|
||||
);
|
||||
assert_eq!(
|
||||
tline.last_aux_file_policy.load(),
|
||||
Some(AuxFilePolicy::V1),
|
||||
"aux file is written with switch_aux_file_policy unset (which is v1), so we should keep v1"
|
||||
Some(AuxFilePolicy::V2),
|
||||
"aux file is written with switch_aux_file_policy unset (which is v2), so we should use v2 there"
|
||||
);
|
||||
|
||||
// we can read everything from the storage
|
||||
@@ -6002,8 +6002,8 @@ mod tests {
|
||||
|
||||
assert_eq!(
|
||||
tline.last_aux_file_policy.load(),
|
||||
Some(AuxFilePolicy::V1),
|
||||
"keep v1 storage format when new files are written"
|
||||
Some(AuxFilePolicy::V2),
|
||||
"keep v2 storage format when new files are written"
|
||||
);
|
||||
|
||||
let files = tline.list_aux_files(lsn, &ctx).await.unwrap();
|
||||
@@ -6019,7 +6019,7 @@ mod tests {
|
||||
|
||||
// child copies the last flag even if that is not on remote storage yet
|
||||
assert_eq!(child.get_switch_aux_file_policy(), AuxFilePolicy::V2);
|
||||
assert_eq!(child.last_aux_file_policy.load(), Some(AuxFilePolicy::V1));
|
||||
assert_eq!(child.last_aux_file_policy.load(), Some(AuxFilePolicy::V2));
|
||||
|
||||
let files = child.list_aux_files(lsn, &ctx).await.unwrap();
|
||||
assert_eq!(files.get("pg_logical/mappings/test1"), None);
|
||||
|
||||
@@ -2234,6 +2234,11 @@ impl Timeline {
|
||||
|
||||
handles: Default::default(),
|
||||
};
|
||||
|
||||
if aux_file_policy == Some(AuxFilePolicy::V1) {
|
||||
warn!("this timeline is using deprecated aux file policy V1");
|
||||
}
|
||||
|
||||
result.repartition_threshold =
|
||||
result.get_checkpoint_distance() / REPARTITION_FREQ_IN_CHECKPOINT_DISTANCE;
|
||||
|
||||
|
||||
@@ -284,6 +284,9 @@ extern PGDLLEXPORT void neon_read_at_lsn(NRelFileInfo rnode, ForkNumber forkNum,
|
||||
extern void neon_write(SMgrRelation reln, ForkNumber forknum,
|
||||
BlockNumber blocknum, const void *buffer, bool skipFsync);
|
||||
#endif
|
||||
|
||||
extern PGDLLEXPORT void neon_dump_relsize_cache(void);
|
||||
|
||||
extern void neon_writeback(SMgrRelation reln, ForkNumber forknum,
|
||||
BlockNumber blocknum, BlockNumber nblocks);
|
||||
extern BlockNumber neon_nblocks(SMgrRelation reln, ForkNumber forknum);
|
||||
|
||||
@@ -110,7 +110,8 @@ get_cached_relsize(NRelFileInfo rinfo, ForkNumber forknum, BlockNumber *size)
|
||||
|
||||
tag.rinfo = rinfo;
|
||||
tag.forknum = forknum;
|
||||
LWLockAcquire(relsize_lock, LW_SHARED);
|
||||
/* We need exclusive lock here because of LRU list manipulation */
|
||||
LWLockAcquire(relsize_lock, LW_EXCLUSIVE);
|
||||
entry = hash_search(relsize_hash, &tag, HASH_FIND, NULL);
|
||||
if (entry != NULL)
|
||||
{
|
||||
@@ -276,3 +277,62 @@ relsize_shmem_request(void)
|
||||
RequestNamedLWLockTranche("neon_relsize", 1);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
* A debugging function, to print the contents of the relsize cache as NOTICE
|
||||
* messages. This is exposed in the neon_test_utils extension.
|
||||
*/
|
||||
void
|
||||
neon_dump_relsize_cache(void)
|
||||
{
|
||||
HASH_SEQ_STATUS status;
|
||||
RelSizeEntry *entry;
|
||||
dlist_iter iter;
|
||||
int cnt;
|
||||
|
||||
if (relsize_hash_size == 0)
|
||||
{
|
||||
elog(NOTICE, "relsize cache is disable");
|
||||
return;
|
||||
}
|
||||
|
||||
LWLockAcquire(relsize_lock, LW_EXCLUSIVE);
|
||||
|
||||
elog(NOTICE, "stats: size %lu hits: " UINT64_FORMAT " misses " UINT64_FORMAT " writes " UINT64_FORMAT,
|
||||
(unsigned long) relsize_ctl->size, relsize_ctl->hits, relsize_ctl->misses, relsize_ctl->writes);
|
||||
|
||||
elog(NOTICE, "hash:");
|
||||
cnt = 0;
|
||||
hash_seq_init(&status, relsize_hash);
|
||||
while ((entry = hash_seq_search(&status)) != NULL)
|
||||
{
|
||||
cnt++;
|
||||
elog(NOTICE, "hash entry %d: rel %u/%u/%u.%u size %u",
|
||||
cnt,
|
||||
RelFileInfoFmt(entry->tag.rinfo),
|
||||
entry->tag.forknum,
|
||||
entry->size);
|
||||
}
|
||||
|
||||
elog(NOTICE, "LRU:");
|
||||
cnt = 0;
|
||||
dlist_foreach(iter, &relsize_ctl->lru)
|
||||
{
|
||||
entry = dlist_container(RelSizeEntry, lru_node, iter.cur);
|
||||
cnt++;
|
||||
elog(NOTICE, "LRU entry %d: rel %u/%u/%u.%u size %u",
|
||||
cnt,
|
||||
RelFileInfoFmt(entry->tag.rinfo),
|
||||
entry->tag.forknum,
|
||||
entry->size);
|
||||
|
||||
if (cnt > relsize_hash_size * 2)
|
||||
{
|
||||
elog(NOTICE, "broken LRU chain??");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
LWLockRelease(relsize_lock);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ OBJS = \
|
||||
neontest.o
|
||||
|
||||
EXTENSION = neon_test_utils
|
||||
DATA = neon_test_utils--1.3.sql
|
||||
DATA = neon_test_utils--1.4.sql
|
||||
PGFILEDESC = "neon_test_utils - helpers for neon testing and debugging"
|
||||
|
||||
PG_CONFIG = pg_config
|
||||
|
||||
+5
@@ -69,3 +69,8 @@ BEGIN
|
||||
PERFORM trigger_segfault();
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE FUNCTION dump_relsize_cache()
|
||||
RETURNS VOID
|
||||
AS 'MODULE_PATHNAME', 'dump_relsize_cache'
|
||||
LANGUAGE C PARALLEL UNSAFE;
|
||||
@@ -1,6 +1,6 @@
|
||||
# neon_test_utils extension
|
||||
comment = 'helpers for neon testing and debugging'
|
||||
default_version = '1.3'
|
||||
default_version = '1.4'
|
||||
module_pathname = '$libdir/neon_test_utils'
|
||||
relocatable = true
|
||||
trusted = true
|
||||
|
||||
@@ -45,6 +45,7 @@ PG_FUNCTION_INFO_V1(get_raw_page_at_lsn_ex);
|
||||
PG_FUNCTION_INFO_V1(neon_xlogflush);
|
||||
PG_FUNCTION_INFO_V1(trigger_panic);
|
||||
PG_FUNCTION_INFO_V1(trigger_segfault);
|
||||
PG_FUNCTION_INFO_V1(dump_relsize_cache);
|
||||
|
||||
/*
|
||||
* Linkage to functions in neon module.
|
||||
@@ -60,6 +61,10 @@ typedef void (*neon_read_at_lsn_type) (NRelFileInfo rinfo, ForkNumber forkNum, B
|
||||
|
||||
static neon_read_at_lsn_type neon_read_at_lsn_ptr;
|
||||
|
||||
typedef void (*neon_dump_relsize_cache_type) (void);
|
||||
|
||||
static neon_dump_relsize_cache_type neon_dump_relsize_cache_ptr;
|
||||
|
||||
/*
|
||||
* Module initialize function: fetch function pointers for cross-module calls.
|
||||
*/
|
||||
@@ -68,12 +73,18 @@ _PG_init(void)
|
||||
{
|
||||
/* Asserts verify that typedefs above match original declarations */
|
||||
AssertVariableIsOfType(&neon_read_at_lsn, neon_read_at_lsn_type);
|
||||
AssertVariableIsOfType(&neon_dump_relsize_cache, neon_dump_relsize_cache_type);
|
||||
neon_read_at_lsn_ptr = (neon_read_at_lsn_type)
|
||||
load_external_function("$libdir/neon", "neon_read_at_lsn",
|
||||
true, NULL);
|
||||
|
||||
neon_dump_relsize_cache_ptr = (neon_dump_relsize_cache_type)
|
||||
load_external_function("$libdir/neon", "neon_dump_relsize_cache",
|
||||
true, NULL);
|
||||
}
|
||||
|
||||
#define neon_read_at_lsn neon_read_at_lsn_ptr
|
||||
#define neon_dump_relsize_cache neon_dump_relsize_cache_ptr
|
||||
|
||||
/*
|
||||
* test_consume_oids(int4), for rapidly consuming OIDs, to test wraparound.
|
||||
@@ -528,3 +539,11 @@ trigger_segfault(PG_FUNCTION_ARGS)
|
||||
*ptr = 42;
|
||||
PG_RETURN_VOID();
|
||||
}
|
||||
|
||||
|
||||
Datum
|
||||
dump_relsize_cache(PG_FUNCTION_ARGS)
|
||||
{
|
||||
neon_dump_relsize_cache();
|
||||
PG_RETURN_VOID();
|
||||
}
|
||||
|
||||
+2
-1
@@ -2,6 +2,7 @@
|
||||
|
||||
import argparse
|
||||
import enum
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from typing import List
|
||||
@@ -93,7 +94,7 @@ if __name__ == "__main__":
|
||||
"--no-color",
|
||||
action="store_true",
|
||||
help="disable colored output",
|
||||
default=not sys.stdout.isatty(),
|
||||
default=not sys.stdout.isatty() or os.getenv("TERM") == "dumb",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ def random_string(n: int):
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"pageserver_aux_file_policy", [AuxFileStore.V1, AuxFileStore.V2, AuxFileStore.CrossValidation]
|
||||
"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
|
||||
@@ -31,9 +31,7 @@ def test_aux_file_v2_flag(neon_simple_env: NeonEnv, pageserver_aux_file_policy:
|
||||
assert pageserver_aux_file_policy == tenant_config["switch_aux_file_policy"]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"pageserver_aux_file_policy", [AuxFileStore.V1, AuxFileStore.CrossValidation]
|
||||
)
|
||||
@pytest.mark.parametrize("pageserver_aux_file_policy", [AuxFileStore.CrossValidation])
|
||||
def test_logical_replication(neon_simple_env: NeonEnv, vanilla_pg):
|
||||
env = neon_simple_env
|
||||
|
||||
@@ -175,9 +173,7 @@ COMMIT;
|
||||
|
||||
|
||||
# Test that neon.logical_replication_max_snap_files works
|
||||
@pytest.mark.parametrize(
|
||||
"pageserver_aux_file_policy", [AuxFileStore.V1, AuxFileStore.CrossValidation]
|
||||
)
|
||||
@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 (
|
||||
@@ -355,9 +351,7 @@ 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.V1, AuxFileStore.CrossValidation]
|
||||
)
|
||||
@pytest.mark.parametrize("pageserver_aux_file_policy", [AuxFileStore.CrossValidation])
|
||||
def test_restart_endpoint(neon_simple_env: NeonEnv, vanilla_pg):
|
||||
env = neon_simple_env
|
||||
|
||||
@@ -402,9 +396,7 @@ 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.V1, AuxFileStore.CrossValidation]
|
||||
)
|
||||
@pytest.mark.parametrize("pageserver_aux_file_policy", [AuxFileStore.CrossValidation])
|
||||
def test_large_records(neon_simple_env: NeonEnv, vanilla_pg):
|
||||
env = neon_simple_env
|
||||
|
||||
@@ -476,9 +468,7 @@ 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.V1, AuxFileStore.CrossValidation]
|
||||
)
|
||||
@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
|
||||
|
||||
@@ -0,0 +1,187 @@
|
||||
import concurrent.futures
|
||||
import time
|
||||
from contextlib import closing
|
||||
import random
|
||||
|
||||
from fixtures.log_helper import log
|
||||
from fixtures.neon_fixtures import NeonEnv
|
||||
from fixtures.utils import query_scalar
|
||||
|
||||
def test_relsize_cache(neon_simple_env: NeonEnv):
|
||||
"""Stress tests the relsize cache in compute
|
||||
|
||||
The test runs a few different workloads in parallel on the same
|
||||
table:
|
||||
* INSERTs
|
||||
* SELECT with seqscan
|
||||
* VACUUM
|
||||
|
||||
The table is created with 100 indexes, to exercise the relation
|
||||
extension codepath as much as possible.
|
||||
|
||||
At the same time, we run yet another thread which creates a new
|
||||
target table, and switches 'tblname' a global variable, so that
|
||||
all the other threads start to use that too. Sometimes (with 50%
|
||||
probability ), it also TRUNCATEs the old table after switching, so
|
||||
that the relsize "forget" function also gets exercised.
|
||||
|
||||
This test was written to test a bug in locking of the relsize
|
||||
cache's LRU list, which lead to a corrupted LRU list, causing the
|
||||
effective size of the relsize cache to shrink to just a few
|
||||
entries over time as old entries were missing from the LRU list
|
||||
and thus "leaked", with the right workload. This is probably more
|
||||
complicated than necessary to reproduce that particular bug, but
|
||||
it gives a nice variety of concurrent activities on the relsize
|
||||
cache.
|
||||
"""
|
||||
env = neon_simple_env
|
||||
env.neon_cli.create_branch("test_relsize_cache", "empty")
|
||||
|
||||
endpoint = env.endpoints.create_start(
|
||||
"test_relsize_cache",
|
||||
config_lines=[
|
||||
# Make the relsize cache small, so that the LRU-based
|
||||
# eviction gets exercised
|
||||
"neon.relsize_hash_size=100",
|
||||
|
||||
# Use a large shared buffers and LFC, so that it's not
|
||||
# slowed down by getpage requests to storage. They are not
|
||||
# interesting for this test, and we want as much
|
||||
# contention on the relsize cache as possible.
|
||||
"shared_buffers='1000 MB'",
|
||||
"neon.file_cache_path='file.cache'",
|
||||
"neon.max_file_cache_size=512MB",
|
||||
"neon.file_cache_size_limit=512MB",
|
||||
],
|
||||
)
|
||||
|
||||
conn = endpoint.connect()
|
||||
cur = conn.cursor()
|
||||
cur.execute("CREATE EXTENSION amcheck")
|
||||
|
||||
# Function to create the target table
|
||||
def create_tbl(wcur, new_tblname: str):
|
||||
wcur.execute(f"CREATE TABLE {new_tblname} (x bigint, y bigint, z bigint)")
|
||||
for i in range(0, 100):
|
||||
wcur.execute(f"CREATE INDEX relsize_test_idx_{new_tblname}_{i} ON {new_tblname} (x, y, z)")
|
||||
|
||||
# create initial table
|
||||
tblname = "tbl_initial"
|
||||
create_tbl(cur, tblname)
|
||||
|
||||
inserters_running = 0
|
||||
total_inserts = 0
|
||||
|
||||
# XXX
|
||||
def insert_thread(id: int):
|
||||
nonlocal tblname, inserters_running, total_inserts
|
||||
log.info(f"i{id}: inserter thread started")
|
||||
with closing(endpoint.connect()) as wconn:
|
||||
with wconn.cursor() as wcur:
|
||||
|
||||
wcur.execute("set synchronous_commit=off")
|
||||
|
||||
for i in range(0, 100):
|
||||
this_tblname = tblname
|
||||
wcur.execute(
|
||||
f"INSERT INTO {this_tblname} SELECT 1000000000*random(), g, g FROM generate_series(1, 100) g"
|
||||
)
|
||||
total_inserts += 100
|
||||
log.info(f"i{id}: inserted to {this_tblname}")
|
||||
|
||||
inserters_running -= 1
|
||||
log.info(f"inserter thread {id} finished!")
|
||||
|
||||
# This thread periodically creates a new target table
|
||||
def switcher_thread():
|
||||
nonlocal tblname, inserters_running, total_inserts
|
||||
log.info("switcher thread started")
|
||||
wconn = endpoint.connect()
|
||||
wcur = wconn.cursor()
|
||||
|
||||
tblcounter = 0
|
||||
while inserters_running > 0:
|
||||
time.sleep(0.01)
|
||||
old_tblname = tblname
|
||||
|
||||
# Create a new target table and change the global 'tblname' variable to
|
||||
# switch to it
|
||||
tblcounter += 1
|
||||
new_tblname = f"tbl{tblcounter}"
|
||||
create_tbl(wcur, new_tblname)
|
||||
tblname = new_tblname
|
||||
|
||||
# With 50% probability, also truncate the old table, to exercise the
|
||||
# relsize "forget" codepath too
|
||||
if random.random() < 0.5:
|
||||
wcur.execute(f"TRUNCATE {old_tblname}")
|
||||
|
||||
# print a "progress repot"
|
||||
log.info(f"switched to {new_tblname} ({total_inserts} inserts done)")
|
||||
|
||||
# Continuously run vacuum on the target table.
|
||||
#
|
||||
# Vacuum has the effect of invalidating the cached relation size in relcache
|
||||
def vacuum_thread():
|
||||
nonlocal tblname, inserters_running
|
||||
log.info("vacuum thread started")
|
||||
wconn = endpoint.connect()
|
||||
wcur = wconn.cursor()
|
||||
|
||||
while inserters_running > 0:
|
||||
wcur.execute(f"vacuum {tblname}")
|
||||
|
||||
# Continuously query the current target table
|
||||
#
|
||||
# This actually queries not just the latest target table, but a
|
||||
# few latest ones. This is implemented by only updating the target
|
||||
# table with 10% probability on each iteration. This gives a bit
|
||||
# more variability on the relsize entries that are requested from
|
||||
# the cache.
|
||||
def query_thread(id: int):
|
||||
nonlocal tblname, inserters_running
|
||||
log.info(f"q{id}: query thread started")
|
||||
wconn = endpoint.connect()
|
||||
wcur = wconn.cursor()
|
||||
wcur.execute("set max_parallel_workers_per_gather=0")
|
||||
|
||||
this_tblname = tblname
|
||||
while inserters_running > 0:
|
||||
if random.random() < 0.1:
|
||||
this_tblname = tblname
|
||||
wcur.execute(f"select count(*) from {this_tblname}")
|
||||
|
||||
log.info(f"q{id}: query thread finished!")
|
||||
|
||||
# With 'with', this waits for all the threads to finish
|
||||
with concurrent.futures.ThreadPoolExecutor(max_workers=50) as executor:
|
||||
futures = []
|
||||
|
||||
# Launch all the threads
|
||||
f = executor.submit(switcher_thread)
|
||||
futures.append(f)
|
||||
f = executor.submit(vacuum_thread)
|
||||
futures.append(f)
|
||||
|
||||
# 5 inserter threads
|
||||
for i in range(0, 5):
|
||||
f = executor.submit(insert_thread, i)
|
||||
futures.append(f)
|
||||
inserters_running += 1
|
||||
|
||||
# 20 query threads
|
||||
for i in range(0, 20):
|
||||
f = executor.submit(query_thread, i)
|
||||
futures.append(f)
|
||||
|
||||
for f in concurrent.futures.as_completed(futures):
|
||||
ex = f.exception()
|
||||
if ex:
|
||||
log.info(f"exception from thread, stopping: {ex}")
|
||||
inserters_running = 0 # abort the other threads
|
||||
f.result()
|
||||
|
||||
# Finally, run amcheck on all the indexes. Most relsize cache bugs
|
||||
# would result in runtime ERRORs, but doesn't hurt to do more sanity
|
||||
# checking.
|
||||
cur.execute(f"select bt_index_check(oid, true) from pg_class where relname like 'relsize_test_idx%'")
|
||||
Vendored
+1
-1
Submodule vendor/postgres-v14 updated: 3fd7a45f8a...b6910406e2
Vendored
+1
-1
Submodule vendor/postgres-v15 updated: 46b4b235f3...76063bff63
Vendored
+1
-1
Submodule vendor/postgres-v16 updated: 47a9122a5a...8efa089aa7
Vendored
+6
-6
@@ -1,14 +1,14 @@
|
||||
{
|
||||
"v16": [
|
||||
"16.3",
|
||||
"47a9122a5a150a3217fafd3f3d4fe8e020ea718a"
|
||||
"16.4",
|
||||
"8efa089aa7786381543a4f9efc69b92d43eab8c0"
|
||||
],
|
||||
"v15": [
|
||||
"15.7",
|
||||
"46b4b235f38413ab5974bb22c022f9b829257674"
|
||||
"15.8",
|
||||
"76063bff638ccce7afa99fc9037ac51338b9823d"
|
||||
],
|
||||
"v14": [
|
||||
"14.12",
|
||||
"3fd7a45f8aae85c080df6329e3c85887b7f3a737"
|
||||
"14.13",
|
||||
"b6910406e2d05a2c94baa2e530ec882733047759"
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user