diff --git a/docs/limitations.md b/docs/limitations.md index e587400..db636d7 100644 --- a/docs/limitations.md +++ b/docs/limitations.md @@ -268,10 +268,22 @@ returning no rows. ## Vacuum and compaction +<<<<<<< HEAD +- `autovacuum_parallel_workers`, the per-table storage parameter PostgreSQL 19 adds + for parallel autovacuum, is accepted on a columnar table and has no effect. + pgColumnar implements its own vacuum, which marks the visibility map and retires + fully-deleted row groups, and does no parallel work. The parameter cannot be + refused: storage-parameter validation belongs to PostgreSQL and is driven by the + relation kind rather than by the access method. Setting it is harmless and + pointless. +- `VACUUM FULL` and `CLUSTER` are not supported on a columnar table; the + copy-for-cluster path raises an error. Use `pgcolumnar.vacuum` or +======= - `REPACK`, `VACUUM FULL` and `CLUSTER` are not supported on a columnar table; the copy-for-cluster path raises an error. `REPACK` arrives in PostgreSQL 19 and replaces the other two, and it dispatches through the same path, so it is refused for the same reason. Use `pgcolumnar.vacuum` or +>>>>>>> origin/main `pgcolumnar.vacuum_full` instead. - `pgcolumnar.vacuum` always rewrites the whole relation into full row groups. It accepts a `stripe_count` argument for compatibility with the interface, but it diff --git a/test/pg19_vacuum_options.sh b/test/pg19_vacuum_options.sh new file mode 100755 index 0000000..c008e44 --- /dev/null +++ b/test/pg19_vacuum_options.sh @@ -0,0 +1,93 @@ +#!/usr/bin/env bash +# +# PostgreSQL 19 parallel autovacuum, on a columnar table (issue #398). +# +# 19 adds parallel autovacuum: a server variable autovacuum_max_parallel_workers +# and a per-table storage parameter autovacuum_parallel_workers. pgColumnar +# implements its own relation_vacuum callback, so the question was whether a table +# access method participates in that at all. +# +# Measured answer: the storage parameter is ACCEPTED on a columnar table and lands +# in pg_class.reloptions exactly as it does on heap, and it has NO EFFECT, because +# pgcolumnar_relation_vacuum marks the visibility map and retires fully-deleted +# row groups and does no parallel work of any kind. A parameter a user can set and +# that silently does nothing is worth pinning, so it is recorded here and in +# docs/limitations.md rather than left to be discovered. +# +# It cannot simply be rejected. Storage-parameter validation is core's, driven by +# relkind rather than by the access method, and pgColumnar has no reloptions hook +# to refuse it from. Documenting it is the available honest option. +# +# The heap arm is a control rather than decoration: without it, "the parameter does +# nothing here" cannot be told apart from "parallel autovacuum does nothing on this +# cluster", and autovacuum_max_parallel_workers defaults to 0 on 19beta2, which +# would make the second reading true for everyone. +# +# Usage: test/pg19_vacuum_options.sh [PG_CONFIG] +# Written fresh for pgColumnar. + +set -uo pipefail +. "$(dirname "${BASH_SOURCE[0]}")/lib.sh" + +pgc_setup "${1:-/usr/local/pg19/bin/pg_config}" + +srv="$(q 'SHOW server_version_num')" +if [ "${srv:-0}" -lt 190000 ]; then + echo "SKIP parallel autovacuum requires PostgreSQL 19 (server_version_num=$srv)" + echo "pg19_vacuum_options.sh: SKIPPED" + exit 0 +fi + +ROWS=${PGC_AV_ROWS:-100000} + +psql_run "CREATE TABLE av_c (id int, v text) USING pgcolumnar; + CREATE TABLE av_h (id int, v text); + INSERT INTO av_c SELECT g,'x'||g FROM generate_series(1,$ROWS) g; + INSERT INTO av_h SELECT g,'x'||g FROM generate_series(1,$ROWS) g; + DELETE FROM av_c WHERE id % 4 = 0; + DELETE FROM av_h WHERE id % 4 = 0;" + +# ---- the setting exists, and is off by default ------------------------------- +check "autovacuum_max_parallel_workers exists on this server" \ + "$(q 'SHOW autovacuum_max_parallel_workers' >/dev/null 2>&1 && echo yes || echo no)" "yes" + +# ---- the storage parameter is accepted on BOTH access methods ---------------- +# This is the answer to the issue: a table AM does not get to refuse it. +for t in av_c av_h; do + out="$(psql_run "ALTER TABLE $t SET (autovacuum_parallel_workers = 3);" 2>&1)" + check "autovacuum_parallel_workers is accepted on $t" \ + "$(case "$out" in *ERROR*) grep -oE 'ERROR:.*' <<<"$out" | head -1 ;; *) echo accepted ;; esac)" \ + "accepted" + check "and it is recorded in reloptions for $t" \ + "$(q "SELECT array_to_string(reloptions,',') FROM pg_class WHERE relname='$t'")" \ + "autovacuum_parallel_workers=3" +done + +# ---- and it changes nothing about what our vacuum does ----------------------- +# Same live rows before and after, with the parameter set. The point is not that +# vacuum works; it is that setting the parameter neither helps nor breaks it. +live_before="$(q 'SELECT count(*) FROM av_c')" +hash_before="$(q "SELECT md5(string_agg(id::text||':'||v, ',' ORDER BY id)) FROM av_c")" + +check "VACUUM on a columnar table with the parameter set succeeds" \ + "$(psql_run 'VACUUM av_c;' 2>&1 | grep -c 'ERROR' || true)" "0" +check "VACUUM (PARALLEL 2) on a columnar table succeeds" \ + "$(psql_run 'VACUUM (PARALLEL 2) av_c;' 2>&1 | grep -c 'ERROR' || true)" "0" +check "pgcolumnar.vacuum with the parameter set succeeds" \ + "$(psql_run "SELECT pgcolumnar.vacuum('av_c');" 2>&1 | grep -c 'ERROR' || true)" "0" + +check "the rows are unchanged by any of it" "$(q 'SELECT count(*) FROM av_c')" "$live_before" +check "and the content is unchanged" \ + "$(q "SELECT md5(string_agg(id::text||':'||v, ',' ORDER BY id)) FROM av_c")" "$hash_before" + +# ---- the control: heap reports parallel vacuum activity, columnar does not ---- +# If a future release makes table AMs participate, this check flips and should be +# rewritten to assert that our vacuum reports workers too. +vh="$(psql_run 'VACUUM (VERBOSE) av_h;' 2>&1)" +vc="$(psql_run 'VACUUM (VERBOSE) av_c;' 2>&1)" +check "control: VACUUM VERBOSE reports on the heap table" \ + "$(grep -qi 'finished vacuuming' <<<"$vh" && echo yes || echo no)" "yes" +check "columnar VACUUM VERBOSE reports nothing, because the AM path is ours" \ + "$(grep -qi 'finished vacuuming .*av_c' <<<"$vc" && echo "reports" || echo "silent")" "silent" + +pgc_summary diff --git a/test/run_all_versions.sh b/test/run_all_versions.sh index 2290c8b..3bb2cce 100755 --- a/test/run_all_versions.sh +++ b/test/run_all_versions.sh @@ -208,7 +208,7 @@ SRCDIR="${PGC_RUN_SRCDIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}" SUITES=(harness_selftest docs_style smoke phase2 phase3 phase4 phase5 phase6 audit concurrency unique_conc \ differential recovery replication native_backend_crash fuzz fuzz_parquet fuzz_arrow hardening concurrent_diff parallel sorted_projection \ arrow_export parquet_export read_stream corruption \ - generated_columns temporal arrow_import index_only projections arrow_nested parquet_import parquet_nested arrow_nested_import parquet_nested_import native_writer native_roundtrip native_encoding native_fastdecode native_zonemap write_minmax_fastpath write_fsst_compressed fsst_margin encode_invariants encode_effort native_skip pushdown_report native_agg native_agg_deletes native_agg_addcolumn native_groupagg ungrouped_vector_agg parallel_vector_agg native_bloom bloom_setting bloom_lazy native_vecskip native_index native_fetch_position native_dml alter_column_type native_ios native_projection native_cluster native_repack native_compact native_recluster recluster_extent native_vacuum_race native_sort_by sort_status native_reclaim native_ownership drop_cleanup pg_dump_roundtrip native_reclaim_cycles native_reclaim_frag native_reclaim_reconcile native_gap native_format native_truncate native_rewrite native_rewrite_conc rewrite_group_scan native_parquet_schema native_read_parquet native_parquet_fdw native_parquet_pushdown native_parquet_hardening server_file_privilege native_parquet_stack native_parquet_units native_parquet_flba native_parquet_codecs native_parquet_projection native_parquet_multifile native_parquet_streaming native_parquet_partition native_cancel cancel_decode wal_envelope decode_interrupts import_exclusion import_deferred parallel_copy parallel_export_parquet fk_referencing row_triggers native_lazy_slot native_ctas native_fetch_cache native_fetch_interrupt analyze_stats analyze_reltuples native_fetch_projection column_projection isolation) + generated_columns temporal arrow_import index_only projections arrow_nested parquet_import parquet_nested arrow_nested_import parquet_nested_import native_writer native_roundtrip native_encoding native_fastdecode native_zonemap write_minmax_fastpath write_fsst_compressed fsst_margin encode_invariants encode_effort native_skip pushdown_report native_agg native_agg_deletes native_agg_addcolumn native_groupagg ungrouped_vector_agg parallel_vector_agg native_bloom bloom_setting bloom_lazy native_vecskip native_index native_fetch_position native_dml alter_column_type native_ios native_projection native_cluster pg19_vacuum_options native_repack native_compact native_recluster recluster_extent native_vacuum_race native_sort_by sort_status native_reclaim native_ownership drop_cleanup pg_dump_roundtrip native_reclaim_cycles native_reclaim_frag native_reclaim_reconcile native_gap native_format native_truncate native_rewrite native_rewrite_conc rewrite_group_scan native_parquet_schema native_read_parquet native_parquet_fdw native_parquet_pushdown native_parquet_hardening server_file_privilege native_parquet_stack native_parquet_units native_parquet_flba native_parquet_codecs native_parquet_projection native_parquet_multifile native_parquet_streaming native_parquet_partition native_cancel cancel_decode wal_envelope decode_interrupts import_exclusion import_deferred parallel_copy parallel_export_parquet fk_referencing row_triggers native_lazy_slot native_ctas native_fetch_cache native_fetch_interrupt analyze_stats analyze_reltuples native_fetch_projection column_projection isolation) # Default matrix: one assert-enabled pg_config per major, 15 through 19. DEFAULT_CONFIGS=(