Compare commits

..

22 Commits

Author SHA1 Message Date
Maksym Buz
f38e9677e5 Enhance partitioning logic and update Zabbix config template 2026-04-02 17:02:16 +00:00
Maksym Buz
a28a789454 docs(architecture): remove deprecated auditlog prep script step and correct numbering 2026-03-30 21:22:16 +00:00
Maksym Buz
3b4f0c9c75 build(repo): structure the repo into postgresql subdirectory with separate template and internal tests 2026-03-30 21:18:01 +00:00
Maksym Buz
7339bf5db0 chore(template): update template compatibility version to 7.0 2026-03-30 21:01:28 +00:00
Maksym Buz
39e37286f2 docs(procedures): refine zabbix monitor user instructions and pg_cron management section 2026-03-30 20:59:06 +00:00
Maksym Buz
db2bc25a84 docs: finalize architecture docs, agent 2 template and procedures privileges 2026-03-30 20:53:04 +00:00
Maksym Buz
32a587172e docs: move pg_cron job management instructions to generic maintenance section 2026-03-30 20:46:55 +00:00
Maksym Buz
505933e880 feat(template): remove ODBC template, move agent2 SQL, and track template directory 2026-03-30 19:55:12 +00:00
Maksym Buz
119b25f1a0 feat(versioning): update versioning scheme to use zabbix major releases (7-1) 2026-03-30 19:51:56 +00:00
Maksym Buz
9d77fac4a4 refactor(procedures): rename init script and revert monitoring view 2026-03-30 19:40:32 +00:00
Maksym Buz
888f61a2c8 docs: add table of contents to procedures readme and update gitignore 2026-03-30 19:13:58 +00:00
Maksym Buz
944b463b75 feat(monitoring): expose raw total_size_bytes for native Zabbix graphing 2026-03-26 19:48:41 +00:00
Maksym Buz
59170a77e6 fix(partitioning): add exact Zabbix auditlog indexes with renamed suffix to prevent IF NOT EXISTS collision 2026-03-26 19:35:53 +00:00
Maksym Buz
2b7a69ba11 Refactor auditlog preparation, rename procedures sequentially, and update test suite 2026-03-26 15:57:35 +00:00
Maksym Buz
14f38efafd feat: Add support for AWS RDS deployment and enhance maintenance scheduling documentation with pg_cron and Systemd Timer options. 2026-03-20 17:56:09 +00:00
Maksym Buz
487f95020d style: Remove redundant script and description prefixes from SQL file comment headers. 2026-02-20 21:58:06 +00:00
Maksym Buz
ea3e89effa feat: Enhanced partitioning procedures with schema awareness. 2026-02-20 21:46:27 +00:00
Maksym Buz
9d1b84225c docs: Change admonition type from critical to warning in README.md 2026-02-20 18:57:45 +00:00
Maksym Buz
c77eb8e4af docs: Correct housekeeping path in the critical warning section of the README. 2026-02-20 17:56:25 +00:00
Maksym Buz
91eb4e17b8 fix: correct syntax errors and refactor interval parsing 2026-02-19 23:20:09 +00:00
Maksym Buz
d7b8c7c9c3 change: Added test scripts for Docker initialization 2026-02-19 22:09:54 +00:00
Maksym Buz
c4420bc1ad refactor: COMMITs added to release locks immediately. UTC usage. Testing env for test branch. 2026-02-19 21:53:51 +00:00
19 changed files with 148189 additions and 159077 deletions

3
.gitattributes vendored
View File

@@ -1,3 +0,0 @@
postgresql/tests/ export-ignore
tests/ export-ignore
.gitignore export-ignore

View File

@@ -1,15 +0,0 @@
# Changelog
All notable changes to this project will be documented in this file.
## [7-2] - 2026-04-29
### Added
- **Housekeeper Interceptor**: Added a PostgreSQL `BEFORE INSERT` trigger to the `housekeeper` table. This trigger dynamically checks `partitions.config` and silently discards Housekeeper tasks (inserts) for any tables managed by the partitioning system. This prevents Zabbix from filling the `housekeeper` table with unnecessary tasks, eliminating Sequential Scans and Table Bloat on the `housekeeper` table, while still allowing tasks for unpartitioned tables (like `events`) to be recorded normally.
## [7-1] - Previous Release
### Added
- Zabbix 7.0 and 7.4 compatibility.
- Special handling for the `auditlog` table primary key changes in Zabbix 7.0+.
- Dynamic creation of partitions and retention management logic.

View File

@@ -15,14 +15,6 @@ CREATE TABLE IF NOT EXISTS partitions.config (
PRIMARY KEY (table_name)
);
-- Ensure the future_partitions column exists for users upgrading from older versions
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'partitions' AND table_name = 'config' AND column_name = 'future_partitions') THEN
ALTER TABLE partitions.config ADD COLUMN future_partitions integer NOT NULL DEFAULT 5;
END IF;
END $$;
-- Table to track installed version of the partitioning solution
CREATE TABLE IF NOT EXISTS partitions.version (
version text PRIMARY KEY,
@@ -30,7 +22,7 @@ CREATE TABLE IF NOT EXISTS partitions.version (
description text
);
INSERT INTO partitions.version (version, description) VALUES ('7-2', 'Added housekeeper task interceptor trigger to drop tasks for partitioned tables')
INSERT INTO partitions.version (version, description) VALUES ('7-1', 'Zabbix 7.4 and 7.0 compatible version')
ON CONFLICT (version) DO NOTHING;
-- Default configuration for Zabbix tables (adjust as needed)
@@ -40,16 +32,9 @@ INSERT INTO partitions.config (table_name, period, keep_history) VALUES
('history_uint', 'day', '30 days'),
('history_str', 'day', '30 days'),
('history_log', 'day', '30 days'),
('history_text', 'day', '30 days'),
('history_bin', 'day', '30 days')
('history_text', 'day', '30 days')
ON CONFLICT (table_name) DO NOTHING;
-- Zabbix 8.0+ only: Uncomment the following lines if running Zabbix 8.0 or later
-- INSERT INTO partitions.config (table_name, period, keep_history) VALUES
-- ('history_json', 'day', '30 days')
-- ON CONFLICT (table_name) DO NOTHING;
-- Trends tables: Monthly partitions, keep 12 months
INSERT INTO partitions.config (table_name, period, keep_history) VALUES
('trends', 'month', '12 months'),

View File

@@ -2,15 +2,14 @@
-- Core functions for Zabbix partitioning (Create, Drop, Maintain).
-- ============================================================================
-- Function to check if a partition exists in a specific schema
CREATE OR REPLACE FUNCTION partitions.partition_exists(p_partition_name text, p_schema text)
-- Function to check if a partition exists
CREATE OR REPLACE FUNCTION partitions.partition_exists(p_partition_name text)
RETURNS boolean AS $$
BEGIN
RETURN EXISTS (
SELECT 1 FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE c.relname = p_partition_name
AND n.nspname = p_schema
);
END;
$$ LANGUAGE plpgsql;
@@ -33,7 +32,7 @@ BEGIN
SELECT n.nspname INTO v_parent_schema
FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE c.relname = p_parent_table AND pg_table_is_visible(c.oid);
WHERE c.relname = p_parent_table;
IF NOT FOUND THEN
RAISE EXCEPTION 'Parent table % not found', p_parent_table;
@@ -52,20 +51,11 @@ BEGIN
v_partition_name := p_parent_table || '_p' || v_suffix;
IF NOT partitions.partition_exists(v_partition_name, v_parent_schema) THEN
BEGIN
IF NOT partitions.partition_exists(v_partition_name) THEN
EXECUTE format(
'CREATE TABLE %I.%I PARTITION OF %I.%I FOR VALUES FROM (%s) TO (%s)',
v_parent_schema, v_partition_name, v_parent_schema, p_parent_table, v_start_ts, v_end_ts
);
EXCEPTION
WHEN invalid_object_definition THEN
-- Ignore overlap errors (e.g., when transitioning from daily to hourly partitioning)
RAISE NOTICE 'Partition % overlaps with an existing partition. Skipping.', v_partition_name;
WHEN duplicate_table THEN
-- Ignore race condition: another process created the partition concurrently
RAISE NOTICE 'Partition % already exists (concurrent creation). Skipping.', v_partition_name;
END;
END IF;
END;
$$;
@@ -94,7 +84,7 @@ BEGIN
JOIN pg_class parent ON pg_inherits.inhparent = parent.oid
JOIN pg_class child ON pg_inherits.inhrelid = child.oid
JOIN pg_namespace n ON child.relnamespace = n.oid
WHERE parent.relname = p_parent_table AND pg_table_is_visible(parent.oid)
WHERE parent.relname = p_parent_table
LOOP
-- Parse partition suffix to determine age
-- Format: parent_pYYYYMM or parent_pYYYYMMDD
@@ -102,11 +92,11 @@ BEGIN
BEGIN
IF length(v_suffix) = 6 THEN -- YYYYMM
v_partition_date := timezone('UTC', to_timestamp(v_suffix || '01', 'YYYYMMDD')::timestamp without time zone);
v_partition_date := to_timestamp(v_suffix || '01', 'YYYYMMDD') AT TIME ZONE 'UTC';
ELSIF length(v_suffix) = 8 THEN -- YYYYMMDD
v_partition_date := timezone('UTC', to_timestamp(v_suffix, 'YYYYMMDD')::timestamp without time zone);
v_partition_date := to_timestamp(v_suffix, 'YYYYMMDD') AT TIME ZONE 'UTC';
ELSIF length(v_suffix) = 10 THEN -- YYYYMMDDHH
v_partition_date := timezone('UTC', to_timestamp(v_suffix, 'YYYYMMDDHH24')::timestamp without time zone);
v_partition_date := to_timestamp(v_suffix, 'YYYYMMDDHH24') AT TIME ZONE 'UTC';
ELSE
CONTINUE; -- Ignore non-matching suffix lengths
END IF;
@@ -163,25 +153,25 @@ DECLARE
BEGIN
IF p_period = 'day' THEN
v_period_interval := '1 day'::interval;
v_start_time := date_trunc('day', now(), 'UTC');
v_start_time := date_trunc('day', now() AT TIME ZONE 'UTC');
-- Calculate how many past days cover the retention period (86400 seconds = 1 day)
v_past_iterations := ceil(extract(epoch from p_keep_history) / 86400)::integer;
ELSIF p_period = 'week' THEN
v_period_interval := '1 week'::interval;
v_start_time := date_trunc('week', now(), 'UTC');
v_start_time := date_trunc('week', now() AT TIME ZONE 'UTC');
-- 604800 seconds = 1 week
v_past_iterations := ceil(extract(epoch from p_keep_history) / 604800)::integer;
ELSIF p_period = 'month' THEN
v_period_interval := '1 month'::interval;
v_start_time := date_trunc('month', now(), 'UTC');
v_start_time := date_trunc('month', now() AT TIME ZONE 'UTC');
-- Approximate 30 days per month (2592000 seconds)
v_past_iterations := ceil(extract(epoch from p_keep_history) / 2592000)::integer;
ELSIF p_period LIKE '%hour%' THEN
v_period_interval := p_period::interval;
v_start_time := to_timestamp(floor(extract(epoch from now()) / extract(epoch from v_period_interval)) * extract(epoch from v_period_interval));
v_start_time := date_trunc('hour', now() AT TIME ZONE 'UTC');
v_past_iterations := ceil(extract(epoch from p_keep_history) / extract(epoch from v_period_interval))::integer;
ELSE
@@ -231,14 +221,3 @@ BEGIN
END LOOP;
END;
$$;
-- Trigger function to silently discard housekeeper tasks for partitioned tables
CREATE OR REPLACE FUNCTION partitions.housekeeper_insert_trigger()
RETURNS TRIGGER AS $$
BEGIN
IF EXISTS (SELECT 1 FROM partitions.config WHERE table_name = NEW.tablename) THEN
RETURN NULL;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;

View File

@@ -19,10 +19,10 @@ BEGIN
SELECT n.nspname INTO v_schema
FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE c.relname = v_table AND pg_table_is_visible(c.oid);
WHERE c.relname = v_table;
IF EXISTS (SELECT 1 FROM pg_class WHERE relname = v_table AND relkind = 'r' AND pg_table_is_visible(oid)) THEN
IF EXISTS (SELECT 1 FROM pg_class WHERE relname = v_table AND relkind = 'r') THEN
RAISE NOTICE 'Converting table % to partitioned table...', v_table;
-- 1. Rename existing table
@@ -48,50 +48,12 @@ BEGIN
-- Optional: Migrate existing data
-- EXECUTE format('INSERT INTO %I.%I SELECT * FROM %I.%I', v_schema, v_table, v_schema, v_old_table);
ELSIF EXISTS (SELECT 1 FROM pg_class WHERE relname = v_table AND relkind = 'p' AND pg_table_is_visible(oid)) THEN
ELSIF EXISTS (SELECT 1 FROM pg_class WHERE relname = v_table AND relkind = 'p') THEN
RAISE NOTICE 'Table % is already partitioned. Skipping conversion.', v_table;
-- Just run maintenance for this specific table to ensure partitions exist
CALL partitions.maintain_table(v_table, v_row.period, v_row.keep_history, v_row.future_partitions);
-- Just run maintenance to ensure partitions exist
CALL partitions.run_maintenance();
ELSE
RAISE WARNING 'Table % not found!', v_table;
END IF;
END LOOP;
-- Attach trigger to housekeeper table to silently discard tasks for partitioned tables.
-- Dynamically determine the schema of the housekeeper table to support custom schemas.
SELECT n.nspname INTO v_schema
FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE c.relname = 'housekeeper' AND pg_table_is_visible(c.oid);
IF v_schema IS NOT NULL THEN
EXECUTE format('DROP TRIGGER IF EXISTS housekeeper_filter ON %I.housekeeper', v_schema);
EXECUTE format('CREATE TRIGGER housekeeper_filter BEFORE INSERT ON %I.housekeeper FOR EACH ROW EXECUTE FUNCTION partitions.housekeeper_insert_trigger()', v_schema);
RAISE NOTICE 'Housekeeper intercept trigger installed on %.housekeeper', v_schema;
ELSE
RAISE WARNING 'housekeeper table not found — trigger NOT installed!';
END IF;
END $$;
-- ==========================================================================
-- IMPORTANT: To ensure the Zabbix Server can insert into the housekeeper
-- table without permission issues, we automatically grant access to the
-- partitions schema to the owner of the housekeeper table.
-- ==========================================================================
DO $$
DECLARE
v_owner text;
BEGIN
SELECT pg_get_userbyid(c.relowner) INTO v_owner
FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE c.relname = 'housekeeper' AND pg_table_is_visible(c.oid);
IF v_owner IS NOT NULL THEN
EXECUTE format('GRANT USAGE ON SCHEMA partitions TO %I', v_owner);
EXECUTE format('GRANT SELECT ON partitions.config TO %I', v_owner);
RAISE NOTICE 'Granted partitions schema permissions to housekeeper owner: %', v_owner;
ELSE
RAISE WARNING 'housekeeper table not found, could not grant permissions automatically!';
END IF;
END $$;

View File

@@ -2,13 +2,13 @@
-- Creates a view to monitor partition status and sizes.
-- ============================================================================
CREATE OR REPLACE VIEW partitions.monitoring AS
DROP VIEW IF EXISTS partitions.monitoring;
CREATE VIEW partitions.monitoring AS
SELECT
parent.relname AS parent_table,
c.table_name,
c.period,
c.keep_history,
c.future_partitions AS configured_future_partitions,
count(child.relname) AS partition_count,
count(child.relname) FILTER (
WHERE
@@ -19,24 +19,15 @@ SELECT
(c.period = 'week' AND child.relname > (parent.relname || '_p' || to_char(date_trunc('week', now() AT TIME ZONE 'UTC'), 'YYYYMMDD')))
OR
(c.period LIKE '%hour%' AND child.relname > (parent.relname || '_p' || to_char(now() AT TIME ZONE 'UTC', 'YYYYMMDDHH24')))
) AS actual_future_partitions,
) AS future_partitions,
sum(pg_total_relation_size(child.oid)) AS total_size_bytes,
pg_size_pretty(sum(pg_total_relation_size(child.oid))) AS total_size,
min(child.relname) AS oldest_partition,
max(child.relname) AS newest_partition,
c.last_updated
FROM partitions.config c
JOIN pg_class parent ON parent.relname = c.table_name AND pg_table_is_visible(parent.oid)
JOIN pg_class parent ON parent.relname = c.table_name
LEFT JOIN pg_inherits ON pg_inherits.inhparent = parent.oid
LEFT JOIN pg_class child ON pg_inherits.inhrelid = child.oid
WHERE parent.relkind = 'p' -- Only partitioned tables
GROUP BY parent.relname, c.table_name, c.period, c.keep_history, c.future_partitions, c.last_updated;
-- ============================================================================
-- IMPORTANT: Since DROP VIEW / CREATE VIEW resets permissions, we automatically
-- grant read access to PUBLIC. This ensures that whatever monitoring user
-- (e.g. zbx_monitor) is connecting to the DB will always be able to read
-- the monitoring stats without manual intervention after upgrades.
-- ==========================================================================
GRANT USAGE ON SCHEMA partitions TO PUBLIC;
GRANT SELECT ON partitions.monitoring TO PUBLIC;
GROUP BY parent.relname, c.table_name, c.period, c.keep_history, c.last_updated;

