Compare commits
9 Commits
060c5cd2db
...
test
| Author | SHA1 | Date | |
|---|---|---|---|
| 094debc46c | |||
| d2de7f8b02 | |||
| 3b2bebf6b9 | |||
| 059aa81c5c | |||
| 08ba77cdf0 | |||
| bcd2bd627e | |||
| 9e53259e61 | |||
| a61f2bdf30 | |||
| 66b1cc036b |
@@ -5,6 +5,37 @@ 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
|
||||||
|
### Added
|
||||||
|
- **CLI**: Added `--verbose` / `-v` flag to switch between INFO (default) and DEBUG logging levels.
|
||||||
|
- **CLI**: Added `-r` short flag for `--dry-run`.
|
||||||
|
|
||||||
## [0.4.0] - 2025-12-16
|
## [0.4.0] - 2025-12-16
|
||||||
### Added
|
### Added
|
||||||
- **Monitoring**: Added `--discovery` argument for Zabbix Low-Level Discovery (LLD) of partitioned tables.
|
- **Monitoring**: Added `--discovery` argument for Zabbix Low-Level Discovery (LLD) of partitioned tables.
|
||||||
@@ -19,10 +50,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
## [0.3.0] - 2025-12-14
|
## [0.3.0] - 2025-12-14
|
||||||
### Changed
|
### Changed
|
||||||
- **Refactor**: Complete rewrite of `zabbix_partitioning.py` using Class-based structure (`ZabbixPartitioner`).
|
- **Refactor**: Complete rewrite of `zabbix_partitioning.py` using Class-based structure (`ZabbixPartitioner`).
|
||||||
- **Configuration**: Switched to YAML configuration file (`zabbix_partitioning.conf`).
|
- **Configuration**: Extended comments in the configuration file (`zabbix_partitioning.conf`). The config file is self-explanatory now.
|
||||||
- **Safety**: Added checks to prevent partitioning of tables incompatible with Zabbix 7.0 schema (e.g., `auditlog` without `clock` in PK).
|
- **Docker**: Introduced Docker container support (`Dockerfile`, `entrypoint.py`). The script can be run in a stateless manner using Docker.
|
||||||
- **Docker**: Introduced Docker container support (`Dockerfile`, `entrypoint.py`).
|
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- **Optimization**: Added `initial_partitioning_start` option (`db_min` vs `retention`) to speed up initialization on large DBs.
|
- **Optimization**: Added `initial_partitioning_start` option (`db_min` vs `retention`) to speed up initialization on large DBs.
|
||||||
- **Reliability**: Use `pymysql` with robust connection handling and SSL support.
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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.0'
|
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,9 +659,149 @@ class ZabbixPartitioner:
|
|||||||
if mode != 'init' and not self.dry_run:
|
if mode != 'init' and not self.dry_run:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def setup_logging(config_log_type: str):
|
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):
|
||||||
logger = logging.getLogger('zabbix_partitioning')
|
logger = logging.getLogger('zabbix_partitioning')
|
||||||
logger.setLevel(logging.DEBUG)
|
logger.setLevel(logging.DEBUG if verbose else logging.INFO)
|
||||||
|
|
||||||
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
|
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
|
||||||
|
|
||||||
@@ -559,15 +815,43 @@ def setup_logging(config_log_type: str):
|
|||||||
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('--dry-run', action='store_true', help='Simulate queries')
|
Examples:
|
||||||
|
# 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()
|
||||||
|
|
||||||
@@ -583,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)
|
||||||
@@ -598,18 +886,19 @@ 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 discovery or check, we mute info logs to stdout to keep output clean,
|
if mode in ['discovery', 'stats']:
|
||||||
# unless errors happen.
|
|
||||||
if mode in ['discovery', 'check']:
|
|
||||||
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'))
|
setup_logging(config.get('logging', 'console'), verbose=args.verbose)
|
||||||
|
|
||||||
logger = logging.getLogger('zabbix_partitioning')
|
logger = logging.getLogger('zabbix_partitioning')
|
||||||
|
|
||||||
@@ -617,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:
|
||||||
|
|||||||
129
partitioning/zabbix_mysql_partitioning_template.yaml
Normal file
129
partitioning/zabbix_mysql_partitioning_template.yaml
Normal 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
|
||||||
@@ -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.'
|
|
||||||
Reference in New Issue
Block a user