def test_wal_removal(zenith_env_builder: ZenithEnvBuilder): zenith_env_builder.num_safekeepers = 2 zenith_env_builder.broker = True # to advance remote_consistent_llsn zenith_env_builder.enable_local_fs_remote_storage() env = zenith_env_builder.init_start() env.zenith_cli.create_branch('test_safekeepers_wal_removal') pg = env.postgres.create_start('test_safekeepers_wal_removal') with closing(pg.connect()) as conn: with conn.cursor() as cur: # we rely upon autocommit after each statement # as waiting for acceptors happens there cur.execute('CREATE TABLE t(key int primary key, value text)') cur.execute( "INSERT INTO t SELECT generate_series(1,100000), 'payload'") tenant_id = pg.safe_psql("show zenith.zenith_tenant")[0][0] timeline_id = pg.safe_psql("show zenith.zenith_timeline")[0][0] # force checkpoint to advance remote_consistent_lsn with closing(env.pageserver.connect()) as psconn: with psconn.cursor() as pscur: pscur.execute(f"checkpoint {tenant_id} {timeline_id}") # We will wait for first segment removal. Make sure they exist for starter. first_segments = [ os.path.join(sk.data_dir(), tenant_id, timeline_id, '000000010000000000000001') for sk in env.safekeepers ] assert all(os.path.exists(p) for p in first_segments) http_cli = env.safekeepers[0].http_client() # Pretend WAL is offloaded to s3. http_cli.record_safekeeper_info(tenant_id, timeline_id, {'s3_wal_lsn': 'FFFFFFFF/FEFFFFFF'}) # wait till first segment is removed on all safekeepers started_at = time.time() while True: if all(not os.path.exists(p) for p in first_segments): break elapsed = time.time() - started_at if elapsed > 20: raise RuntimeError( f"timed out waiting {elapsed:.0f}s for first segment get removed" ) time.sleep(0.5)
def test_broker(zenith_env_builder: ZenithEnvBuilder): zenith_env_builder.num_safekeepers = 3 zenith_env_builder.broker = True zenith_env_builder.enable_local_fs_remote_storage() env = zenith_env_builder.init_start() env.zenith_cli.create_branch("test_broker", "main") pg = env.postgres.create_start('test_broker') pg.safe_psql("CREATE TABLE t(key int primary key, value text)") # learn zenith timeline from compute tenant_id = pg.safe_psql("show zenith.zenith_tenant")[0][0] timeline_id = pg.safe_psql("show zenith.zenith_timeline")[0][0] # wait until remote_consistent_lsn gets advanced on all safekeepers clients = [sk.http_client() for sk in env.safekeepers] stat_before = [ cli.timeline_status(tenant_id, timeline_id) for cli in clients ] log.info(f"statuses is {stat_before}") pg.safe_psql("INSERT INTO t SELECT generate_series(1,100), 'payload'") # force checkpoint to advance remote_consistent_lsn with closing(env.pageserver.connect()) as psconn: with psconn.cursor() as pscur: pscur.execute(f"checkpoint {tenant_id} {timeline_id}") # and wait till remote_consistent_lsn propagates to all safekeepers started_at = time.time() while True: stat_after = [ cli.timeline_status(tenant_id, timeline_id) for cli in clients ] if all( lsn_from_hex(s_after.remote_consistent_lsn) > lsn_from_hex( s_before.remote_consistent_lsn) for s_after, s_before in zip(stat_after, stat_before)): break elapsed = time.time() - started_at if elapsed > 20: raise RuntimeError( f"timed out waiting {elapsed:.0f}s for remote_consistent_lsn propagation: status before {stat_before}, status current {stat_after}" ) time.sleep(0.5)
def test_tenant_relocation(zenith_env_builder: ZenithEnvBuilder, port_distributor: PortDistributor, with_load: str): zenith_env_builder.num_safekeepers = 1 zenith_env_builder.enable_local_fs_remote_storage() env = zenith_env_builder.init_start() # create folder for remote storage mock remote_storage_mock_path = env.repo_dir / 'local_fs_remote_storage' tenant = env.zenith_cli.create_tenant( UUID("74ee8b079a0e437eb0afea7d26a07209")) log.info("tenant to relocate %s", tenant) env.zenith_cli.create_root_branch('main', tenant_id=tenant) env.zenith_cli.create_branch('test_tenant_relocation', tenant_id=tenant) tenant_pg = env.postgres.create_start(branch_name='main', node_name='test_tenant_relocation', tenant_id=tenant) # insert some data with closing(tenant_pg.connect()) as conn: with conn.cursor() as cur: # save timeline for later gc call cur.execute("SHOW zenith.zenith_timeline") timeline = UUID(cur.fetchone()[0]) log.info("timeline to relocate %s", timeline.hex) # we rely upon autocommit after each statement # as waiting for acceptors happens there cur.execute("CREATE TABLE t(key int primary key, value text)") cur.execute( "INSERT INTO t SELECT generate_series(1,1000), 'some payload'") cur.execute("SELECT sum(key) FROM t") assert cur.fetchone() == (500500, ) cur.execute("SELECT pg_current_wal_flush_lsn()") current_lsn = lsn_from_hex(cur.fetchone()[0]) pageserver_http = env.pageserver.http_client() # wait until pageserver receives that data wait_for_last_record_lsn(pageserver_http, tenant, timeline, current_lsn) timeline_detail = assert_local(pageserver_http, tenant, timeline) if with_load == 'with_load': # create load table with pg_cur(tenant_pg) as cur: cur.execute("CREATE TABLE load(value text)") load_stop_event = threading.Event() load_ok_event = threading.Event() load_thread = threading.Thread(target=load, args=(tenant_pg, load_stop_event, load_ok_event)) load_thread.start() # run checkpoint manually to be sure that data landed in remote storage with closing(env.pageserver.connect()) as psconn: with psconn.cursor() as pscur: pscur.execute(f"checkpoint {tenant.hex} {timeline.hex}") # wait until pageserver successfully uploaded a checkpoint to remote storage wait_for_upload(pageserver_http, tenant, timeline, current_lsn) log.info("inititalizing new pageserver") # bootstrap second pageserver new_pageserver_dir = env.repo_dir / 'new_pageserver' new_pageserver_dir.mkdir() new_pageserver_pg_port = port_distributor.get_port() new_pageserver_http_port = port_distributor.get_port() log.info("new pageserver ports pg %s http %s", new_pageserver_pg_port, new_pageserver_http_port) pageserver_bin = pathlib.Path(zenith_binpath) / 'pageserver' new_pageserver_http = ZenithPageserverHttpClient( port=new_pageserver_http_port, auth_token=None) with new_pageserver_helper(new_pageserver_dir, pageserver_bin, remote_storage_mock_path, new_pageserver_pg_port, new_pageserver_http_port): # call to attach timeline to new pageserver new_pageserver_http.timeline_attach(tenant, timeline) # new pageserver should be in sync (modulo wal tail or vacuum activity) with the old one because there was no new writes since checkpoint new_timeline_detail = wait_for( number_of_iterations=5, interval=1, func=lambda: assert_local(new_pageserver_http, tenant, timeline)) # when load is active these checks can break because lsns are not static # so lets check with some margin assert_abs_margin_ratio( lsn_from_hex(new_timeline_detail['local']['disk_consistent_lsn']), lsn_from_hex(timeline_detail['local']['disk_consistent_lsn']), 0.03) # callmemaybe to start replication from safekeeper to the new pageserver # when there is no load there is a clean checkpoint and no wal delta # needs to be streamed to the new pageserver # TODO (rodionov) use attach to start replication with pg_cur(PgProtocol(host='localhost', port=new_pageserver_pg_port)) as cur: # "callmemaybe {} {} host={} port={} options='-c ztimelineid={} ztenantid={}'" safekeeper_connstring = f"host=localhost port={env.safekeepers[0].port.pg} options='-c ztimelineid={timeline} ztenantid={tenant} pageserver_connstr=postgresql://no_user:@localhost:{new_pageserver_pg_port}'" cur.execute("callmemaybe {} {} {}".format(tenant.hex, timeline.hex, safekeeper_connstring)) tenant_pg.stop() # rewrite zenith cli config to use new pageserver for basebackup to start new compute cli_config_lines = (env.repo_dir / 'config').read_text().splitlines() cli_config_lines[ -2] = f"listen_http_addr = 'localhost:{new_pageserver_http_port}'" cli_config_lines[ -1] = f"listen_pg_addr = 'localhost:{new_pageserver_pg_port}'" (env.repo_dir / 'config').write_text('\n'.join(cli_config_lines)) tenant_pg_config_file_path = pathlib.Path(tenant_pg.config_file_path()) tenant_pg_config_file_path.open('a').write( f"\nzenith.page_server_connstring = 'postgresql://no_user:@localhost:{new_pageserver_pg_port}'" ) tenant_pg.start() # detach tenant from old pageserver before we check # that all the data is there to be sure that old pageserver # is no longer involved, and if it is, we will see the errors pageserver_http.timeline_detach(tenant, timeline) with pg_cur(tenant_pg) as cur: # check that data is still there cur.execute("SELECT sum(key) FROM t") assert cur.fetchone() == (500500, ) # check that we can write new data cur.execute( "INSERT INTO t SELECT generate_series(1001,2000), 'some payload'" ) cur.execute("SELECT sum(key) FROM t") assert cur.fetchone() == (2001000, ) if with_load == 'with_load': assert load_ok_event.wait(3) log.info('stopping load thread') load_stop_event.set() load_thread.join(timeout=10) log.info('load thread stopped') # bring old pageserver back for clean shutdown via zenith cli # new pageserver will be shut down by the context manager cli_config_lines = (env.repo_dir / 'config').read_text().splitlines() cli_config_lines[ -2] = f"listen_http_addr = 'localhost:{env.pageserver.service_port.http}'" cli_config_lines[ -1] = f"listen_pg_addr = 'localhost:{env.pageserver.service_port.pg}'" (env.repo_dir / 'config').write_text('\n'.join(cli_config_lines))
def test_remote_storage_backup_and_restore( zenith_env_builder: ZenithEnvBuilder, storage_type: str): zenith_env_builder.rust_log_override = 'debug' zenith_env_builder.num_safekeepers = 1 if storage_type == 'local_fs': zenith_env_builder.enable_local_fs_remote_storage() elif storage_type == 'mock_s3': zenith_env_builder.enable_s3_mock_remote_storage( 'test_remote_storage_backup_and_restore') else: raise RuntimeError(f'Unknown storage type: {storage_type}') data_id = 1 data_secret = 'very secret secret' ##### First start, insert secret data and upload it to the remote storage env = zenith_env_builder.init_start() pg = env.postgres.create_start('main') client = env.pageserver.http_client() tenant_id = pg.safe_psql("show zenith.zenith_tenant")[0][0] timeline_id = pg.safe_psql("show zenith.zenith_timeline")[0][0] checkpoint_numbers = range(1, 3) for checkpoint_number in checkpoint_numbers: with closing(pg.connect()) as conn: with conn.cursor() as cur: cur.execute(f''' CREATE TABLE t{checkpoint_number}(id int primary key, secret text); INSERT INTO t{checkpoint_number} VALUES ({data_id}, '{data_secret}|{checkpoint_number}'); ''') cur.execute("SELECT pg_current_wal_flush_lsn()") current_lsn = lsn_from_hex(cur.fetchone()[0]) # wait until pageserver receives that data wait_for_last_record_lsn(client, UUID(tenant_id), UUID(timeline_id), current_lsn) # run checkpoint manually to be sure that data landed in remote storage with closing(env.pageserver.connect()) as psconn: with psconn.cursor() as pscur: pscur.execute(f"checkpoint {tenant_id} {timeline_id}") log.info(f'waiting for checkpoint {checkpoint_number} upload') # wait until pageserver successfully uploaded a checkpoint to remote storage wait_for_upload(client, UUID(tenant_id), UUID(timeline_id), current_lsn) log.info(f'upload of checkpoint {checkpoint_number} is done') ##### Stop the first pageserver instance, erase all its data env.postgres.stop_all() env.pageserver.stop() dir_to_clear = Path(env.repo_dir) / 'tenants' shutil.rmtree(dir_to_clear) os.mkdir(dir_to_clear) ##### Second start, restore the data and ensure it's the same env.pageserver.start() client.timeline_attach(UUID(tenant_id), UUID(timeline_id)) log.info("waiting for timeline redownload") wait_for( number_of_iterations=10, interval=1, func=lambda: assert_local(client, UUID(tenant_id), UUID(timeline_id))) pg = env.postgres.create_start('main') with closing(pg.connect()) as conn: with conn.cursor() as cur: for checkpoint_number in checkpoint_numbers: cur.execute( f'SELECT secret FROM t{checkpoint_number} WHERE id = {data_id};' ) assert cur.fetchone() == ( f'{data_secret}|{checkpoint_number}', )