Compare commits

...

7 Commits

Author SHA1 Message Date
094debc46c Change: 0.6.0 - SSL, housekeeper cleanup, short flags, various improvemets. Check changelog. 2025-12-20 22:18:00 +01:00
d2de7f8b02 Change: Adjusted README to the new changes. Added runtime parameters. 2025-12-16 23:05:44 +01:00
3b2bebf6b9 Change: Improved wizard logic 2025-12-16 22:40:27 +01:00
059aa81c5c Change: Changelog and version changed 2025-12-16 22:14:37 +01:00
08ba77cdf0 Feature: Added setup wizard. Changed scan-skip logic.
Some checks failed
Zabbix APK Builder / check-version (push) Successful in 14s
Zabbix APK Builder / update-version (push) Has been skipped
Zabbix APK Builder / build-packages (push) Failing after 21s
Zabbix APK Builder / deploy-test (push) Has been skipped
2025-12-16 22:11:56 +01:00
bcd2bd627e Fix: Template adjustments 2025-12-16 21:33:53 +01:00
9e53259e61 Fix: Socket path 2025-12-16 19:23:45 +01:00
7 changed files with 625 additions and 202 deletions

View File

@@ -5,6 +5,32 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.6.0] - 2025-12-20
### Added
- **SSL**: Added support for simplified SSL configuration (`ssl: required`) for Managed/Cloud databases and updated Wizard/Config.
- **Safety**: Added automatic cleanup of `housekeeper` table during init (targeted) and conflict detection during maintenance.
- **CLI**: Added short flags for all arguments (`-vv`, `-f`, `-d`, `-s`, `-w`).
- **Validation**: Enforced strict unit validation for retention periods and strict table existence checks.
- **Error Handling**: Improved error reporting used to identify specific tables causing configuration errors.
### Changed
- **CLI**: Refactored `--init` and `--fast-init` into mutually exclusive standalone commands (Breaking Change).
- **CLI**: Reorganized argument parsing logic for better maintainability.
- **Safety**: Updated initialization warnings to explicitly mention duration ("SEVERAL HOURS") and disk space requirements.
- **Logic**: Refined housekeeper conflict check to only warn about tables actively configured for partitioning.
## [0.5.0] - 2025-12-16
### Added
- **Wizard**: Added interactive configuration wizard (`--wizard`).
- **CLI**: Added `--fast-init` flag to skip table scanning during initialization, instead of config option.
- **Monitoring**: Added `--stats` argument to output JSON statistics (Size, Count, Days Left).
- **Template**: Updated `zabbix_mysql_partitioning_template.yaml` to use Master/Dependent items for efficient stats collection.
### Changed
- **Config**: Removed `initial_partitioning_start` from configuration file (moved to `--fast-init`).
- **Template**: Replaced legacy check item with comprehensive stats master item.
- **CLI**: Removed legacy `--check-days` argument (replaced by `--stats`).
## [0.4.1] - 2025-12-16 ## [0.4.1] - 2025-12-16
### Added ### Added
- **CLI**: Added `--verbose` / `-v` flag to switch between INFO (default) and DEBUG logging levels. - **CLI**: Added `--verbose` / `-v` flag to switch between INFO (default) and DEBUG logging levels.

View File