View File

@@ -1,79 +0,0 @@
-- ============================================================================
-- Reverts Zabbix partitioned tables back to standard non-partitioned tables.
-- Existing partitioned tables will be renamed to *_part (data is preserved).
-- ============================================================================
DO $$
DECLARE
v_row record;
v_table text;
v_part_table text;
v_schema text;
BEGIN
FOR v_row IN SELECT * FROM partitions.config LOOP
v_table := v_row.table_name;
v_part_table := v_table || '_part';
-- Determine schema of the partitioned table
SELECT n.nspname INTO v_schema
FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE c.relname = v_table AND c.relkind = 'p' AND pg_table_is_visible(c.oid);
IF v_schema IS NOT NULL THEN
RAISE NOTICE 'Reverting partitioned table %...', v_table;
-- 1. Rename existing partitioned table to *_part
EXECUTE format('ALTER TABLE %I.%I RENAME TO %I', v_schema, v_table, v_part_table);
-- 2. Create standard (unpartitioned) replacement table based on the structure
IF v_table = 'auditlog' THEN
-- For auditlog, we need to try and restore the original single-column PK (auditid) if possible
EXECUTE format('CREATE TABLE %I.%I (LIKE %I.%I INCLUDING DEFAULTS INCLUDING COMMENTS)', v_schema, v_table, v_schema, v_part_table);
BEGIN
EXECUTE format('ALTER TABLE %I.%I ADD PRIMARY KEY (auditid)', v_schema, v_table);
EXCEPTION WHEN others THEN
RAISE WARNING 'Failed to create primary key on auditlog, might already exist or duplicates present.';
END;
EXECUTE format('CREATE INDEX IF NOT EXISTS auditlog_1 ON %I.%I (userid, clock)', v_schema, v_table);
EXECUTE format('CREATE INDEX IF NOT EXISTS auditlog_2 ON %I.%I (clock)', v_schema, v_table);
EXECUTE format('CREATE INDEX IF NOT EXISTS auditlog_3 ON %I.%I (resourcetype, resourceid)', v_schema, v_table);
EXECUTE format('CREATE INDEX IF NOT EXISTS auditlog_4 ON %I.%I (recordsetid)', v_schema, v_table);
EXECUTE format('CREATE INDEX IF NOT EXISTS auditlog_5 ON %I.%I (ip)', v_schema, v_table);
ELSE
-- For others, copy everything including indexes
EXECUTE format('CREATE TABLE %I.%I (LIKE %I.%I INCLUDING ALL)', v_schema, v_table, v_schema, v_part_table);
END IF;
RAISE NOTICE 'SUCCESS: % reverted to default. Partitioned data stored in % (You can DROP TABLE % CASCADE; later).', v_table, v_part_table, v_part_table;
ELSIF EXISTS (SELECT 1 FROM pg_class WHERE relname = v_table AND relkind = 'r' AND pg_table_is_visible(oid)) THEN
RAISE NOTICE 'Table % is already a regular table. Skipping.', v_table;
ELSE
RAISE WARNING 'Partitioned table % not found!', v_table;
END IF;
END LOOP;
-- Drop the housekeeper intercept trigger (dynamically determine schema for custom schema support)
SELECT n.nspname INTO v_schema
FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE c.relname = 'housekeeper' AND pg_table_is_visible(c.oid);
IF v_schema IS NOT NULL THEN
EXECUTE format('DROP TRIGGER IF EXISTS housekeeper_filter ON %I.housekeeper', v_schema);
RAISE NOTICE 'Housekeeper intercept trigger removed from %.housekeeper', v_schema;
ELSE
RAISE WARNING 'housekeeper table not found — trigger removal skipped.';
END IF;
RAISE NOTICE '================================================================================';
RAISE NOTICE 'Undo complete. Partitioned tables have been renamed to *_part.';
RAISE NOTICE 'If you want to migrate your history back, you must do it manually:';
RAISE NOTICE ' INSERT INTO history SELECT * FROM history_part;';
RAISE NOTICE 'Once done, or if you do not need the data, drop the partitioned tables:';
RAISE NOTICE ' DROP TABLE history_part CASCADE;';
RAISE NOTICE 'After that, you can safely remove the partitions infrastructure:';
RAISE NOTICE ' DROP SCHEMA partitions CASCADE;';
RAISE NOTICE '================================================================================';
END $$;

View File

