Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions docs/limitations.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
93 changes: 93 additions & 0 deletions test/pg19_vacuum_options.sh
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion test/run_all_versions.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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=(
Expand Down
Loading