@@ -47,8 +47,16 @@ The script manages MySQL table partitions based on time (Range Partitioning on t
--- ---
## 3. Configuration ## 3. Configuration
Edit `/etc/zabbix/zabbix_partitioning.conf`: The preferred way to create the configuration file is using the **Interactive Wizard**:
```bash
/opt/zabbix_partitioning/zabbix_partitioning.py --wizard
```
Follow the on-screen prompts to configure your database connection, retention periods, and other settings. The wizard will generate the YAML configuration file for you.
### Automatic Config Structure (Reference)
If you prefer to edit manually `/etc/zabbix/zabbix_partitioning.conf`:
```yaml ```yaml
database: database:
host: localhost host: localhost
@@ -56,6 +64,7 @@ database:
passwd: YOUR_PASSWORD passwd: YOUR_PASSWORD
db: zabbix db: zabbix
# port: 3306 # Optional, default is 3306 # port: 3306 # Optional, default is 3306
# socket: /var/run/mysqld/mysqld.sock # Overrides host if present
partitions: partitions:
daily: daily:
@@ -74,9 +83,7 @@ partitions:
- **`yearly`**: Partitions are created for each year. - **`yearly`**: Partitions are created for each year.
- Retention Format: `14d` (days), `12w` (weeks), `12m` (months), `1y` (years). - Retention Format: `14d` (days), `12w` (weeks), `12m` (months), `1y` (years).
- **`initial_partitioning_start`**: Controls how the very FIRST partition is determined during initialization (`--init` mode). - **`replicate_sql`**: Controls MySQL Binary Logging for partitioning commands.
- `db_min`: (Default) Queries the table for the oldest record (`MIN(clock)`). Accurate but **slow** on large tables.
- `retention`: (Recommended for large DBs) Skips the query. Calculates the start date as `Now - Retention Period`. Creates a single `p_archive` partition for all data older than that date.
- **`premake`**: Number of future partitions to create in advance. - **`premake`**: Number of future partitions to create in advance.
- Default: `10`. Ensures you have a buffer if the script fails to run for a few days. - Default: `10`. Ensures you have a buffer if the script fails to run for a few days.
@@ -90,7 +97,25 @@ partitions:
--- ---
## 4. Zabbix Preparation (CRITICAL) ---
## 4. Runtime Parameters
| Argument | Description |
|---|---|
| `-c`, `--config FILE` | Path to configuration file (Default: `/etc/zabbix/zabbix_partitioning.conf`) |
| `-i`, `--init` | Initialize partitions (Standard Mode: Scan Table). |
| `-f`, `--fast-init` | Initialize partitions (Fast Mode: Skip Scan, use Retention). |
| `--wizard` | Launch interactive configuration wizard. |
| `-r`, `--dry-run` | Simulate queries without executing. Logs expected actions (Safe mode). |
| `-v`, `--verbose` | Enable debug logging (DEBUG level). |
| `--stats TABLE` | Output JSON statistics (Size, Count, Days Left) for a specific table. |
| `--discovery` | Output Zabbix Low-Level Discovery (LLD) JSON. |
| `-V`, `--version` | Show script version. |
---
## 5. Zabbix Preparation (CRITICAL)
Before partitioning, you **must disable** Zabbix's internal housekeeping for the tables you intend to partition. If you don't, Zabbix will try to delete individual rows while the script tries to drop partitions, causing conflicts. Before partitioning, you **must disable** Zabbix's internal housekeeping for the tables you intend to partition. If you don't, Zabbix will try to delete individual rows while the script tries to drop partitions, causing conflicts.
1. Log in to Zabbix Web Interface. 1. Log in to Zabbix Web Interface.
@@ -102,38 +127,52 @@ Before partitioning, you **must disable** Zabbix's internal housekeeping for the
--- ---
## 5. Initialization ## 6. Initialization
This step converts existing standard tables into partitioned tables. This step converts existing standard tables into partitioned tables.
1. **Dry Run** (Verify what will happen): 1. **Dry Run** (Verify what will happen):
```bash ```bash
/opt/zabbix_partitioning/zabbix_partitioning.py --init --dry-run /opt/zabbix_partitioning/zabbix_partitioning.py --init --dry-run
``` ```
*Check the output for any errors.*
2. **Execute Initialization**: 2. **Execute Initialization**:
> [!WARNING]
> For large databases, standard initialization may take **SEVERAL HOURS** and require significant disk space (approx 2x table size).
There are two strategies for initialization:
**A. Standard Initialization (`--init`)**:
Scans the database to find the oldest record (`MIN(clock)`) and creates partitions from that point forward.
*Best for smaller databases or when you need to retain ALL existing data.*
```bash ```bash
/opt/zabbix_partitioning/zabbix_partitioning.py --init /opt/zabbix_partitioning/zabbix_partitioning.py --init
``` ```
*This may take time depending on table size.*
**B. Fast Initialization (`--fast-init`)**:
Skips the slow table scan. It calculates the start date based on your configured **Retention Period** (e.g., `Now - 365d`).
It creates a single catch-all `p_archive` partition for all data older than the retention start date, then creates granular partitions forward.
*Recommended for large databases to avoid long table locks/scans.*
```bash
/opt/zabbix_partitioning/zabbix_partitioning.py --fast-init
```
--- ---
## 6. Automation (Cron Job) ## 7. Automation (Cron Job)
Set up a daily cron job to create new partitions and remove old ones. Set up a cron job to create new partitions and remove old ones.
1. Open crontab: 1. Open crontab:
```bash ```bash
crontab -e crontab -e
``` ```
2. Add the line (run daily at 00:30): 2. Add the line (run twice a day at 00:10 and 04:10):
```cron ```cron
30 0 * * * /usr/bin/python3 /opt/zabbix_partitioning/zabbix_partitioning.py -c /etc/zabbix/zabbix_partitioning.conf >> /var/log/zabbix_partitioning.log 2>&1 10 0,4 * * * /usr/bin/python3 /opt/zabbix_partitioning/zabbix_partitioning.py -c /etc/zabbix/zabbix_partitioning.conf >> /var/log/zabbix_partitioning.log 2>&1
``` ```
--- ---
## 7. Automation (Systemd Timer) — Recommended ## 8. Automation (Systemd Timer) — Recommended
Alternatively, use systemd timers for more robust scheduling and logging. Alternatively, use systemd timers for more robust scheduling and logging.
1. **Create Service Unit** (`/etc/systemd/system/zabbix-partitioning.service`): 1. **Create Service Unit** (`/etc/systemd/system/zabbix-partitioning.service`):
@@ -151,10 +190,10 @@ Alternatively, use systemd timers for more robust scheduling and logging.
2. **Create Timer Unit** (`/etc/systemd/system/zabbix-partitioning.timer`): 2. **Create Timer Unit** (`/etc/systemd/system/zabbix-partitioning.timer`):
```ini ```ini
[Unit] [Unit]
Description=Run Zabbix Partitioning Daily Description=Run Zabbix Partitioning twice a day
[Timer] [Timer]
OnCalendar=*-*-* 00:30:00 OnCalendar=*-*-* 00:10:00 *-*-* 04:10:00
Persistent=true Persistent=true
[Install] [Install]
@@ -176,12 +215,12 @@ Alternatively, use systemd timers for more robust scheduling and logging.
--- ---
## 8. Troubleshooting ## 9. Troubleshooting
- **Connection Refused**: Check `host`, `port` in config. Ensure MySQL is running. - **Connection Refused**: Check `host`, `port` in config. Ensure MySQL is running.
- **Access Denied (1227)**: The DB user needs `SUPER` privileges to disable binary logging (`replicate_sql: False`). Either grant the privilege or set `replicate_sql: True` (if replication load is acceptable). - **Access Denied (1227)**: The DB user needs `SUPER` privileges to disable binary logging (`replicate_sql: False`). Either grant the privilege or set `replicate_sql: True` (if replication load is acceptable).
- **Primary Key Error**: "Primary Key does not include 'clock'". The table cannot be partitioned by range on `clock` without schema changes. Remove it from config. - **Primary Key Error**: "Primary Key does not include 'clock'". The table cannot be partitioned by range on `clock` without schema changes. Remove it from config.
## 9. Docker Usage ## 10. Docker Usage
You can run the partitioning script as a stateless Docker container. This is ideal for Kubernetes CronJobs or environments where you don't want to manage Python dependencies on the host. You can run the partitioning script as a stateless Docker container. This is ideal for Kubernetes CronJobs or environments where you don't want to manage Python dependencies on the host.
@@ -267,8 +306,8 @@ docker run --rm \
| `RETENTION_TRENDS` | 365d | Retention for `trends*` tables | | `RETENTION_TRENDS` | 365d | Retention for `trends*` tables |
| `RETENTION_AUDIT` | 365d | Retention for `auditlog` (if enabled) | | `RETENTION_AUDIT` | 365d | Retention for `auditlog` (if enabled) |
| `ENABLE_AUDITLOG_PARTITIONING` | false | Set to `true` to partition `auditlog` | | `ENABLE_AUDITLOG_PARTITIONING` | false | Set to `true` to partition `auditlog` |
| `RUN_MODE` | maintenance | `init`, `maintenance`, `dry-run`, `discovery`, `check` | | `RUN_MODE` | maintenance | `init`, `maintenance`, `dry-run`, `discovery`, `stats` |
| `CHECK_TARGET` | - | Required if `RUN_MODE=check`. Table name to check (e.g. `history`). | | `CHECK_TARGET` | - | Required if `RUN_MODE=stats`. Table name to check (e.g. `history`). |
| `PARTITION_DAILY_[TABLE]` | - | Custom daily retention (e.g., `PARTITION_DAILY_mytable=30d`) | | `PARTITION_DAILY_[TABLE]` | - | Custom daily retention (e.g., `PARTITION_DAILY_mytable=30d`) |
| `PARTITION_WEEKLY_[TABLE]` | - | Custom weekly retention | | `PARTITION_WEEKLY_[TABLE]` | - | Custom weekly retention |
| `PARTITION_MONTHLY_[TABLE]` | - | Custom monthly retention | | `PARTITION_MONTHLY_[TABLE]` | - | Custom monthly retention |
@@ -282,36 +321,37 @@ docker run --rm \
zabbix-partitioning zabbix-partitioning
``` ```
#### Scenario G: Monitoring (Health Check) #### Scenario G: Monitoring (Stats)
Check days remaining for a specific table (e.g., `history`). Returns integer days. Get detailed JSON statistics for a specific table.
```bash ```bash
docker run --rm \ docker run --rm \
-e DB_HOST=zabbix-db \ -e DB_HOST=zabbix-db \
-e RUN_MODE=check \ -e RUN_MODE=stats \
-e CHECK_TARGET=history \ -e CHECK_TARGET=history \
zabbix-partitioning zabbix-partitioning
``` ```
--- ---
## 10. Monitoring ## 11. Monitoring
The script includes built-in features for monitoring the health of your partitions via Zabbix. The script includes built-in features for monitoring the health of your partitions via Zabbix.
### 10.1 CLI Usage ### 10.1 CLI Usage
- **Discovery (LLD)**: - **Discovery (LLD)**:
Output the list of configured tables for Zabbix Discovery rules.
```bash ```bash
./zabbix_partitioning.py --discovery ./zabbix_partitioning.py --discovery
# Output: [{"{#TABLE}": "history", "{#PERIOD}": "daily"}, ...] # Output: [{"{#TABLE}": "history", "{#PERIOD}": "daily"}, ...]
``` ```
- **Check Days**: - **Statistics (JSON)**:
Get detailed stats (Size, Partition Count, Days Remaining).
```bash ```bash
./zabbix_partitioning.py --check-days history ./zabbix_partitioning.py --stats history
# Output: 30 (integer days remaining) # Output: {"table": "history", "size_bytes": 102400, "partition_count": 5, "days_left": 30}
``` ```
- **Version**: - **Version**:
```bash ```bash
./zabbix_partitioning.py --version ./zabbix_partitioning.py --version
# Output: zabbix_partitioning.py 0.3.1-test
``` ```
### 10.2 Zabbix Template ### 10.2 Zabbix Template

View File

@@ -105,6 +105,13 @@ def main():
sys.exit(1) sys.exit(1)
cmd.append('--check-days') cmd.append('--check-days')
cmd.append(target) cmd.append(target)
elif run_mode == 'stats':
target = os.getenv('CHECK_TARGET')
if not target:
print("Error: CHECK_TARGET env var required for stats mode")
sys.exit(1)
cmd.append('--stats')
cmd.append(target)
print(f"Executing: {' '.join(cmd)}") print(f"Executing: {' '.join(cmd)}")
result = subprocess.run(cmd) result = subprocess.run(cmd)

View File

@@ -2,15 +2,20 @@
database: database:
type: mysql type: mysql
# host: Database server hostname or IP # host: Database server hostname or IP
host: localhost # host: localhost
# socket: Path to the MySQL unix socket (overrides host if set) # socket: Path to the MySQL unix socket (overrides host if set)
socket: /var/run/mysqlrouter/mysql_rw.sock socket: /var/run/mysqld/mysqld.sock
# port: Database port (default: 3306) # port: Database port (default: 3306)
# port: 3306 # port: 3306
# credentials # credentials
user: zbx_part user: zbx_part
passwd: <password> passwd: <password>
db: zabbix db: zabbix
# ssl: required # Use system CAs (Mandatory for Azure/AWS/GCP)
# ssl: # Or use custom certificates
# ca: /etc/ssl/certs/ca-cert.pem
# cert: /etc/ssl/certs/client-cert.pem
# key: /etc/ssl/certs/client-key.pem
# partitions: Define retention periods for tables. # partitions: Define retention periods for tables.
# Format: table_name: duration (e.g., 14d, 12w, 1m, 1y) # Format: table_name: duration (e.g., 14d, 12w, 1m, 1y)
@@ -26,9 +31,8 @@ partitions:
# weekly: Partitions created weekly # weekly: Partitions created weekly
weekly: weekly:
# - auditlog: 180d # - auditlog: 180d
# Note: auditlog is not partitionable by default in Zabbix 7.0 and 7.4 (PK missing clock). # Note: auditlog is not partitionable by default in Zabbix 7.0 (PK missing clock).
# To partition, the Primary Key must be altered to include 'clock'. # To partition, the Primary Key must be altered to include 'clock'.
# https://www.zabbix.com/documentation/current/en/manual/appendix/install/auditlog_primary_keys
# monthly: Partitions created monthly # monthly: Partitions created monthly
monthly: monthly:
- trends: 1y - trends: 1y
@@ -40,14 +44,6 @@ logging: syslog
# premake: Number of partitions to create in advance # premake: Number of partitions to create in advance
premake: 10 premake: 10
# initial_partitioning_start: Strategy for the first partition during initialization (--init).
# Options:
# db_min: (Default) Queries SELECT MIN(clock) to ensure ALL data is covered. Slow on huge tables consistently.
# retention: Starts partitioning from (Now - Retention Period).
# Creates a 'p_archive' partition for all data older than retention.
# Much faster as it skips the MIN(clock) query. (Recommended for large DBs)
initial_partitioning_start: db_min
# replicate_sql: False - Disable binary logging. Partitioning changes are NOT replicated to slaves (use for independent maintenance). # replicate_sql: False - Disable binary logging. Partitioning changes are NOT replicated to slaves (use for independent maintenance).
# replicate_sql: True - Enable binary logging. Partitioning changes ARE replicated to slaves (use for consistent cluster schema). # replicate_sql: True - Enable binary logging. Partitioning changes ARE replicated to slaves (use for consistent cluster schema).
replicate_sql: False replicate_sql: False

View File

@@ -1,9 +1,8 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
Zabbix Database Partitioning Management Script Zabbix MySQL Partitioning Script
Refactored for Zabbix 7.x compatibility, better maintainability, and standard logging.
""" """
import os import os
@@ -21,7 +20,7 @@ from typing import Optional, Dict, List, Any, Union, Tuple
from contextlib import contextmanager from contextlib import contextmanager
# Semantic Versioning # Semantic Versioning
VERSION = '0.4.1' VERSION = '0.6.0'
# Constants # Constants
PART_PERIOD_REGEX = r'([0-9]+)(h|d|m|y)' PART_PERIOD_REGEX = r'([0-9]+)(h|d|m|y)'
@@ -35,9 +34,10 @@ class DatabaseError(Exception):
pass pass
class ZabbixPartitioner: class ZabbixPartitioner:
def __init__(self, config: Dict[str, Any], dry_run: bool = False): def __init__(self, config: Dict[str, Any], dry_run: bool = False, fast_init: bool = False):
self.config = config self.config = config
self.dry_run = dry_run self.dry_run = dry_run
self.fast_init = fast_init
self.conn = None self.conn = None
self.logger = logging.getLogger('zabbix_partitioning') self.logger = logging.getLogger('zabbix_partitioning')
@@ -73,7 +73,12 @@ class ZabbixPartitioner:
connect_args['host'] = self.db_host connect_args['host'] = self.db_host
if self.db_ssl: if self.db_ssl:
connect_args['ssl'] = self.db_ssl if self.db_ssl is True or (isinstance(self.db_ssl, str) and self.db_ssl.lower() == 'required'):
# Enable SSL with default context (uses system CAs)
# Useful for Cloud DBs (Azure, AWS) that require TLS by default
connect_args['ssl'] = {}
else:
connect_args['ssl'] = self.db_ssl
# PyMySQL SSL options # PyMySQL SSL options
# Note: valid ssl keys for PyMySQL are 'ca', 'capath', 'cert', 'key', 'cipher', 'check_hostname' # Note: valid ssl keys for PyMySQL are 'ca', 'capath', 'cert', 'key', 'cipher', 'check_hostname'
@@ -176,9 +181,11 @@ class ZabbixPartitioner:
""" """
Calculate the retention date based on config string (e.g., "30d", "12m"). Calculate the retention date based on config string (e.g., "30d", "12m").
""" """
# Ensure string
period_str = str(period_str)
match = re.search(PART_PERIOD_REGEX, period_str) match = re.search(PART_PERIOD_REGEX, period_str)
if not match: if not match:
raise ConfigurationError(f"Invalid period format: {period_str}") raise ConfigurationError(f"Invalid period format: '{period_str}' (Expected format like 30d, 12w, 1y)")
amount = int(match.group(1)) amount = int(match.group(1))
unit = match.group(2) unit = match.group(2)
@@ -232,10 +239,9 @@ class ZabbixPartitioner:
raise DatabaseError("Could not determine MySQL version") raise DatabaseError("Could not determine MySQL version")
# MySQL 8.0+ supports partitioning natively # MySQL 8.0+ supports partitioning natively
# (Assuming MySQL 8+ or MariaDB 10+ for modern Zabbix)
self.logger.info(f"MySQL Version: {version_str}") self.logger.info(f"MySQL Version: {version_str}")
# 2. Check Zabbix DB Version (optional info) # 2. Check Zabbix DB Version
try: try:
mandatory = self.execute_query('SELECT `mandatory` FROM `dbversion`', fetch='one') mandatory = self.execute_query('SELECT `mandatory` FROM `dbversion`', fetch='one')
if mandatory: if mandatory:
@@ -243,6 +249,18 @@ class ZabbixPartitioner:
except Exception: except Exception:
self.logger.warning("Could not read 'dbversion' table. Is this a Zabbix DB?") self.logger.warning("Could not read 'dbversion' table. Is this a Zabbix DB?")
def validate_table_exists(self, table: str) -> bool:
"""Return True if table exists in the database."""
# Use simple select from information_schema
exists = self.execute_query(
"SELECT 1 FROM information_schema.tables WHERE table_schema = %s AND table_name = %s",
(self.db_name, table), fetch='one'
)
if not exists:
self.logger.error(f"Table '{table}' does NOT exist in database '{self.db_name}'. Skipped.")
return False
return True
def get_table_min_clock(self, table: str) -> Optional[datetime]: def get_table_min_clock(self, table: str) -> Optional[datetime]:
ts = self.execute_query(f"SELECT MIN(`clock`) FROM `{table}`", fetch='one') ts = self.execute_query(f"SELECT MIN(`clock`) FROM `{table}`", fetch='one')
return datetime.fromtimestamp(int(ts)) if ts else None return datetime.fromtimestamp(int(ts)) if ts else None
@@ -372,78 +390,131 @@ class ZabbixPartitioner:
for name in to_drop: for name in to_drop:
self.execute_query(f"ALTER TABLE `{table}` DROP PARTITION {name}") self.execute_query(f"ALTER TABLE `{table}` DROP PARTITION {name}")
def initialize_partitioning(self, table: str, period: str, premake: int, retention_str: str):
"""Initial partitioning for a table (convert regular table to partitioned)."""
self.logger.info(f"Initializing partitioning for {table}")
def _check_init_prerequisites(self, table: str) -> bool:
"""Return True if partitioning can proceed."""
if self.has_incompatible_primary_key(table): if self.has_incompatible_primary_key(table):
self.logger.error(f"Cannot partition {table}: Primary Key does not include 'clock' column.") self.logger.error(f"Cannot partition {table}: Primary Key does not include 'clock' column.")
return return False
# If already partitioned, skip
if self.get_existing_partitions(table): if self.get_existing_partitions(table):
self.logger.info(f"Table {table} is already partitioned.") self.logger.info(f"Table {table} is already partitioned.")
return return False
init_strategy = self.config.get('initial_partitioning_start', 'db_min') # Disk Space & Lock Warning
start_dt = None msg = (
p_archive_ts = None f"WARNING: Partitioning table '{table}' requires creating a copy of the table.\n"
f" Ensure you have free disk space >= Data_Length + Index_Length (approx 2x table size).\n"
f" For large databases, this operation may take SEVERAL HOURS to complete."
)
self.logger.warning(msg)
if init_strategy == 'retention': # Interactive Check
self.logger.info(f"Strategy 'retention': Calculating start date from retention ({retention_str})") if sys.stdin.isatty():
retention_date = self.get_lookback_date(retention_str) print(f"\n{msg}")
# Start granular partitions from the retention date if input("Do you have enough free space and is Zabbix stopped? [y/N]: ").lower().strip() != 'y':
start_dt = self.truncate_date(retention_date, period) self.logger.error("Initialization aborted by user.")
# Create a catch-all for anything older return False
p_archive_ts = int(start_dt.timestamp())
else:
# Default 'db_min' strategy
self.logger.info("Strategy 'db_min': Querying table for minimum clock (may be slow)")
min_clock = self.get_table_min_clock(table)
if not min_clock: return True
# Empty table. Start from NOW
start_dt = self.truncate_date(datetime.now(), period)
else:
# Table has data.
start_dt = self.truncate_date(min_clock, period)
# Build list of partitions from start_dt up to NOW + premake def _generate_init_sql(self, table: str, period: str, start_dt: datetime, premake: int, p_archive_ts: int = None):
"""Generate and execute ALTER TABLE command."""
target_dt = self.get_next_date(self.truncate_date(datetime.now(), period), period, premake) target_dt = self.get_next_date(self.truncate_date(datetime.now(), period), period, premake)
curr = start_dt
partitions_def = {}
# If we have an archive partition, add it first
if p_archive_ts:
partitions_def['p_archive'] = str(p_archive_ts)
while curr < target_dt:
name = self.get_partition_name(curr, period)
desc = self.get_partition_description(curr, period)
partitions_def[name] = desc
curr = self.get_next_date(curr, period, 1)
# Re-doing the loop to be cleaner on types
parts_sql = [] parts_sql = []
# 1. Archive Partition
if p_archive_ts: if p_archive_ts:
parts_sql.append(f"PARTITION p_archive VALUES LESS THAN ({p_archive_ts}) ENGINE = InnoDB") parts_sql.append(f"PARTITION p_archive VALUES LESS THAN ({p_archive_ts}) ENGINE = InnoDB")
# 2. Granular Partitions
# We need to iterate again from start_dt
curr = start_dt curr = start_dt
while curr < target_dt: while curr < target_dt:
name = self.get_partition_name(curr, period) name = self.get_partition_name(curr, period)
desc_date_str = self.get_partition_description(curr, period) # Returns "YYYY-MM-DD HH:MM:SS" desc_date_str = self.get_partition_description(curr, period)
parts_sql.append(PARTITION_TEMPLATE % (name, desc_date_str)) parts_sql.append(PARTITION_TEMPLATE % (name, desc_date_str))
curr = self.get_next_date(curr, period, 1) curr = self.get_next_date(curr, period, 1)
if not parts_sql:
self.logger.warning(f"No partitions generated for {table}")
return
query = f"ALTER TABLE `{table}` PARTITION BY RANGE (`clock`) (\n" + ",\n".join(parts_sql) + "\n)" query = f"ALTER TABLE `{table}` PARTITION BY RANGE (`clock`) (\n" + ",\n".join(parts_sql) + "\n)"
self.logger.info(f"Applying initial partitioning to {table} ({len(parts_sql)} partitions)") self.logger.info(f"Applying initial partitioning to {table} ({len(parts_sql)} partitions)")
self.execute_query(query) self.execute_query(query)
def _get_configured_tables(self) -> List[str]:
"""Return a list of all tables configured for partitioning."""
tables_set = set()
partitions_conf = self.config.get('partitions', {})
for tables in partitions_conf.values():
if not tables: continue
for item in tables:
tables_set.add(list(item.keys())[0])
return list(tables_set)
def clean_housekeeper_table(self):
"""
Clean up Zabbix housekeeper table during initialization.
Removes pending tasks ONLY for configured tables to prevent conflicts.
"""
configured_tables = self._get_configured_tables()
if not configured_tables:
return
self.logger.info(f"Cleaning up 'housekeeper' table for: {', '.join(configured_tables)}")
# Construct IN clause
placeholders = ', '.join(['%s'] * len(configured_tables))
query = f"DELETE FROM `housekeeper` WHERE `tablename` IN ({placeholders})"
self.execute_query(query, configured_tables)
def check_housekeeper_execution(self):
"""
Check if Zabbix internal housekeeper is running for configured partitioned tables.
"""
configured_tables = self._get_configured_tables()
if not configured_tables:
return
query = "SELECT DISTINCT `tablename` FROM `housekeeper` WHERE `tablename` != 'events'"
rows = self.execute_query(query, fetch='all')
if rows:
found_tables = {r[0] for r in rows}
configured_set = set(configured_tables)
conflicts = found_tables.intersection(configured_set)
if conflicts:
self.logger.error(f"CRITICAL: Found pending housekeeper tasks for partitioned tables: {', '.join(conflicts)}")
self.logger.error("Please DISABLE Zabbix internal housekeeper for these tables in Administration -> General -> Housekeeping!")
def initialize_partitioning_fast(self, table: str, period: str, premake: int, retention_str: str):
"""Fast Init: Use retention period to determine start date."""
self.logger.info(f"Initializing partitioning for {table} (Fast Mode)")
if not self._check_init_prerequisites(table): return
self.logger.info(f"Strategy 'fast-init': Calculating start date from retention ({retention_str})")
retention_date = self.get_lookback_date(retention_str)
start_dt = self.truncate_date(retention_date, period)
p_archive_ts = int(start_dt.timestamp())
self._generate_init_sql(table, period, start_dt, premake, p_archive_ts)
def initialize_partitioning_scan(self, table: str, period: str, premake: int):
"""Scan Init: Scan table for minimum clock value."""
self.logger.info(f"Initializing partitioning for {table} (Scan Mode)")
if not self._check_init_prerequisites(table): return
self.logger.info("Strategy 'db_min': Querying table for minimum clock (may be slow)")
min_clock = self.get_table_min_clock(table)
if not min_clock:
start_dt = self.truncate_date(datetime.now(), period)
else:
start_dt = self.truncate_date(min_clock, period)
self._generate_init_sql(table, period, start_dt, premake)
def discovery(self): def discovery(self):
"""Output Zabbix Low-Level Discovery logic JSON.""" """Output Zabbix Low-Level Discovery logic JSON."""
partitions_conf = self.config.get('partitions', {}) partitions_conf = self.config.get('partitions', {})
@@ -481,6 +552,51 @@ class ZabbixPartitioner:
diff = end_dt - now diff = end_dt - now
return max(0, diff.days) return max(0, diff.days)
def get_table_stats(self, table: str) -> Dict[str, Any]:
"""
Get detailed statistics for a table:
- size_bytes (data + index)
- partition_count
- days_left (coverage)
"""
# 1. Get Size
size_query = """
SELECT (DATA_LENGTH + INDEX_LENGTH)
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = %s AND TABLE_NAME = %s
"""
size_bytes = self.execute_query(size_query, (self.db_name, table), fetch='one')
# 2. Get Partition Count
count_query = """
SELECT COUNT(*)
FROM information_schema.PARTITIONS
WHERE TABLE_SCHEMA = %s AND TABLE_NAME = %s AND PARTITION_NAME IS NOT NULL
"""
p_count = self.execute_query(count_query, (self.db_name, table), fetch='one')
# 3. Get Days Left
# We need the period first.
partitions_conf = self.config.get('partitions', {})
found_period = None
for period, tables in partitions_conf.items():
for item in tables:
if list(item.keys())[0] == table:
found_period = period
break
if found_period: break
days_left = -1
if found_period:
days_left = self.check_partitions_coverage(table, found_period)
return {
"table": table,
"size_bytes": int(size_bytes) if size_bytes is not None else 0,
"partition_count": int(p_count) if p_count is not None else 0,
"days_left": days_left
}
def run(self, mode: str, target_table: str = None): def run(self, mode: str, target_table: str = None):
"""Main execution loop.""" """Main execution loop."""
with self.connect_db(): with self.connect_db():
@@ -491,36 +607,24 @@ class ZabbixPartitioner:
self.discovery() self.discovery()
return return
# --- Check Mode --- # --- Stats Mode ---
if mode == 'check': if mode == 'stats':
if not target_table: if not target_table:
# Check all and print simple status? Or error? raise ConfigurationError("Target table required for stats mode")
# Zabbix usually queries one by one.
# Implementing simple check which returns days for specific table
raise ConfigurationError("Target table required for check mode")
# Find period for table stats = self.get_table_stats(target_table)
found_period = None print(json.dumps(stats))
for period, tables in partitions_conf.items():
for item in tables:
if list(item.keys())[0] == target_table:
found_period = period
break
if found_period: break
if not found_period:
# Table not in config?
print("-1") # Error code
return
days_left = self.check_partitions_coverage(target_table, found_period)
print(days_left)
return return
# --- Normal Mode (Init/Maintain) --- # --- Normal Mode (Init/Maintain) ---
self.check_compatibility() self.check_compatibility()
premake = self.config.get('premake', 10) premake = self.config.get('premake', 10)
if mode == 'init':
self.clean_housekeeper_table()
else:
self.check_housekeeper_execution()
for period, tables in partitions_conf.items(): for period, tables in partitions_conf.items():
if not tables: if not tables:
continue continue
@@ -529,12 +633,24 @@ class ZabbixPartitioner:
table = list(item.keys())[0] table = list(item.keys())[0]
retention = item[table] retention = item[table]
if not self.validate_table_exists(table):
continue
if mode == 'init': if mode == 'init':
self.initialize_partitioning(table, period, premake, retention) try:
if self.fast_init:
self.initialize_partitioning_fast(table, period, premake, retention)
else:
self.initialize_partitioning_scan(table, period, premake)
except ConfigurationError as e:
raise ConfigurationError(f"Table '{table}': {e}")
else: else:
# Maintenance mode (Add new, remove old) # Maintenance mode (Add new, remove old)
self.create_future_partitions(table, period, premake) try:
self.remove_old_partitions(table, retention) self.create_future_partitions(table, period, premake)
self.remove_old_partitions(table, retention)
except ConfigurationError as e:
raise ConfigurationError(f"Table '{table}': {e}")
# Housekeeping extras # Housekeeping extras
if mode != 'init' and not self.dry_run: if mode != 'init' and not self.dry_run:
@@ -543,6 +659,146 @@ class ZabbixPartitioner:
if mode != 'init' and not self.dry_run: if mode != 'init' and not self.dry_run:
pass pass
def get_retention_input(prompt: str, default: str = None, allow_empty: bool = False) -> str:
"""Helper to get and validate retention period input."""
while True:
val = input(prompt).strip()
# Handle Empty Input
if not val:
if default:
return default
if allow_empty:
return ""
# If no default and not allow_empty, continue loop
continue
# Handle Unit-less Input (Reject)
if val.isdigit():
print(f"Error: '{val}' is missing a unit. Please use 'd', 'w', 'm', or 'y' (e.g., {val}d).")
continue
# Validate Format
if re.search(PART_PERIOD_REGEX, val):
return val
print("Invalid format. Use format like 30d, 12w, 1y.")
def run_wizard():
print("Welcome to Zabbix Partitioning Wizard")
print("-------------------------------------")
config = {
'database': {'type': 'mysql'},
'partitions': {'daily': [], 'weekly': [], 'monthly': []},
'logging': 'console',
'premake': 10,
'replicate_sql': False
}
# 1. Connection
print("\n[Database Connection]")
use_socket = input("Use Socket (s) or Address (a)? [s/a]: ").lower().strip() == 's'
if use_socket:
sock = input("Socket path [/var/run/mysqld/mysqld.sock]: ").strip() or '/var/run/mysqld/mysqld.sock'
config['database']['socket'] = sock
config['database']['host'] = 'localhost' # Fallback
config['database']['port'] = 3306
else:
host = input("Database Host [localhost]: ").strip() or 'localhost'
port_str = input("Database Port [3306]: ").strip() or '3306'
config['database']['host'] = host
config['database']['port'] = int(port_str)
config['database']['user'] = input("Database User [zabbix]: ").strip() or 'zabbix'
config['database']['passwd'] = input("Database Password: ").strip()
config['database']['db'] = input("Database Name [zabbix]: ").strip() or 'zabbix'
# 1.1 SSL
if input("Use SSL/TLS for connection? [y/N]: ").lower().strip() == 'y':
print(" Mode 1: Managed/Cloud DB (Use system CAs, e.g. Azure/AWS)")
print(" Mode 2: Custom Certificates (Provide ca, cert, key)")
if input(" Use custom certificates? [y/N]: ").lower().strip() == 'y':
ssl_conf = {}
ssl_conf['ca'] = input(" CA Certificate Path: ").strip()
ssl_conf['cert'] = input(" Client Certificate Path: ").strip()
ssl_conf['key'] = input(" Client Key Path: ").strip()
# Filter empty
config['database']['ssl'] = {k: v for k, v in ssl_conf.items() if v}
else:
config['database']['ssl'] = 'required'
# 2. Auditlog
print("\n[Auditlog]")
print("Note: To partition 'auditlog', ensure its Primary Key includes the 'clock' column.")
if input("Partition 'auditlog' table? [y/N]: ").lower().strip() == 'y':
ret = get_retention_input("Auditlog retention (e.g. 365d) [365d]: ", "365d")
config['partitions']['weekly'].append({'auditlog': ret})
# 3. History Tables
# History tables list
history_tables = ['history', 'history_uint', 'history_str', 'history_log', 'history_text', 'history_bin']
print("\n[History Tables]")
# Separate logic as requested
if input("Set SAME retention for all history tables? [Y/n]: ").lower().strip() != 'n':
ret = get_retention_input("Retention for all history tables (e.g. 30d) [30d]: ", "30d")
for t in history_tables:
config['partitions']['daily'].append({t: ret})
else:
for t in history_tables:
ret = get_retention_input(f"Retention for '{t}' (e.g. 30d, skip to ignore): ", allow_empty=True)
if ret:
config['partitions']['daily'].append({t: ret})
# 4. Trends Tables
trends_tables = ['trends', 'trends_uint']
print("\n[Trends Tables]")
if input("Set SAME retention for all trends tables? [Y/n]: ").lower().strip() != 'n':
ret = get_retention_input("Retention for all trends tables (e.g. 365d) [365d]: ", "365d")
for t in trends_tables:
config['partitions']['monthly'].append({t: ret})
else:
for t in trends_tables:
ret = get_retention_input(f"Retention for '{t}' (e.g. 365d, skip to ignore): ", allow_empty=True)
if ret:
config['partitions']['monthly'].append({t: ret})
# 5. Replication
print("\n[Replication]")
config['replicate_sql'] = input("Enable binary logging for replication? [y/N]: ").lower().strip() == 'y'
# 6. Premake
print("\n[Premake]")
pm = input("How many future partitions to create? [10]: ").strip()
config['premake'] = int(pm) if pm.isdigit() else 10
# 7. Logging
print("\n[Logging]")
config['logging'] = 'syslog' if input("Log to syslog? [y/N]: ").lower().strip() == 'y' else 'console'
# Save
print("\n[Output]")
path = input("Save config to [/etc/zabbix/zabbix_partitioning.conf]: ").strip() or '/etc/zabbix/zabbix_partitioning.conf'
try:
# Create dir if not exists
folder = os.path.dirname(path)
if folder and not os.path.exists(folder):
try:
os.makedirs(folder)
except OSError:
print(f"Warning: Could not create directory {folder}. Saving to current directory.")
path = 'zabbix_partitioning.conf'
with open(path, 'w') as f:
yaml.dump(config, f, default_flow_style=False)
print(f"\nConfiguration saved to {path}")
except Exception as e:
print(f"Error saving config: {e}")
print(yaml.dump(config)) # dump to stdout if fails
def setup_logging(config_log_type: str, verbose: bool = False): def setup_logging(config_log_type: str, verbose: bool = False):
logger = logging.getLogger('zabbix_partitioning') logger = logging.getLogger('zabbix_partitioning')
logger.setLevel(logging.DEBUG if verbose else logging.INFO) logger.setLevel(logging.DEBUG if verbose else logging.INFO)
@@ -559,16 +815,43 @@ def setup_logging(config_log_type: str, verbose: bool = False):
logger.addHandler(handler) logger.addHandler(handler)
def parse_args(): def parse_args():
parser = argparse.ArgumentParser(description='Zabbix Partitioning Manager') parser = argparse.ArgumentParser(
parser.add_argument('-c', '--config', default='/etc/zabbix/zabbix_partitioning.conf', help='Config file path') description='Zabbix Database Partitioning Script',
parser.add_argument('-i', '--init', action='store_true', help='Initialize partitions') epilog='''
parser.add_argument('-r', '--dry-run', action='store_true', help='Simulate queries') Examples:
parser.add_argument('-v', '--verbose', action='store_true', help='Enable debug logging') # 1. Interactive Configuration (Initial config file creation)
%(prog)s --wizard
# Monitoring args # 2. Initialization (First Run)
parser.add_argument('--discovery', action='store_true', help='Output Zabbix LLD JSON') # A. Standard (Scans DB for oldest record - Recommended):
parser.add_argument('--check-days', type=str, help='Check days of future partitions left for table', metavar='TABLE') %(prog)s --init
parser.add_argument('-V', '--version', action='version', version=f'%(prog)s {VERSION}', help='Show version and exit')
# B. Fast (Start from retention period - Best for large DBs):
%(prog)s --fast-init
# 3. Regular Maintenance (Cron/Systemd)
# Creates future partitions and drops old ones.
%(prog)s
# 4. Monitoring (Zabbix Integration)
# Discovery (LLD): %(prog)s --discovery
# Statistics (JSON): %(prog)s --stats history
''',
formatter_class=argparse.RawDescriptionHelpFormatter
)
parser.add_argument('--config','-c', default='/etc/zabbix/zabbix_partitioning.conf', help='Path to configuration file')
# Mutually Exclusive Actions
group = parser.add_mutually_exclusive_group()
group.add_argument('--init', '-i', action='store_true', help='Initialize partitions (Standard: Scans DB for oldest record)')
group.add_argument('--fast-init', '-f', action='store_true', help='Initialize partitions (Fast: Starts FROM retention period, skips scan)')
group.add_argument('--discovery', '-d', action='store_true', help='Output Zabbix Low-Level Discovery (LLD) JSON')
group.add_argument('--stats', '-s', type=str, help='Output table statistics (Size, Count, Usage) in JSON', metavar='TABLE')
group.add_argument('--wizard', '-w', action='store_true', help='Launch interactive configuration wizard')
parser.add_argument('--version', '-V', action='version', version=f'%(prog)s {VERSION}', help='Show version and exit')
parser.add_argument('--verbose', '-vv', action='store_true', help='Enable debug logging')
parser.add_argument('--dry-run', '-r', action='store_true', help='Simulate queries without executing them')
return parser.parse_args() return parser.parse_args()
@@ -584,6 +867,10 @@ def main():
args = parse_args() args = parse_args()
try: try:
if args.wizard:
run_wizard()
return
conf_path = load_config(args.config) conf_path = load_config(args.config)
with open(conf_path, 'r') as f: with open(conf_path, 'r') as f:
config = yaml.safe_load(f) config = yaml.safe_load(f)
@@ -599,13 +886,16 @@ def main():
config['logging'] = 'console' # Force console for discovery? Or suppress? config['logging'] = 'console' # Force console for discovery? Or suppress?
# actually we don't want logs mixing with JSON output # actually we don't want logs mixing with JSON output
# so checking mode before setup logging # so checking mode before setup logging
elif args.check_days: elif args.stats:
mode = 'check' mode = 'stats'
target = args.check_days target = args.stats
elif args.init: mode = 'init' elif args.init:
mode = 'init'
elif args.fast_init:
mode = 'init'
# Setup logging # Setup logging
if mode in ['discovery', 'check']: if mode in ['discovery', 'stats']:
logging.basicConfig(level=logging.ERROR) # Only show critical errors logging.basicConfig(level=logging.ERROR) # Only show critical errors
else: else:
setup_logging(config.get('logging', 'console'), verbose=args.verbose) setup_logging(config.get('logging', 'console'), verbose=args.verbose)
@@ -616,7 +906,7 @@ def main():
logger.info("Starting in DRY-RUN mode") logger.info("Starting in DRY-RUN mode")
# ZabbixPartitioner expects dict config # ZabbixPartitioner expects dict config
app = ZabbixPartitioner(config, dry_run=args.dry_run) app = ZabbixPartitioner(config, dry_run=args.dry_run, fast_init=args.fast_init)
app.run(mode, target) app.run(mode, target)
except Exception as e: except Exception as e:

View File

@@ -0,0 +1,129 @@
zabbix_export:
version: '7.0'
template_groups:
- uuid: e29f7cbf75cf41cb81078cb4c10d584a
name: Templates/Databases
templates:
- uuid: 69899eb3126b4c62b70351f305b69dd9
template: 'Zabbix Partitioning Monitor'
name: 'Zabbix Partitioning Monitor'
description: |
Monitor Zabbix Database Partitioning.
Prerequisites:
1. Install zabbix_partitioning.py on the Zabbix Server/Proxy.
2. Configure userparameter for automatic discovery:
UserParameter=zabbix.partitioning.discovery[*], /usr/local/bin/zabbix_partitioning.py -c $1 --discovery
UserParameter=zabbix.partitioning.discovery[*], /usr/local/bin/zabbix_partitioning.py -c $1 --discovery
UserParameter=zabbix.partitioning.stats[*], /usr/local/bin/zabbix_partitioning.py -c $1 --stats $2
# Legacy check removed in favor of stats
Or use Docker wrapper scripts.
groups:
- name: Templates/Databases
items:
- uuid: bc753e750cc2485f917ba1f023c87d05
name: 'Partitioning Last Run Status'
type: ZABBIX_ACTIVE
key: 'log[/var/log/syslog,zabbix_partitioning]'
history: 7d
value_type: LOG
trends: '0'
triggers:
- uuid: 25497978dbb943e49dac8f3b9db91c29
expression: 'find(/Zabbix Partitioning Monitor/log[/var/log/syslog,zabbix_partitioning],,"like","Failed")=1'
name: 'Partitioning Script Failed'
priority: HIGH
description: 'The partitioning script reported a failure.'
tags:
- tag: services
value: database
discovery_rules:
- uuid: 097c96467035468a80ce5c519b0297bb
name: 'Partitioning Discovery'
key: 'zabbix.partitioning.discovery[{$PATH.TO.CONFIG}]'
delay: 1d
description: 'Discover partitioned tables'
item_prototypes:
- uuid: 1fbff85191c244dca956be7a94bf08a3
name: 'Partitioning Stats: {#TABLE}'
key: 'zabbix.partitioning.stats[{$PATH.TO.CONFIG}, {#TABLE}]'
delay: 12h
history: '0'
trends: '0'
value_type: TEXT
description: 'JSON statistics for table {#TABLE}'
tags:
- tag: component
value: partitioning
- tag: table
value: '{#TABLE}'
- uuid: a8371234567890abcdef1234567890ab
name: 'Days remaining: {#TABLE}'
type: DEPENDENT
key: 'zabbix.partitioning.days_left[{#TABLE}]'
delay: 0
history: 7d
description: 'Days until the last partition runs out for {#TABLE}'
master_item:
key: 'zabbix.partitioning.stats[{$PATH.TO.CONFIG}, {#TABLE}]'
preprocessing:
- type: JSONPATH
parameters:
- $.days_left
tags:
- tag: component
value: partitioning
- tag: table
value: '{#TABLE}'
trigger_prototypes:
- uuid: da23fae76a41455c86c58267d6d9f86d
expression: 'last(/Zabbix Partitioning Monitor/zabbix.partitioning.days_left[{#TABLE}])<={$PARTITION.DAYS}'
name: 'Partitioning critical: {#TABLE} has less than {$PARTITION.DAYS} days in partition'
opdata: 'Days till Zabbix server will crash: {ITEM.LASTVALUE}'
priority: DISASTER
description: 'New partitions are not being created. Check the script logs.'
- uuid: b9482345678901bcdef23456789012cd
name: 'Partition Count: {#TABLE}'
type: DEPENDENT
key: 'zabbix.partitioning.count[{#TABLE}]'
delay: 0
history: 7d
description: 'Total number of partitions for {#TABLE}'
master_item:
key: 'zabbix.partitioning.stats[{$PATH.TO.CONFIG}, {#TABLE}]'
preprocessing:
- type: JSONPATH
parameters:
- $.partition_count
tags:
- tag: component
value: partitioning
- tag: table
value: '{#TABLE}'
- uuid: c0593456789012cdef345678901234de
name: 'Table Size: {#TABLE}'
type: DEPENDENT
key: 'zabbix.partitioning.size[{#TABLE}]'
delay: 0
history: 7d
units: B
description: 'Total size (Data+Index) of {#TABLE}'
master_item:
key: 'zabbix.partitioning.stats[{$PATH.TO.CONFIG}, {#TABLE}]'
preprocessing:
- type: JSONPATH
parameters:
- $.size_bytes
tags:
- tag: component
value: partitioning
- tag: table
value: '{#TABLE}'
macros:
- macro: '{$PARTITION.DAYS}'
value: '3'
- macro: '{$PATH.TO.CONFIG}'
value: /etc/zabbix/scripts/zabbix_partitioning.conf

View File

@@ -1,65 +0,0 @@
zabbix_export:
version: '7.0'
template_groups:
- uuid: e29f7cbf75cf41cb81078cb4c10d584a
name: 'Templates/Databases'
templates:
- uuid: 69899eb3126b4c62b70351f305b69dd9
template: 'Zabbix Partitioning Monitor'
name: 'Zabbix Partitioning Monitor'
description: |
Monitor Zabbix Database Partitioning.
Prerequisites:
1. Install zabbix_partitioning.py on the Zabbix Server/Proxy.
2. Configure userparameter for automatic discovery:
UserParameter=zabbix.partitioning.discovery[*], /usr/local/bin/zabbix_partitioning.py -c $1 --discovery
UserParameter=zabbix.partitioning.check[*], /usr/local/bin/zabbix_partitioning.py -c $1 --check-days $2
Or use Docker wrapper scripts.
groups:
- name: 'Templates/Databases'
items:
- uuid: bc753e750cc2485f917ba1f023c87d05
name: 'Partitioning Last Run Status'
type: TRAP
key: partitioning.run.status
delay: 0
history: 7d
trends: '0'
value_type: TEXT
description: 'Send "Success" or "Failed" via zabbix_sender or check log file'
triggers:
- uuid: 25497978dbb943e49dac8f3b9db91c29
expression: 'find(/Zabbix Partitioning Monitor/partitioning.run.status,,"like","Failed")=1'
name: 'Zabbix Partitioning Failed'
priority: HIGH
description: 'The partitioning script reported a failure.'
tags:
- tag: services
value: database
discovery_rules:
- uuid: 097c96467035468a80ce5c519b0297bb
name: 'Partitioning Discovery'
key: 'zabbix.partitioning.discovery[/etc/zabbix/zabbix_partitioning.conf]'
delay: 1h
description: 'Discover partitioned tables'
item_prototypes:
- uuid: 1fbff85191c244dca956be7a94bf08a3
name: 'Partitions remaining: {#TABLE}'
key: 'zabbix.partitioning.check[/etc/zabbix/zabbix_partitioning.conf, {#TABLE}]'
delay: 12h
history: 7d
description: 'Days until the last partition runs out for {#TABLE}'
tags:
- tag: component
value: partitioning
- tag: table
value: '{#TABLE}'
trigger_prototypes:
- uuid: da23fae76a41455c86c58267d6d9f86d
expression: 'last(/Zabbix Partitioning Monitor/zabbix.partitioning.check[/etc/zabbix/zabbix_partitioning.conf, {#TABLE}])<=3'
name: 'Partitioning critical: {#TABLE} has less than 3 days of partitions'
priority: HIGH
description: 'New partitions are not being created. Check the script logs.'