@@ -1,189 +0,0 @@
# Zabbix Partitioning Deployment Manual
This guide provides a step-by-step process for deploying the PostgreSQL partitioning solution for Zabbix.
**🚨 DANGER: CRITICAL WARNING 🚨**
**BEFORE YOU PROCEED, YOU ABSOLUTELY MUST TAKE A FULL BACKUP OF YOUR ZABBIX DATABASE.**
**DO NOT SKIP THIS STEP. Schema modifications are dangerous. If something goes wrong and you do not have a backup, your historical data will be lost permanently, and we take ZERO responsibility.**
---
## Step 1: Preparation & Safety
Because database migrations can take time (especially on large tables), **never** run these scripts directly in a standard SSH session that might disconnect.
1. Open a safe terminal session using `tmux` or `screen`:
```bash
tmux new -s zabbix_partitioning
# OR
screen -S zabbix_partitioning
```
2. Disable the Zabbix Housekeeper for History and Trends:
- Go to your Zabbix Web UI -> **Administration** -> **Housekeeping**.
- **Uncheck** "Enable internal housekeeping" for **History and Trends**.
- Click **Update**.
3. Stop your Zabbix Server to ensure no new data is being written during the schema migration:
```bash
sudo systemctl stop zabbix-server
```
---
## Step 2: Database Connection & Schema Selection
Connect to your PostgreSQL server as an administrator (e.g., `postgres` or the database owner).
```bash
psql -U postgres -h localhost
```
Once inside `psql`, connect to your Zabbix database (usually named `zabbix`):
```sql
\c zabbix
```
> [!IMPORTANT]
> **Custom Schemas:** By default, Zabbix installs into the `public` schema. If you installed Zabbix into a custom schema (e.g., `zabbix_schema`), you **must** set your `search_path` now before running the scripts, otherwise they will fail to find your tables:
> ```sql
> SET search_path TO zabbix_schema, public;
> ```
---
## Step 3: Execute Installation Scripts
Run the scripts in the following exact order. You can execute them directly from your bash terminal, from within the interactive `psql` console, or by pasting their contents into a GUI tool (like pgAdmin).
> [!NOTE]
> **Zabbix 8.0+ Users:** Zabbix 8.0 introduced a new `history_json` table. Before running the first script, open `00_schema_create.sql` in a text editor and uncomment the lines specifically marked for Zabbix 8.0 at the end of the history tables block.
### Option A: From bash terminal (Recommended)
If you are in your normal Linux terminal, run these commands one by one:
```bash
psql -U postgres -d zabbix -f 00_schema_create.sql
psql -U postgres -d zabbix -f 01_maintenance.sql
# MIGRATION STEP: The next script renames your existing large tables to `_old`
# and instantly creates new partitioned tables. This might take a few moments.
psql -U postgres -d zabbix -f 02_enable_partitioning.sql
psql -U postgres -d zabbix -f 03_monitoring_view.sql
```
### Option B: Inside interactive `psql`
If you already connected via `psql` (as in Step 2), use the `\i` command:
```sql
\i 00_schema_create.sql
\i 01_maintenance.sql
\i 02_enable_partitioning.sql
\i 03_monitoring_view.sql
```
### Option C: GUI Tools (pgAdmin, DBeaver, etc.)
If you use a visual database manager, simply open each file in your query editor and execute them one by one in the exact order (00, 01, 02, 03).
---
## Step 4: Schedule Automated Maintenance
Partitioning requires a daily job to create new partitions for tomorrow and drop old partitions from last month.
> [!WARNING]
> **CRITICAL EXECUTION RULE**: You MUST execute the maintenance procedure as a standalone command exactly as shown below. **DO NOT** wrap it in a transaction block (`BEGIN; ... COMMIT;`) or batch it with other SQL commands in a single string, or it will crash with an `invalid transaction termination` error.
If you are using **AWS RDS** or a managed database with `pg_cron` enabled, run this inside `psql`:
```sql
CREATE EXTENSION IF NOT EXISTS pg_cron;
SELECT cron.schedule('zabbix_partition_maintenance', '30 5,23 * * *', 'CALL partitions.run_maintenance();');
```
If you are **self-hosting** and prefer standard system `cron`, simply add this to your `crontab -e`:
```bash
# Run Zabbix partition maintenance twice daily (5:30 AM and 11:30 PM)
30 5,23 * * * psql -U postgres -d zabbix -c "CALL partitions.run_maintenance();" >> /var/log/zabbix_maintenance.log 2>&1
```
---
## Step 5: Start Zabbix Server
Now that the database is fully partitioned, you can safely start Zabbix Server again:
```bash
sudo systemctl start zabbix-server
```
*(Note: Your old history data remains safely preserved in tables like `history_old`. It is no longer visible in the UI, but it is available in the database if you ever need to manually migrate it.)*
---
## Step 6: Configure Zabbix Agent Monitoring
To ensure your partitions don't run out, you must monitor them. We use Zabbix Agent 2 for this.
1. On your database server (where Zabbix Agent 2 is installed), create the SQL query file using this simple one-liner. Copy and paste the entire block below into your terminal:
```bash
cat << 'EOF' | sudo tee /etc/zabbix/zabbix_agent2.d/partitions.get_all.sql > /dev/null
SELECT
table_name,
period,
keep_history::text AS keep_history,
configured_future_partitions,
actual_future_partitions,
total_size_bytes,
EXTRACT(EPOCH FROM (now() - last_updated)) AS age_seconds
FROM partitions.monitoring;
EOF
```
*(Note: If your Zabbix Agent is configured to look for custom queries in a different directory like `/etc/zabbix/postgresql/sql/`, modify the destination path above).*
2. Configure the PostgreSQL Plugin by editing `/etc/zabbix/zabbix_agent2.d/plugins.d/postgresql.conf`. Ensure you have defined a session (e.g., `MY_DB`) and enabled custom queries:
```ini
Plugins.PostgreSQL.CustomQueriesPath=/etc/zabbix/zabbix_agent2.d/
Plugins.PostgreSQL.CustomQueriesEnabled=true
# Example Session (replace with your actual credentials)
Plugins.PostgreSQL.Sessions.MY_DB.Uri=tcp://localhost:5432
Plugins.PostgreSQL.Sessions.MY_DB.User=zbx_monitor
Plugins.PostgreSQL.Sessions.MY_DB.Password=your_password
```
3. Restart the Zabbix Agent 2:
```bash
sudo systemctl restart zabbix-agent2
```
---
## Step 7: Import Template in Zabbix
1. Log into your Zabbix Web UI.
2. Go to **Data collection** -> **Templates** and click **Import**.
3. Upload the `template/zbx_pg_partitions_monitor_agent2.yaml` file from this repository.
4. Go to your Database Host in Zabbix, and link the newly imported template: `PostgreSQL Partitioning by Zabbix Agent 2`.
5. On the Host configuration, go to the **Macros** tab.
6. You will see a macro named `{$PG.CONNSTRING.AGENT2}` with the value `<replace_me>`.
7. Change `<replace_me>` to the name of the session you configured in Step 6 (e.g., `MY_DB`).
8. Click **Update**.
**Congratulations!** Your Zabbix database is now fully partitioned, optimized, and monitored.
---
## Step 8: Upgrades & Updates
To apply future updates or fixes to the partitioning logic, the scripts are fully idempotent and safe to re-run on a live database.
Simply connect to `psql` and re-execute the updated scripts in order:
```bash
psql -U postgres -d zabbix -f 00_schema_create.sql
psql -U postgres -d zabbix -f 01_maintenance.sql
psql -U postgres -d zabbix -f 02_enable_partitioning.sql
psql -U postgres -d zabbix -f 03_monitoring_view.sql
```
> [!WARNING]
> If you update the agent SQL query, always remember to restart the agent (`sudo systemctl restart zabbix-agent2`) so it flushes its cache!

View File

@@ -21,8 +21,6 @@ This is the declarative partitioning implementation for Zabbix `history*`, `tren
- [Implementation Details](#implementation-details)
- [`auditlog` Table](#auditlog-table)
- [Converting Existing Tables](#converting-existing-tables)
- [PostgreSQL Tuning](#postgresql-tuning)
- [Uninstall / Reverting](#uninstall--reverting)
- [Upgrades](#upgrades)
## Architecture
@@ -38,10 +36,27 @@ All procedures, information, statistics and configuration are stored in the `par
## Installation
> [!IMPORTANT]
> **For deployment instructions, please refer to [MANUAL.md](MANUAL.md).**
> `MANUAL.md` contains the step-by-step, foolproof installation guide intended for customer sharing. This `README.md` serves as an internal knowledge base for maintaining, troubleshooting, and understanding the architecture.
The installation is performed by executing the SQL procedures in the following order:
1. Initialize schema (`00_schema_create.sql`).
2. Install maintenance procedures (`01_maintenance.sql`).
3. Enable partitioning on tables (`02_enable_partitioning.sql`).
4. Install monitoring views (`03_monitoring_view.sql`).
**Command Example:**
You can deploy these scripts manually against your Zabbix database using `psql`. Navigate to the `procedures/` directory and run:
```bash
# Connect as the zabbix database user
export PGPASSWORD="your_zabbix_password"
DB_HOST="localhost" # Or your DB endpoint
DB_NAME="zabbix"
DB_USER="zbxpart_admin"
for script in 00_schema_create.sql 01_maintenance.sql 02_enable_partitioning.sql 03_monitoring_view.sql; do
echo "Applying $script..."
psql -h $DB_HOST -U $DB_USER -d $DB_NAME -f "$script"
done
```
## Configuration
@@ -198,24 +213,6 @@ System state can be monitored via the `partitions.monitoring` view. It includes
SELECT * FROM partitions.monitoring;
```
### Zabbix Agent Integration
To monitor the state of the partitions directly from Zabbix, you need to provide the Zabbix Agent with the SQL query used to fetch this data. You can automatically generate the required `partitions.get_all.sql` file on your agent using this one-liner:
```bash
cat << 'EOF' | sudo tee /etc/zabbix/zabbix_agent2.d/partitions.get_all.sql > /dev/null
SELECT
table_name,
period,
keep_history::text AS keep_history,
configured_future_partitions,
actual_future_partitions,
total_size_bytes,
EXTRACT(EPOCH FROM (now() - last_updated)) AS age_seconds
FROM partitions.monitoring;
EOF
```
*(Make sure to adjust the destination path according to your Zabbix Agent template directory)*
### Versioning
To check the installed version of the partitioning solution:
```sql
@@ -231,8 +228,8 @@ GRANT USAGE ON SCHEMA partitions TO zbxpart_monitor;
GRANT SELECT ON partitions.monitoring TO zbxpart_monitor;
```
> [!NOTE]
> The `03_monitoring_view.sql` script is designed to automatically execute `GRANT SELECT ON partitions.monitoring TO PUBLIC;` so that read access is never lost during upgrades when the view is recreated.
> [!WARNING]
> Because `03_monitoring_view.sql` uses a `DROP VIEW` command to apply updates, re-running the script will destroy all previously assigned `GRANT` permissions. If you ever update the view script, you **must** manually re-run the `GRANT SELECT` command above to restore access for the `zbxpart_monitor` user!
## Implementation Details
@@ -246,77 +243,9 @@ The enablement script guarantees practically zero downtime by automatically rena
* New data flows into the new partitioned tables immediately.
* Old data remains accessible in `table_name_old` for manual lookup or migration if required.
> [!WARNING]
> **Non-Atomic Enablement**: The `02_enable_partitioning.sql` script is executed within a `DO $$` block. However, because it calls the maintenance procedure (which issues `COMMIT` statements to manage locks), the enablement process is **not atomic**. If the script crashes halfway through (e.g., due to a missing table), it will not roll back the previously converted tables. The database will be in a partially partitioned state. Fortunately, the script is fully restartable. Simply fix the underlying issue and run the script again; it will skip already partitioned tables and resume where it left off.
### Housekeeper Interceptor
Even when Zabbix Housekeeping is disabled in the UI for History and Trends, the Zabbix Server daemon may still generate and insert tasks into the `housekeeper` table (e.g., when an item or trigger is deleted, it schedules the deletion of its historical data). Without intervention, this results in the `housekeeper` table bloating massively over time, leading to slow sequential scans and `autovacuum` overhead.
To prevent this, this extension installs a `BEFORE INSERT` trigger on the `housekeeper` table.
* When Zabbix attempts to insert a housekeeper task, the trigger intercepts it and checks if the target table is managed in `partitions.config`.
* If the table is partitioned (like `history`), the trigger **silently discards the insert** (`RETURNS NULL`), preventing disk I/O and table bloat entirely.
* If the table is not partitioned (like `events` or `sessions`), the task is allowed to be recorded and is cleaned up naturally by Zabbix.
## Troubleshooting & Known Exceptions
### `invalid transaction termination`
When manually executing or scheduling the maintenance procedure (`CALL partitions.run_maintenance();`), you or the customer might encounter the following fatal error:
`ERROR: invalid transaction termination`
**Why this happens:**
Inside the `partitions` PL/pgSQL procedures, we execute `COMMIT;` statements within loops to eagerly release locks and prevent transaction bloat when dropping dozens of partitions. PostgreSQL fundamentally prohibits executing `COMMIT` from within a procedure if that procedure was called from inside an explicit transaction block or a batched command string.
**How to fix/avoid it:**
Ensure the procedure is ALWAYS executed as a top-level, standalone command.
* **Bad**: `BEGIN; CALL partitions.run_maintenance(); COMMIT;`
* **Bad**: `psql -c "UPDATE partitions.config SET keep_history = '1 day'; CALL partitions.run_maintenance();"` (psql batches these into one transaction)
* **Good**: `psql -c "CALL partitions.run_maintenance();"`
## PostgreSQL Tuning
Before or immediately after enabling partitioning, you should tune your `postgresql.conf`. The standard configuration is not optimized for partitioned tables and might cause performance degradation or out-of-memory errors.
| Parameter | Recommended | Description |
|-----------|-------------|-------------|
| `max_locks_per_transaction`| `512` (or higher) | **Requires DB Restart.** Default is `64`, which is far too low. PostgreSQL lock tables per partition. With many partitioned tables (e.g., history x 30 days), operations like `pg_dump`, `VACUUM`, or queries crossing multiple boundaries will fail with *“out of shared memory”*. |
| `jit` | `off` | **Highly Recommended.** JIT adds overhead to query planning. With many partitions, JIT can drastically increase CPU usage as PostgreSQL attempts to optimize simple queries across dozens of partitions. |
**Default parameters to verify:**
The following are usually set correctly by default, but you should verify them just in case:
* `enable_partition_pruning = on` : **Critical.** Ensures PostgreSQL only queries the necessary partitions instead of scanning everything.
* `enable_partitionwise_join = off` : Zabbix does not do massive joins on history tables; enabling this only wastes planner CPU time.
* `enable_partitionwise_aggregate = off` : Zabbix doesn't perform complex DB-side `GROUP BY` aggregations on history. Leave it disabled.
## Uninstall / Reverting
If you wish to stop using partitioning and revert back to standard, unpartitioned tables without data loss, carefully follow these steps.
> [!CAUTION]
> Reverting partitioning replaces your partitioned tables with standard empty tables. If you need to retain data from the partitioned period, you must manually migrate it before dropping the partition sets. **Always stop Zabbix Server before proceeding.**
1. **Stop Zabbix Server** to prevent new data from being inserted during the transition.
2. **Execute Undo Script:** Run the `04_undo_partitioning.sql` script to recreate non-partitioned tables matching your original Zabbix schema. This script will rename your current partitioned tables to `*_part` (`history_part`, `trends_part`, etc.) and automatically create native, clean tables (`history`, `trends`) in their place.
```bash
psql -h $DB_HOST -U zbxpart_admin -d zabbix -f 04_undo_partitioning.sql
```
3. **Data Migration (Optional):** If you want to keep the metrics collected during the partitioned period, you must manually insert them into the newly created regular tables. This step can take hours depending on table sizes.
```sql
INSERT INTO history SELECT * FROM history_part;
INSERT INTO trends SELECT * FROM trends_part;
-- Repeat for all tables you wish to restore
```
4. **Cleanup:** Once you have migrated the data you need (or if you don't need it at all), you can drop the heavy partitioned tables and remove the partitioning extensions completely.
```sql
DROP TABLE history_part CASCADE;
DROP TABLE history_uint_part CASCADE;
-- Repeat for all *_part tables ...
-- To drop the automatic maintenance infrastructure:
DROP SCHEMA partitions CASCADE;
```
5. **Start Zabbix Server & Re-enable Housekeeper:** Once the tables are replaced, you can start the server. *Don't forget to re-enable Housekeeping for History and Trends in the Zabbix UI!*
## Upgrades
When upgrading Zabbix:
1. **Backup**: Ensure a full database backup exists.
2. **Compatibility**: Zabbix upgrade scripts may attempt to `ALTER` tables. PostgreSQL supports `ALTER TABLE` on partitioned tables for adding columns, which propagates to partitions.
3. **Failure Scenarios**: If an upgrade script fails due to partitioning, the table may need to be temporarily reverted or the partition structure manually adjusted.

View File

@@ -2,8 +2,7 @@ SELECT
table_name,
period,
keep_history::text AS keep_history,
configured_future_partitions,
actual_future_partitions,
future_partitions,
total_size_bytes,
EXTRACT(EPOCH FROM (now() - last_updated)) AS age_seconds
FROM partitions.monitoring;

View File

@@ -57,7 +57,7 @@ zabbix_export:
preprocessing:
- type: JSONPATH
parameters:
- '$.[?(@.table_name == "{#TABLE_NAME}")].actual_future_partitions.first()'
- '$.[?(@.table_name == "{#TABLE_NAME}")].future_partitions.first()'
master_item:
key: 'pgsql.custom.query["{$PG.CONNSTRING.AGENT2}",,,"{$PG.DBNAME}","partitions.get_all"]'
tags:
@@ -131,7 +131,7 @@ zabbix_export:
value: '2'
description: 'The minimum number of partitions that must exist in the future'
- macro: '{$PG.CONNSTRING.AGENT2}'
value: '<replace_me>'
value: AWS_RDS
description: 'Session name or URI of the PostgreSQL instance'
- macro: '{$PG.DBNAME}'
value: zabbix

View File

@@ -36,7 +36,6 @@ The solution is divided into a series of SQL scripts that must be executed seque
* `drop_old_partitions()`: Iterates over existing child partitions (using `pg_inherits`) and calculates their age based on their suffix. Drops those older than the defined `keep_history` policy.
* `maintain_table()`: The orchestrator for a single table. It calculates the necessary UTC timestamps, calls `create_partition()` to build the future buffer, calls `create_partition()` recursively backward to cover the retention period, and finally calls `drop_old_partitions()`.
* `run_maintenance()`: The global loop that iterates through `partitions.config` and triggers `maintain_table()` for every configured Zabbix table.
* `housekeeper_insert_trigger()`: A trigger function that intercepts `INSERT` queries into the Zabbix `housekeeper` table, silently discarding tasks for tables managed by the partitioning system (based on `partitions.config`).
### 3. `02_enable_partitioning.sql`
* **Purpose:** The migration script that actually executes the partition conversion on the live database.
@@ -45,7 +44,6 @@ The solution is divided into a series of SQL scripts that must be executed seque
* It takes the original Zabbix table (e.g., `history`) and renames it to `history_old` (`ALTER TABLE ... RENAME TO ...`).
* It immediately creates a new partitioned table with the original name, inheriting the exact structure of the old table (`CREATE TABLE ... (LIKE ... INCLUDING ALL) PARTITION BY RANGE (clock)`).
* It triggers the first maintenance run so new incoming data has immediate partitions to land in.
* It attaches the `housekeeper_filter` trigger to the `housekeeper` table to prevent Zabbix from inserting garbage tasks for the partitioned tables.
### 4. `03_monitoring_view.sql`
* **Purpose:** Provides an easy-to-read observability layer.

View File

@@ -1 +0,0 @@
init_scripts/

View File

@@ -86,8 +86,6 @@ CREATE TABLE hosts (
vendor_version varchar(32) DEFAULT '' NOT NULL,
proxy_groupid bigint NULL,
monitored_by integer DEFAULT '0' NOT NULL,
wizard_ready integer DEFAULT '0' NOT NULL,
readme text DEFAULT '' NOT NULL,
PRIMARY KEY (hostid)
);
CREATE INDEX hosts_1 ON hosts (host);
@@ -362,20 +360,6 @@ CREATE TABLE media_type (
PRIMARY KEY (mediatypeid)
);
CREATE UNIQUE INDEX media_type_1 ON media_type (name);
CREATE TABLE media_type_oauth (
mediatypeid bigint NOT NULL,
redirection_url varchar(2048) DEFAULT '' NOT NULL,
client_id varchar(255) DEFAULT '' NOT NULL,
client_secret varchar(255) DEFAULT '' NOT NULL,
authorization_url varchar(2048) DEFAULT '' NOT NULL,
tokens_status integer DEFAULT '0' NOT NULL,
access_token text DEFAULT '' NOT NULL,
access_token_updated integer DEFAULT '0' NOT NULL,
access_expires_in integer DEFAULT '0' NOT NULL,
refresh_token text DEFAULT '' NOT NULL,
token_url varchar(2048) DEFAULT '' NOT NULL,
PRIMARY KEY (mediatypeid)
);
CREATE TABLE media_type_param (
mediatype_paramid bigint NOT NULL,
mediatypeid bigint NOT NULL,
@@ -586,6 +570,132 @@ CREATE TABLE conditions (
PRIMARY KEY (conditionid)
);
CREATE INDEX conditions_1 ON conditions (actionid);
CREATE TABLE config (
configid bigint NOT NULL,
work_period varchar(255) DEFAULT '1-5,09:00-18:00' NOT NULL,
alert_usrgrpid bigint NULL,
default_theme varchar(128) DEFAULT 'blue-theme' NOT NULL,
authentication_type integer DEFAULT '0' NOT NULL,
discovery_groupid bigint NULL,
max_in_table integer DEFAULT '50' NOT NULL,
search_limit integer DEFAULT '1000' NOT NULL,
severity_color_0 varchar(6) DEFAULT '97AAB3' NOT NULL,
severity_color_1 varchar(6) DEFAULT '7499FF' NOT NULL,
severity_color_2 varchar(6) DEFAULT 'FFC859' NOT NULL,
severity_color_3 varchar(6) DEFAULT 'FFA059' NOT NULL,
severity_color_4 varchar(6) DEFAULT 'E97659' NOT NULL,
severity_color_5 varchar(6) DEFAULT 'E45959' NOT NULL,
severity_name_0 varchar(32) DEFAULT 'Not classified' NOT NULL,
severity_name_1 varchar(32) DEFAULT 'Information' NOT NULL,
severity_name_2 varchar(32) DEFAULT 'Warning' NOT NULL,
severity_name_3 varchar(32) DEFAULT 'Average' NOT NULL,
severity_name_4 varchar(32) DEFAULT 'High' NOT NULL,
severity_name_5 varchar(32) DEFAULT 'Disaster' NOT NULL,
ok_period varchar(32) DEFAULT '5m' NOT NULL,
blink_period varchar(32) DEFAULT '2m' NOT NULL,
problem_unack_color varchar(6) DEFAULT 'CC0000' NOT NULL,
problem_ack_color varchar(6) DEFAULT 'CC0000' NOT NULL,
ok_unack_color varchar(6) DEFAULT '009900' NOT NULL,
ok_ack_color varchar(6) DEFAULT '009900' NOT NULL,
problem_unack_style integer DEFAULT '1' NOT NULL,
problem_ack_style integer DEFAULT '1' NOT NULL,
ok_unack_style integer DEFAULT '1' NOT NULL,
ok_ack_style integer DEFAULT '1' NOT NULL,
snmptrap_logging integer DEFAULT '1' NOT NULL,
server_check_interval integer DEFAULT '10' NOT NULL,
hk_events_mode integer DEFAULT '1' NOT NULL,
hk_events_trigger varchar(32) DEFAULT '365d' NOT NULL,
hk_events_internal varchar(32) DEFAULT '1d' NOT NULL,
hk_events_discovery varchar(32) DEFAULT '1d' NOT NULL,
hk_events_autoreg varchar(32) DEFAULT '1d' NOT NULL,
hk_services_mode integer DEFAULT '1' NOT NULL,
hk_services varchar(32) DEFAULT '365d' NOT NULL,
hk_audit_mode integer DEFAULT '1' NOT NULL,
hk_audit varchar(32) DEFAULT '31d' NOT NULL,
hk_sessions_mode integer DEFAULT '1' NOT NULL,
hk_sessions varchar(32) DEFAULT '365d' NOT NULL,
hk_history_mode integer DEFAULT '1' NOT NULL,
hk_history_global integer DEFAULT '0' NOT NULL,
hk_history varchar(32) DEFAULT '31d' NOT NULL,
hk_trends_mode integer DEFAULT '1' NOT NULL,
hk_trends_global integer DEFAULT '0' NOT NULL,
hk_trends varchar(32) DEFAULT '365d' NOT NULL,
default_inventory_mode integer DEFAULT '-1' NOT NULL,
custom_color integer DEFAULT '0' NOT NULL,
http_auth_enabled integer DEFAULT '0' NOT NULL,
http_login_form integer DEFAULT '0' NOT NULL,
http_strip_domains varchar(2048) DEFAULT '' NOT NULL,
http_case_sensitive integer DEFAULT '1' NOT NULL,
ldap_auth_enabled integer DEFAULT '0' NOT NULL,
ldap_case_sensitive integer DEFAULT '1' NOT NULL,
db_extension varchar(32) DEFAULT '' NOT NULL,
autoreg_tls_accept integer DEFAULT '1' NOT NULL,
compression_status integer DEFAULT '0' NOT NULL,
compress_older varchar(32) DEFAULT '7d' NOT NULL,
instanceid varchar(32) DEFAULT '' NOT NULL,
saml_auth_enabled integer DEFAULT '0' NOT NULL,
saml_case_sensitive integer DEFAULT '0' NOT NULL,
default_lang varchar(5) DEFAULT 'en_US' NOT NULL,
default_timezone varchar(50) DEFAULT 'system' NOT NULL,
login_attempts integer DEFAULT '5' NOT NULL,
login_block varchar(32) DEFAULT '30s' NOT NULL,
show_technical_errors integer DEFAULT '0' NOT NULL,
validate_uri_schemes integer DEFAULT '1' NOT NULL,
uri_valid_schemes varchar(255) DEFAULT 'http,https,ftp,file,mailto,tel,ssh' NOT NULL,
x_frame_options varchar(255) DEFAULT 'SAMEORIGIN' NOT NULL,
iframe_sandboxing_enabled integer DEFAULT '1' NOT NULL,
iframe_sandboxing_exceptions varchar(255) DEFAULT '' NOT NULL,
max_overview_table_size integer DEFAULT '50' NOT NULL,
history_period varchar(32) DEFAULT '24h' NOT NULL,
period_default varchar(32) DEFAULT '1h' NOT NULL,
max_period varchar(32) DEFAULT '2y' NOT NULL,
socket_timeout varchar(32) DEFAULT '3s' NOT NULL,
connect_timeout varchar(32) DEFAULT '3s' NOT NULL,
media_type_test_timeout varchar(32) DEFAULT '65s' NOT NULL,
script_timeout varchar(32) DEFAULT '60s' NOT NULL,
item_test_timeout varchar(32) DEFAULT '60s' NOT NULL,
session_key varchar(32) DEFAULT '' NOT NULL,
url varchar(2048) DEFAULT '' NOT NULL,
report_test_timeout varchar(32) DEFAULT '60s' NOT NULL,
dbversion_status text DEFAULT '' NOT NULL,
hk_events_service varchar(32) DEFAULT '1d' NOT NULL,
passwd_min_length integer DEFAULT '8' NOT NULL,
passwd_check_rules integer DEFAULT '8' NOT NULL,
auditlog_enabled integer DEFAULT '1' NOT NULL,
ha_failover_delay varchar(32) DEFAULT '1m' NOT NULL,
geomaps_tile_provider varchar(255) DEFAULT '' NOT NULL,
geomaps_tile_url varchar(2048) DEFAULT '' NOT NULL,
geomaps_max_zoom integer DEFAULT '0' NOT NULL,
geomaps_attribution varchar(1024) DEFAULT '' NOT NULL,
vault_provider integer DEFAULT '0' NOT NULL,
ldap_userdirectoryid bigint DEFAULT NULL NULL,
server_status text DEFAULT '' NOT NULL,
jit_provision_interval varchar(32) DEFAULT '1h' NOT NULL,
saml_jit_status integer DEFAULT '0' NOT NULL,
ldap_jit_status integer DEFAULT '0' NOT NULL,
disabled_usrgrpid bigint DEFAULT NULL NULL,
timeout_zabbix_agent varchar(255) DEFAULT '3s' NOT NULL,
timeout_simple_check varchar(255) DEFAULT '3s' NOT NULL,
timeout_snmp_agent varchar(255) DEFAULT '3s' NOT NULL,
timeout_external_check varchar(255) DEFAULT '3s' NOT NULL,
timeout_db_monitor varchar(255) DEFAULT '3s' NOT NULL,
timeout_http_agent varchar(255) DEFAULT '3s' NOT NULL,
timeout_ssh_agent varchar(255) DEFAULT '3s' NOT NULL,
timeout_telnet_agent varchar(255) DEFAULT '3s' NOT NULL,
timeout_script varchar(255) DEFAULT '3s' NOT NULL,
auditlog_mode integer DEFAULT '1' NOT NULL,
mfa_status integer DEFAULT '0' NOT NULL,
mfaid bigint NULL,
software_update_checkid varchar(32) DEFAULT '' NOT NULL,
software_update_check_data text DEFAULT '' NOT NULL,
timeout_browser varchar(255) DEFAULT '60s' NOT NULL,
PRIMARY KEY (configid)
);
CREATE INDEX config_1 ON config (alert_usrgrpid);
CREATE INDEX config_2 ON config (discovery_groupid);
CREATE INDEX config_3 ON config (ldap_userdirectoryid);
CREATE INDEX config_4 ON config (disabled_usrgrpid);
CREATE INDEX config_5 ON config (mfaid);
CREATE TABLE triggers (
triggerid bigint NOT NULL,
expression varchar(2048) DEFAULT '' NOT NULL,
@@ -715,18 +825,6 @@ CREATE TABLE hostmacro (
PRIMARY KEY (hostmacroid)
);
CREATE UNIQUE INDEX hostmacro_1 ON hostmacro (hostid,macro);
CREATE TABLE hostmacro_config (
hostmacroid bigint NOT NULL,
type integer DEFAULT '0' NOT NULL,
priority integer DEFAULT '0' NOT NULL,
section_name varchar(255) DEFAULT '' NOT NULL,
label varchar(255) DEFAULT '' NOT NULL,
description text DEFAULT '' NOT NULL,
required integer DEFAULT '0' NOT NULL,
regex varchar(255) DEFAULT '' NOT NULL,
options text DEFAULT '' NOT NULL,
PRIMARY KEY (hostmacroid)
);
CREATE TABLE hosts_groups (
hostgroupid bigint NOT NULL,
hostid bigint NOT NULL,
@@ -858,9 +956,6 @@ CREATE TABLE sysmaps (
userid bigint NOT NULL,
private integer DEFAULT '1' NOT NULL,
show_suppressed integer DEFAULT '0' NOT NULL,
background_scale integer DEFAULT '1' NOT NULL,
show_element_label integer DEFAULT '1' NOT NULL,
show_link_label integer DEFAULT '1' NOT NULL,
PRIMARY KEY (sysmapid)
);
CREATE UNIQUE INDEX sysmaps_1 ON sysmaps (name);
@@ -887,8 +982,6 @@ CREATE TABLE sysmaps_elements (
viewtype integer DEFAULT '0' NOT NULL,
use_iconmap integer DEFAULT '1' NOT NULL,
evaltype integer DEFAULT '0' NOT NULL,
show_label integer DEFAULT '-1' NOT NULL,
zindex integer DEFAULT '0' NOT NULL,
PRIMARY KEY (selementid)
);
CREATE INDEX sysmaps_elements_1 ON sysmaps_elements (sysmapid);
@@ -904,15 +997,11 @@ CREATE TABLE sysmaps_links (
drawtype integer DEFAULT '0' NOT NULL,
color varchar(6) DEFAULT '000000' NOT NULL,
label varchar(2048) DEFAULT '' NOT NULL,
show_label integer DEFAULT '-1' NOT NULL,
indicator_type integer DEFAULT '0' NOT NULL,
itemid bigint NULL,
PRIMARY KEY (linkid)
);
CREATE INDEX sysmaps_links_1 ON sysmaps_links (sysmapid);
CREATE INDEX sysmaps_links_2 ON sysmaps_links (selementid1);
CREATE INDEX sysmaps_links_3 ON sysmaps_links (selementid2);
CREATE INDEX sysmaps_links_4 ON sysmaps_links (itemid);
CREATE TABLE sysmaps_link_triggers (
linktriggerid bigint NOT NULL,
linkid bigint NOT NULL,
@@ -923,18 +1012,6 @@ CREATE TABLE sysmaps_link_triggers (
);
CREATE UNIQUE INDEX sysmaps_link_triggers_1 ON sysmaps_link_triggers (linkid,triggerid);
CREATE INDEX sysmaps_link_triggers_2 ON sysmaps_link_triggers (triggerid);
CREATE TABLE sysmap_link_threshold (
linkthresholdid bigint NOT NULL,
linkid bigint NOT NULL,
drawtype integer DEFAULT '0' NOT NULL,
color varchar(6) DEFAULT '000000' NOT NULL,
type integer DEFAULT '0' NOT NULL,
threshold varchar(255) DEFAULT '' NOT NULL,
pattern varchar(255) DEFAULT '' NOT NULL,
sortorder integer DEFAULT '0' NOT NULL,
PRIMARY KEY (linkthresholdid)
);
CREATE INDEX sysmap_link_threshold_1 ON sysmap_link_threshold (linkid);
CREATE TABLE sysmap_element_url (
sysmapelementurlid bigint NOT NULL,
selementid bigint NOT NULL,
@@ -1120,6 +1197,7 @@ CREATE TABLE proxy_history (
write_clock integer DEFAULT '0' NOT NULL,
PRIMARY KEY (id)
);
CREATE INDEX proxy_history_1 ON proxy_history (clock);
CREATE INDEX proxy_history_2 ON proxy_history (write_clock);
CREATE TABLE proxy_dhistory (
id bigint NOT NULL,
@@ -1396,23 +1474,21 @@ CREATE UNIQUE INDEX images_1 ON images (name);
CREATE TABLE item_discovery (
itemdiscoveryid bigint NOT NULL,
itemid bigint NOT NULL,
parent_itemid bigint NULL,
parent_itemid bigint NOT NULL,
key_ varchar(2048) DEFAULT '' NOT NULL,
lastcheck integer DEFAULT '0' NOT NULL,
ts_delete integer DEFAULT '0' NOT NULL,
status integer DEFAULT '0' NOT NULL,
disable_source integer DEFAULT '0' NOT NULL,
ts_disable integer DEFAULT '0' NOT NULL,
lldruleid bigint NULL,
PRIMARY KEY (itemdiscoveryid)
);
CREATE UNIQUE INDEX item_discovery_1 ON item_discovery (itemid,parent_itemid);
CREATE INDEX item_discovery_2 ON item_discovery (parent_itemid);
CREATE INDEX item_discovery_3 ON item_discovery (lldruleid);
CREATE TABLE host_discovery (
hostid bigint NOT NULL,
parent_hostid bigint NULL,
lldruleid bigint NULL,
parent_itemid bigint NULL,
host varchar(128) DEFAULT '' NOT NULL,
lastcheck integer DEFAULT '0' NOT NULL,
ts_delete integer DEFAULT '0' NOT NULL,
@@ -1422,7 +1498,7 @@ CREATE TABLE host_discovery (
PRIMARY KEY (hostid)
);
CREATE INDEX host_discovery_1 ON host_discovery (parent_hostid);
CREATE INDEX host_discovery_2 ON host_discovery (lldruleid);
CREATE INDEX host_discovery_2 ON host_discovery (parent_itemid);
CREATE TABLE interface_discovery (
interfaceid bigint NOT NULL,
parent_interfaceid bigint NOT NULL,
@@ -2407,36 +2483,13 @@ CREATE TABLE mfa_totp_secret (
);
CREATE INDEX mfa_totp_secret_1 ON mfa_totp_secret (mfaid);
CREATE INDEX mfa_totp_secret_2 ON mfa_totp_secret (userid);
CREATE TABLE settings (
name varchar(255) NOT NULL,
type integer NOT NULL,
value_str text DEFAULT '' NOT NULL,
value_int integer DEFAULT '0' NOT NULL,
value_usrgrpid bigint NULL,
value_hostgroupid bigint NULL,
value_userdirectoryid bigint NULL,
value_mfaid bigint NULL,
PRIMARY KEY (name)
);
CREATE INDEX settings_2 ON settings (value_usrgrpid);
CREATE INDEX settings_3 ON settings (value_hostgroupid);
CREATE INDEX settings_4 ON settings (value_userdirectoryid);
CREATE INDEX settings_5 ON settings (value_mfaid);
CREATE TABLE lld_macro_export (
lld_macro_exportid bigint NOT NULL,
itemid bigint NOT NULL,
lld_macro varchar(255) DEFAULT '' NOT NULL,
value text DEFAULT '' NOT NULL,
PRIMARY KEY (lld_macro_exportid)
);
CREATE INDEX lld_macro_export_1 ON lld_macro_export (itemid);
CREATE TABLE dbversion (
dbversionid bigint NOT NULL,
mandatory integer DEFAULT '0' NOT NULL,
optional integer DEFAULT '0' NOT NULL,
PRIMARY KEY (dbversionid)
);
INSERT INTO dbversion VALUES ('1','7040000','7040009');
INSERT INTO dbversion VALUES ('1','7000000','7000029');
create or replace function changelog_hosts_insert() returns trigger as $$
begin
insert into changelog (object,objectid,operation,clock)
@@ -3111,7 +3164,6 @@ ALTER TABLE ONLY httpstepitem ADD CONSTRAINT c_httpstepitem_1 FOREIGN KEY (https
ALTER TABLE ONLY httpstepitem ADD CONSTRAINT c_httpstepitem_2 FOREIGN KEY (itemid) REFERENCES items (itemid);
ALTER TABLE ONLY httptestitem ADD CONSTRAINT c_httptestitem_1 FOREIGN KEY (httptestid) REFERENCES httptest (httptestid);
ALTER TABLE ONLY httptestitem ADD CONSTRAINT c_httptestitem_2 FOREIGN KEY (itemid) REFERENCES items (itemid);
ALTER TABLE ONLY media_type_oauth ADD CONSTRAINT c_media_type_oauth_1 FOREIGN KEY (mediatypeid) REFERENCES media_type (mediatypeid) ON DELETE CASCADE;
ALTER TABLE ONLY media_type_param ADD CONSTRAINT c_media_type_param_1 FOREIGN KEY (mediatypeid) REFERENCES media_type (mediatypeid) ON DELETE CASCADE;
ALTER TABLE ONLY media_type_message ADD CONSTRAINT c_media_type_message_1 FOREIGN KEY (mediatypeid) REFERENCES media_type (mediatypeid) ON DELETE CASCADE;
ALTER TABLE ONLY usrgrp ADD CONSTRAINT c_usrgrp_2 FOREIGN KEY (userdirectoryid) REFERENCES userdirectory (userdirectoryid);
@@ -3145,6 +3197,11 @@ ALTER TABLE ONLY optemplate ADD CONSTRAINT c_optemplate_1 FOREIGN KEY (operation
ALTER TABLE ONLY optemplate ADD CONSTRAINT c_optemplate_2 FOREIGN KEY (templateid) REFERENCES hosts (hostid);
ALTER TABLE ONLY opconditions ADD CONSTRAINT c_opconditions_1 FOREIGN KEY (operationid) REFERENCES operations (operationid) ON DELETE CASCADE;
ALTER TABLE ONLY conditions ADD CONSTRAINT c_conditions_1 FOREIGN KEY (actionid) REFERENCES actions (actionid) ON DELETE CASCADE;
ALTER TABLE ONLY config ADD CONSTRAINT c_config_1 FOREIGN KEY (alert_usrgrpid) REFERENCES usrgrp (usrgrpid);
ALTER TABLE ONLY config ADD CONSTRAINT c_config_2 FOREIGN KEY (discovery_groupid) REFERENCES hstgrp (groupid);
ALTER TABLE ONLY config ADD CONSTRAINT c_config_3 FOREIGN KEY (ldap_userdirectoryid) REFERENCES userdirectory (userdirectoryid);
ALTER TABLE ONLY config ADD CONSTRAINT c_config_4 FOREIGN KEY (disabled_usrgrpid) REFERENCES usrgrp (usrgrpid);
ALTER TABLE ONLY config ADD CONSTRAINT c_config_5 FOREIGN KEY (mfaid) REFERENCES mfa (mfaid);
ALTER TABLE ONLY triggers ADD CONSTRAINT c_triggers_1 FOREIGN KEY (templateid) REFERENCES triggers (triggerid);
ALTER TABLE ONLY trigger_depends ADD CONSTRAINT c_trigger_depends_1 FOREIGN KEY (triggerid_down) REFERENCES triggers (triggerid) ON DELETE CASCADE;
ALTER TABLE ONLY trigger_depends ADD CONSTRAINT c_trigger_depends_2 FOREIGN KEY (triggerid_up) REFERENCES triggers (triggerid) ON DELETE CASCADE;
@@ -3156,7 +3213,6 @@ ALTER TABLE ONLY graphs ADD CONSTRAINT c_graphs_3 FOREIGN KEY (ymax_itemid) REFE
ALTER TABLE ONLY graphs_items ADD CONSTRAINT c_graphs_items_1 FOREIGN KEY (graphid) REFERENCES graphs (graphid) ON DELETE CASCADE;
ALTER TABLE ONLY graphs_items ADD CONSTRAINT c_graphs_items_2 FOREIGN KEY (itemid) REFERENCES items (itemid) ON DELETE CASCADE;
ALTER TABLE ONLY hostmacro ADD CONSTRAINT c_hostmacro_1 FOREIGN KEY (hostid) REFERENCES hosts (hostid) ON DELETE CASCADE;
ALTER TABLE ONLY hostmacro_config ADD CONSTRAINT c_hostmacro_config_1 FOREIGN KEY (hostmacroid) REFERENCES hostmacro (hostmacroid) ON DELETE CASCADE;
ALTER TABLE ONLY hosts_groups ADD CONSTRAINT c_hosts_groups_1 FOREIGN KEY (hostid) REFERENCES hosts (hostid) ON DELETE CASCADE;
ALTER TABLE ONLY hosts_groups ADD CONSTRAINT c_hosts_groups_2 FOREIGN KEY (groupid) REFERENCES hstgrp (groupid) ON DELETE CASCADE;
ALTER TABLE ONLY hosts_templates ADD CONSTRAINT c_hosts_templates_1 FOREIGN KEY (hostid) REFERENCES hosts (hostid) ON DELETE CASCADE;
@@ -3185,10 +3241,8 @@ ALTER TABLE ONLY sysmaps_elements ADD CONSTRAINT c_sysmaps_elements_5 FOREIGN KE
ALTER TABLE ONLY sysmaps_links ADD CONSTRAINT c_sysmaps_links_1 FOREIGN KEY (sysmapid) REFERENCES sysmaps (sysmapid) ON DELETE CASCADE;
ALTER TABLE ONLY sysmaps_links ADD CONSTRAINT c_sysmaps_links_2 FOREIGN KEY (selementid1) REFERENCES sysmaps_elements (selementid) ON DELETE CASCADE;
ALTER TABLE ONLY sysmaps_links ADD CONSTRAINT c_sysmaps_links_3 FOREIGN KEY (selementid2) REFERENCES sysmaps_elements (selementid) ON DELETE CASCADE;
ALTER TABLE ONLY sysmaps_links ADD CONSTRAINT c_sysmaps_links_4 FOREIGN KEY (itemid) REFERENCES items (itemid);
ALTER TABLE ONLY sysmaps_link_triggers ADD CONSTRAINT c_sysmaps_link_triggers_1 FOREIGN KEY (linkid) REFERENCES sysmaps_links (linkid) ON DELETE CASCADE;
ALTER TABLE ONLY sysmaps_link_triggers ADD CONSTRAINT c_sysmaps_link_triggers_2 FOREIGN KEY (triggerid) REFERENCES triggers (triggerid) ON DELETE CASCADE;
ALTER TABLE ONLY sysmap_link_threshold ADD CONSTRAINT c_sysmap_link_threshold_1 FOREIGN KEY (linkid) REFERENCES sysmaps_links (linkid) ON DELETE CASCADE;
ALTER TABLE ONLY sysmap_element_url ADD CONSTRAINT c_sysmap_element_url_1 FOREIGN KEY (selementid) REFERENCES sysmaps_elements (selementid) ON DELETE CASCADE;
ALTER TABLE ONLY sysmap_url ADD CONSTRAINT c_sysmap_url_1 FOREIGN KEY (sysmapid) REFERENCES sysmaps (sysmapid) ON DELETE CASCADE;
ALTER TABLE ONLY sysmap_user ADD CONSTRAINT c_sysmap_user_1 FOREIGN KEY (sysmapid) REFERENCES sysmaps (sysmapid) ON DELETE CASCADE;
@@ -3222,10 +3276,9 @@ ALTER TABLE ONLY graph_discovery ADD CONSTRAINT c_graph_discovery_2 FOREIGN KEY
ALTER TABLE ONLY host_inventory ADD CONSTRAINT c_host_inventory_1 FOREIGN KEY (hostid) REFERENCES hosts (hostid) ON DELETE CASCADE;
ALTER TABLE ONLY item_discovery ADD CONSTRAINT c_item_discovery_1 FOREIGN KEY (itemid) REFERENCES items (itemid) ON DELETE CASCADE;
ALTER TABLE ONLY item_discovery ADD CONSTRAINT c_item_discovery_2 FOREIGN KEY (parent_itemid) REFERENCES items (itemid) ON DELETE CASCADE;
ALTER TABLE ONLY item_discovery ADD CONSTRAINT c_item_discovery_3 FOREIGN KEY (lldruleid) REFERENCES items (itemid);
ALTER TABLE ONLY host_discovery ADD CONSTRAINT c_host_discovery_1 FOREIGN KEY (hostid) REFERENCES hosts (hostid) ON DELETE CASCADE;
ALTER TABLE ONLY host_discovery ADD CONSTRAINT c_host_discovery_2 FOREIGN KEY (parent_hostid) REFERENCES hosts (hostid);
ALTER TABLE ONLY host_discovery ADD CONSTRAINT c_host_discovery_3 FOREIGN KEY (lldruleid) REFERENCES items (itemid);
ALTER TABLE ONLY host_discovery ADD CONSTRAINT c_host_discovery_3 FOREIGN KEY (parent_itemid) REFERENCES items (itemid);
ALTER TABLE ONLY interface_discovery ADD CONSTRAINT c_interface_discovery_1 FOREIGN KEY (interfaceid) REFERENCES interface (interfaceid) ON DELETE CASCADE;
ALTER TABLE ONLY interface_discovery ADD CONSTRAINT c_interface_discovery_2 FOREIGN KEY (parent_interfaceid) REFERENCES interface (interfaceid) ON DELETE CASCADE;
ALTER TABLE ONLY profiles ADD CONSTRAINT c_profiles_1 FOREIGN KEY (userid) REFERENCES users (userid) ON DELETE CASCADE;
@@ -3352,8 +3405,3 @@ ALTER TABLE ONLY host_proxy ADD CONSTRAINT c_host_proxy_1 FOREIGN KEY (hostid) R
ALTER TABLE ONLY host_proxy ADD CONSTRAINT c_host_proxy_2 FOREIGN KEY (proxyid) REFERENCES proxy (proxyid);
ALTER TABLE ONLY mfa_totp_secret ADD CONSTRAINT c_mfa_totp_secret_1 FOREIGN KEY (mfaid) REFERENCES mfa (mfaid) ON DELETE CASCADE;
ALTER TABLE ONLY mfa_totp_secret ADD CONSTRAINT c_mfa_totp_secret_2 FOREIGN KEY (userid) REFERENCES users (userid) ON DELETE CASCADE;
ALTER TABLE ONLY settings ADD CONSTRAINT c_settings_2 FOREIGN KEY (value_usrgrpid) REFERENCES usrgrp (usrgrpid);
ALTER TABLE ONLY settings ADD CONSTRAINT c_settings_3 FOREIGN KEY (value_hostgroupid) REFERENCES hstgrp (groupid);
ALTER TABLE ONLY settings ADD CONSTRAINT c_settings_4 FOREIGN KEY (value_userdirectoryid) REFERENCES userdirectory (userdirectoryid);
ALTER TABLE ONLY settings ADD CONSTRAINT c_settings_5 FOREIGN KEY (value_mfaid) REFERENCES mfa (mfaid);
ALTER TABLE ONLY lld_macro_export ADD CONSTRAINT c_lld_macro_export_1 FOREIGN KEY (itemid) REFERENCES items (itemid) ON DELETE CASCADE;

View File

@@ -8,7 +8,7 @@ CREATE SCHEMA IF NOT EXISTS partitions;
-- Configuration table to store partitioning settings per table
CREATE TABLE IF NOT EXISTS partitions.config (
table_name text NOT NULL,
period text NOT NULL,
period text NOT NULL CHECK (period IN ('day', 'week', 'month', 'year')),
keep_history interval NOT NULL,
future_partitions integer NOT NULL DEFAULT 5,
last_updated timestamp WITH TIME ZONE DEFAULT (now() AT TIME ZONE 'UTC'),
@@ -22,7 +22,8 @@ CREATE TABLE IF NOT EXISTS partitions.version (
description text
);
INSERT INTO partitions.version (version, description) VALUES ('7-2', 'Added housekeeper task interceptor trigger to drop tasks for partitioned tables')
-- Set initial version
INSERT INTO partitions.version (version, description) VALUES ('1.0', 'Initial release')
ON CONFLICT (version) DO NOTHING;
-- Default configuration for Zabbix tables (adjust as needed)
@@ -32,16 +33,9 @@ INSERT INTO partitions.config (table_name, period, keep_history) VALUES
('history_uint', 'day', '30 days'),
('history_str', 'day', '30 days'),
('history_log', 'day', '30 days'),
('history_text', 'day', '30 days'),
('history_bin', 'day', '30 days')
('history_text', 'day', '30 days')
ON CONFLICT (table_name) DO NOTHING;
-- Zabbix 8.0+ only: Uncomment the following lines if running Zabbix 8.0 or later
-- INSERT INTO partitions.config (table_name, period, keep_history) VALUES
-- ('history_json', 'day', '30 days')
-- ON CONFLICT (table_name) DO NOTHING;
-- Trends tables: Monthly partitions, keep 12 months
INSERT INTO partitions.config (table_name, period, keep_history) VALUES
('trends', 'month', '12 months'),

View File

@@ -2,15 +2,14 @@
-- Core functions for Zabbix partitioning (Create, Drop, Maintain).
-- ============================================================================
-- Function to check if a partition exists in a specific schema
CREATE OR REPLACE FUNCTION partitions.partition_exists(p_partition_name text, p_schema text)
-- Function to check if a partition exists
CREATE OR REPLACE FUNCTION partitions.partition_exists(p_partition_name text)
RETURNS boolean AS $$
BEGIN
RETURN EXISTS (
SELECT 1 FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE c.relname = p_partition_name
AND n.nspname = p_schema
);
END;
$$ LANGUAGE plpgsql;
@@ -33,7 +32,7 @@ BEGIN
SELECT n.nspname INTO v_parent_schema
FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE c.relname = p_parent_table AND pg_table_is_visible(c.oid);
WHERE c.relname = p_parent_table;
IF NOT FOUND THEN
RAISE EXCEPTION 'Parent table % not found', p_parent_table;
@@ -44,28 +43,17 @@ BEGIN
IF p_period = 'month' THEN
v_suffix := to_char(p_start_time, 'YYYYMM');
ELSIF p_period LIKE '%hour%' THEN
v_suffix := to_char(p_start_time, 'YYYYMMDDHH24');
ELSE
v_suffix := to_char(p_start_time, 'YYYYMMDD');
END IF;
v_partition_name := p_parent_table || '_p' || v_suffix;
IF NOT partitions.partition_exists(v_partition_name, v_parent_schema) THEN
BEGIN
IF NOT partitions.partition_exists(v_partition_name) THEN
EXECUTE format(
'CREATE TABLE %I.%I PARTITION OF %I.%I FOR VALUES FROM (%s) TO (%s)',
v_parent_schema, v_partition_name, v_parent_schema, p_parent_table, v_start_ts, v_end_ts
);
EXCEPTION
WHEN invalid_object_definition THEN
-- Ignore overlap errors (e.g., when transitioning from daily to hourly partitioning)
RAISE NOTICE 'Partition % overlaps with an existing partition. Skipping.', v_partition_name;
WHEN duplicate_table THEN
-- Ignore race condition: another process created the partition concurrently
RAISE NOTICE 'Partition % already exists (concurrent creation). Skipping.', v_partition_name;
END;
END IF;
END;
$$;
@@ -94,7 +82,7 @@ BEGIN
JOIN pg_class parent ON pg_inherits.inhparent = parent.oid
JOIN pg_class child ON pg_inherits.inhrelid = child.oid
JOIN pg_namespace n ON child.relnamespace = n.oid
WHERE parent.relname = p_parent_table AND pg_table_is_visible(parent.oid)
WHERE parent.relname = p_parent_table
LOOP
-- Parse partition suffix to determine age
-- Format: parent_pYYYYMM or parent_pYYYYMMDD
@@ -102,48 +90,27 @@ BEGIN
BEGIN
IF length(v_suffix) = 6 THEN -- YYYYMM
v_partition_date := timezone('UTC', to_timestamp(v_suffix || '01', 'YYYYMMDD')::timestamp without time zone);
ELSIF length(v_suffix) = 8 THEN -- YYYYMMDD
v_partition_date := timezone('UTC', to_timestamp(v_suffix, 'YYYYMMDD')::timestamp without time zone);
ELSIF length(v_suffix) = 10 THEN -- YYYYMMDDHH
v_partition_date := timezone('UTC', to_timestamp(v_suffix, 'YYYYMMDDHH24')::timestamp without time zone);
ELSE
CONTINUE; -- Ignore non-matching suffix lengths
END IF;
EXCEPTION WHEN OTHERS THEN
-- Safely ignore parsing errors for oddly named partitions
CONTINUE;
END;
-- Now check retention and execute DROP TABLE (so dropping errors are correctly raised!)
IF length(v_suffix) = 6 THEN -- YYYYMM
v_partition_date := to_timestamp(v_suffix || '01', 'YYYYMMDD') AT TIME ZONE 'UTC';
-- For monthly, we check if the END of the month is older than retention?
-- Or just strict retention.
-- To be safe, adding 1 month to check vs cutoff.
IF extract(epoch from (v_partition_date + '1 month'::interval)) < v_cutoff_ts THEN
RAISE NOTICE 'Dropping old partition %', v_partition.partition_name;
EXECUTE format('DROP TABLE %I.%I', v_partition.partition_schema, v_partition.partition_name);
COMMIT; -- Release lock immediately
END IF;
ELSIF length(v_suffix) = 8 THEN -- YYYYMMDD
-- If period is weekly, the partition spans an entire week. Otherwise, it spans one day.
IF p_period = 'week' THEN
IF extract(epoch from (v_partition_date + '1 week'::interval)) < v_cutoff_ts THEN
RAISE NOTICE 'Dropping old partition %', v_partition.partition_name;
EXECUTE format('DROP TABLE %I.%I', v_partition.partition_schema, v_partition.partition_name);
COMMIT; -- Release lock immediately
END IF;
ELSE
v_partition_date := to_timestamp(v_suffix, 'YYYYMMDD') AT TIME ZONE 'UTC';
IF extract(epoch from (v_partition_date + '1 day'::interval)) < v_cutoff_ts THEN
RAISE NOTICE 'Dropping old partition %', v_partition.partition_name;
EXECUTE format('DROP TABLE %I.%I', v_partition.partition_schema, v_partition.partition_name);
COMMIT; -- Release lock immediately
END IF;
END IF;
ELSIF length(v_suffix) = 10 THEN -- YYYYMMDDHH
IF extract(epoch from (v_partition_date + p_period::interval)) < v_cutoff_ts THEN
RAISE NOTICE 'Dropping old partition %', v_partition.partition_name;
EXECUTE format('DROP TABLE %I.%I', v_partition.partition_schema, v_partition.partition_name);
COMMIT; -- Release lock immediately
END IF;
END IF;
EXCEPTION WHEN OTHERS THEN
-- Ignore parsing errors for non-standard partitions
NULL;
END;
END LOOP;
END;
$$;
@@ -163,29 +130,23 @@ DECLARE
BEGIN
IF p_period = 'day' THEN
v_period_interval := '1 day'::interval;
v_start_time := date_trunc('day', now(), 'UTC');
v_start_time := date_trunc('day', now() AT TIME ZONE 'UTC');
-- Calculate how many past days cover the retention period (86400 seconds = 1 day)
v_past_iterations := ceil(extract(epoch from p_keep_history) / 86400)::integer;
ELSIF p_period = 'week' THEN
v_period_interval := '1 week'::interval;
v_start_time := date_trunc('week', now(), 'UTC');
v_start_time := date_trunc('week', now() AT TIME ZONE 'UTC');
-- 604800 seconds = 1 week
v_past_iterations := ceil(extract(epoch from p_keep_history) / 604800)::integer;
ELSIF p_period = 'month' THEN
v_period_interval := '1 month'::interval;
v_start_time := date_trunc('month', now(), 'UTC');
v_start_time := date_trunc('month', now() AT TIME ZONE 'UTC');
-- Approximate 30 days per month (2592000 seconds)
v_past_iterations := ceil(extract(epoch from p_keep_history) / 2592000)::integer;
ELSIF p_period LIKE '%hour%' THEN
v_period_interval := p_period::interval;
v_start_time := to_timestamp(floor(extract(epoch from now()) / extract(epoch from v_period_interval)) * extract(epoch from v_period_interval));
v_past_iterations := ceil(extract(epoch from p_keep_history) / extract(epoch from v_period_interval))::integer;
ELSE
RAISE EXCEPTION 'Unsupported partitioning period: %', p_period;
RETURN;
END IF;
-- 1. Create Future Partitions (Current + Buffer)
@@ -231,14 +192,3 @@ BEGIN
END LOOP;
END;
$$;
-- Trigger function to silently discard housekeeper tasks for partitioned tables
CREATE OR REPLACE FUNCTION partitions.housekeeper_insert_trigger()
RETURNS TRIGGER AS $$
BEGIN
IF EXISTS (SELECT 1 FROM partitions.config WHERE table_name = NEW.tablename) THEN
RETURN NULL;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;

View File

@@ -19,10 +19,10 @@ BEGIN
SELECT n.nspname INTO v_schema
FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE c.relname = v_table AND pg_table_is_visible(c.oid);
WHERE c.relname = v_table;
IF EXISTS (SELECT 1 FROM pg_class WHERE relname = v_table AND relkind = 'r' AND pg_table_is_visible(oid)) THEN
IF EXISTS (SELECT 1 FROM pg_class WHERE relname = v_table AND relkind = 'r') THEN
RAISE NOTICE 'Converting table % to partitioned table...', v_table;
-- 1. Rename existing table
@@ -32,11 +32,8 @@ BEGIN
IF v_table = 'auditlog' THEN
EXECUTE format('CREATE TABLE %I.%I (LIKE %I.%I INCLUDING DEFAULTS INCLUDING COMMENTS) PARTITION BY RANGE (clock)', v_schema, v_table, v_schema, v_old_table);
EXECUTE format('ALTER TABLE %I.%I ADD PRIMARY KEY (auditid, clock)', v_schema, v_table);
EXECUTE format('CREATE INDEX IF NOT EXISTS auditlog_p_1 ON %I.%I (userid, clock)', v_schema, v_table);
EXECUTE format('CREATE INDEX IF NOT EXISTS auditlog_p_2 ON %I.%I (clock)', v_schema, v_table);
EXECUTE format('CREATE INDEX IF NOT EXISTS auditlog_p_3 ON %I.%I (resourcetype, resourceid)', v_schema, v_table);
EXECUTE format('CREATE INDEX IF NOT EXISTS auditlog_p_4 ON %I.%I (recordsetid)', v_schema, v_table);
EXECUTE format('CREATE INDEX IF NOT EXISTS auditlog_p_5 ON %I.%I (ip)', v_schema, v_table);
EXECUTE format('CREATE INDEX IF NOT EXISTS auditlog_1 ON %I.%I (userid, clock)', v_schema, v_table);
EXECUTE format('CREATE INDEX IF NOT EXISTS auditlog_2 ON %I.%I (clock)', v_schema, v_table);
ELSE
EXECUTE format('CREATE TABLE %I.%I (LIKE %I.%I INCLUDING ALL) PARTITION BY RANGE (clock)', v_schema, v_table, v_schema, v_old_table);
END IF;
@@ -48,37 +45,12 @@ BEGIN
-- Optional: Migrate existing data
-- EXECUTE format('INSERT INTO %I.%I SELECT * FROM %I.%I', v_schema, v_table, v_schema, v_old_table);
ELSIF EXISTS (SELECT 1 FROM pg_class WHERE relname = v_table AND relkind = 'p' AND pg_table_is_visible(oid)) THEN
ELSIF EXISTS (SELECT 1 FROM pg_class WHERE relname = v_table AND relkind = 'p') THEN
RAISE NOTICE 'Table % is already partitioned. Skipping conversion.', v_table;
-- Just run maintenance for this specific table to ensure partitions exist
CALL partitions.maintain_table(v_table, v_row.period, v_row.keep_history, v_row.future_partitions);
-- Just run maintenance to ensure partitions exist
CALL partitions.run_maintenance();
ELSE
RAISE WARNING 'Table % not found!', v_table;
END IF;
END LOOP;
-- Attach trigger to housekeeper table to silently discard tasks for partitioned tables.
-- Dynamically determine the schema of the housekeeper table to support custom schemas.
SELECT n.nspname INTO v_schema
FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE c.relname = 'housekeeper' AND pg_table_is_visible(c.oid);
IF v_schema IS NOT NULL THEN
EXECUTE format('DROP TRIGGER IF EXISTS housekeeper_filter ON %I.housekeeper', v_schema);
EXECUTE format('CREATE TRIGGER housekeeper_filter BEFORE INSERT ON %I.housekeeper FOR EACH ROW EXECUTE FUNCTION partitions.housekeeper_insert_trigger()', v_schema);
RAISE NOTICE 'Housekeeper intercept trigger installed on %.housekeeper', v_schema;
ELSE
RAISE WARNING 'housekeeper table not found — trigger NOT installed!';
END IF;
END $$;
-- ==========================================================================
-- IMPORTANT: If the Zabbix Server connects with a non-superuser (e.g., 'zabbix'),
-- that user MUST have access to the partitions schema for the housekeeper trigger
-- to work. Without these GRANTs, every INSERT into housekeeper will FAIL.
-- Uncomment and adjust the username below:
-- ==========================================================================
-- GRANT USAGE ON SCHEMA partitions TO zabbix;
-- GRANT SELECT ON partitions.config TO zabbix;

View File

@@ -8,26 +8,20 @@ SELECT
c.table_name,
c.period,
c.keep_history,
c.future_partitions AS configured_future_partitions,
count(child.relname) AS partition_count,
count(child.relname) FILTER (
WHERE
(c.period = 'day' AND child.relname > (parent.relname || '_p' || to_char(now() AT TIME ZONE 'UTC', 'YYYYMMDD')))
(c.period = 'day' AND child.relname > (parent.relname || '_p' || to_char(now(), 'YYYYMMDD')))
OR
(c.period = 'month' AND child.relname > (parent.relname || '_p' || to_char(now() AT TIME ZONE 'UTC', 'YYYYMM')))
OR
(c.period = 'week' AND child.relname > (parent.relname || '_p' || to_char(date_trunc('week', now() AT TIME ZONE 'UTC'), 'YYYYMMDD')))
OR
(c.period LIKE '%hour%' AND child.relname > (parent.relname || '_p' || to_char(now() AT TIME ZONE 'UTC', 'YYYYMMDDHH24')))
) AS actual_future_partitions,
sum(pg_total_relation_size(child.oid)) AS total_size_bytes,
(c.period = 'month' AND child.relname > (parent.relname || '_p' || to_char(now(), 'YYYYMM')))
) AS future_partitions,
pg_size_pretty(sum(pg_total_relation_size(child.oid))) AS total_size,
min(child.relname) AS oldest_partition,
max(child.relname) AS newest_partition,
c.last_updated
FROM partitions.config c
JOIN pg_class parent ON parent.relname = c.table_name AND pg_table_is_visible(parent.oid)
JOIN pg_class parent ON parent.relname = c.table_name
LEFT JOIN pg_inherits ON pg_inherits.inhparent = parent.oid
LEFT JOIN pg_class child ON pg_inherits.inhrelid = child.oid
WHERE parent.relkind = 'p' -- Only partitioned tables
GROUP BY parent.relname, c.table_name, c.period, c.keep_history, c.future_partitions, c.last_updated;
GROUP BY parent.relname, c.table_name, c.period, c.keep_history, c.last_updated;

File diff suppressed because it is too large Load Diff