# MySQL/MariaDB Point-in-Time Recovery (PITR)

This guide explains how to use dbbackup for Point-in-Time Recovery with MySQL and MariaDB databases.

## Overview

Point-in-Time Recovery (PITR) allows you to restore your database to any specific moment in time, not just to when a backup was taken. This is essential for:

- Recovering from accidental data deletion or corruption
- Restoring to a state just before a problematic change
- Meeting regulatory compliance requirements for data recovery

### How MySQL PITR Works

MySQL PITR uses binary logs (binlogs) which record all changes to the database:

1. **Base Backup**: A full database backup with the binlog position recorded
2. **Binary Log Archiving**: Continuous archiving of binlog files
3. **Recovery**: Restore base backup, then replay binlogs up to the target time

```
┌─────────────┐     ┌──────────────┐     ┌──────────────┐     ┌──────────────┐
│ Base Backup │ --> │ binlog.00001 │ --> │ binlog.00002 │ --> │ binlog.00003 │
│ (pos: 1234) │     │              │     │              │     │  (current)   │
└─────────────┘     └──────────────┘     └──────────────┘     └──────────────┘
       │                   │                    │                    │
       ▼                   ▼                    ▼                    ▼
   10:00 AM            10:30 AM             11:00 AM            11:30 AM
                                                 ↑
                                          Target: 11:15 AM
```

## Prerequisites

### MySQL Configuration

Binary logging must be enabled in MySQL. Add to `my.cnf`:

```ini
[mysqld]
# Enable binary logging
log_bin = mysql-bin
server_id = 1

# Recommended: Use ROW format for PITR
binlog_format = ROW

# Optional but recommended: Enable GTID for easier replication and recovery
gtid_mode = ON
enforce_gtid_consistency = ON

# Keep binlogs for at least 7 days (adjust as needed)
expire_logs_days = 7
# Or for MySQL 8.0+:
# binlog_expire_logs_seconds = 604800
```

After changing configuration, restart MySQL:
```bash
sudo systemctl restart mysql
```

### MariaDB Configuration

MariaDB configuration is similar:

```ini
[mysqld]
log_bin = mariadb-bin
server_id = 1
binlog_format = ROW

# MariaDB uses different GTID implementation (auto-enabled with log_slave_updates)
log_slave_updates = ON
```

## Quick Start

### 1. Check PITR Status

```bash
# Check if MySQL is properly configured for PITR
dbbackup pitr mysql-status
```

Example output:
```
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  MySQL/MariaDB PITR Status (mysql)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

PITR Status:     ❌ NOT CONFIGURED
Binary Logging:  ✅ ENABLED
Binlog Format:   ROW
GTID Mode:       ON
Current Position: mysql-bin.000042:1234

PITR Requirements:
  ✅ Binary logging enabled
  ✅ Row-based logging (recommended)
```

### 2. Enable PITR

```bash
# Enable PITR and configure archive directory
dbbackup pitr mysql-enable --archive-dir /backups/binlog_archive
```

### 3. Create a Base Backup

```bash
# Create a backup - binlog position is automatically recorded
dbbackup backup single mydb
```

> **Note:** All backups automatically capture the current binlog position when PITR is enabled at the MySQL level. This position is stored in the backup metadata and used as the starting point for binlog replay during recovery.

### 4. Start Binlog Archiving

```bash
# Run binlog archiver in the background
dbbackup binlog watch --binlog-dir /var/lib/mysql --archive-dir /backups/binlog_archive --interval 30s
```

Or set up a cron job for periodic archiving:
```bash
# Archive new binlogs every 5 minutes
*/5 * * * * dbbackup binlog archive --binlog-dir /var/lib/mysql --archive-dir /backups/binlog_archive
```

### 5. Restore to Point in Time

```bash
# Restore to a specific time
dbbackup restore pitr mydb_backup.sql.gz --target-time '2024-01-15 14:30:00'
```

## Commands Reference

### PITR Commands

#### `pitr mysql-status`
Show MySQL/MariaDB PITR configuration and status.

```bash
dbbackup pitr mysql-status
```

#### `pitr mysql-enable`
Enable PITR for MySQL/MariaDB.

```bash
dbbackup pitr mysql-enable \
  --archive-dir /backups/binlog_archive \
  --retention-days 7 \
  --require-row-format \
  --require-gtid
```

Options:
- `--archive-dir`: Directory to store archived binlogs (required)
- `--retention-days`: Days to keep archived binlogs (default: 7)
- `--require-row-format`: Require ROW binlog format (default: true)
- `--require-gtid`: Require GTID mode enabled (default: false)

### Binlog Commands

#### `binlog list`
List available binary log files.

```bash
# List binlogs from MySQL data directory
dbbackup binlog list --binlog-dir /var/lib/mysql

# List archived binlogs
dbbackup binlog list --archive-dir /backups/binlog_archive
```

#### `binlog archive`
Archive binary log files.

```bash
dbbackup binlog archive \
  --binlog-dir /var/lib/mysql \
  --archive-dir /backups/binlog_archive \
  --compress
```

Options:
- `--binlog-dir`: MySQL binary log directory
- `--archive-dir`: Destination for archived binlogs (required)
- `--compress`: Compress archived binlogs with gzip
- `--encrypt`: Encrypt archived binlogs
- `--encryption-key-file`: Path to encryption key file

#### `binlog watch`
Continuously monitor and archive new binlog files.

```bash
dbbackup binlog watch \
  --binlog-dir /var/lib/mysql \
  --archive-dir /backups/binlog_archive \
  --interval 30s \
  --compress
```

Options:
- `--interval`: How often to check for new binlogs (default: 30s)

#### `binlog validate`
Validate binlog chain integrity.

```bash
dbbackup binlog validate --binlog-dir /var/lib/mysql
```

Output shows:
- Whether the chain is complete (no missing files)
- Any gaps in the sequence
- Server ID changes (indicating possible failover)
- Total size and file count

#### `binlog position`
Show current binary log position.

```bash
dbbackup binlog position
```

Output:
```
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  Current Binary Log Position
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

File:     mysql-bin.000042
Position: 123456
GTID Set: 3E11FA47-71CA-11E1-9E33-C80AA9429562:1-1000

Position String: mysql-bin.000042:123456
```

## Restore Scenarios

### Restore to Specific Time

```bash
# Restore to January 15, 2024 at 2:30 PM
dbbackup restore pitr mydb_backup.sql.gz \
  --target-time '2024-01-15 14:30:00'
```

### Restore to Specific Position

```bash
# Restore to a specific binlog position
dbbackup restore pitr mydb_backup.sql.gz \
  --target-position 'mysql-bin.000042:12345'
```

### Dry Run (Preview)

```bash
# See what SQL would be replayed without applying
dbbackup restore pitr mydb_backup.sql.gz \
  --target-time '2024-01-15 14:30:00' \
  --dry-run
```

### Restore to Backup Point Only

```bash
# Restore just the base backup without replaying binlogs
dbbackup restore pitr mydb_backup.sql.gz --immediate
```

## Best Practices

### 1. Archiving Strategy

- Archive binlogs frequently (every 5-30 minutes)
- Use compression to save disk space
- Store archives on separate storage from the database

### 2. Retention Policy

- Keep archives for at least as long as your oldest valid base backup
- Consider regulatory requirements for data retention
- Use the cleanup command to purge old archives:

```bash
dbbackup binlog cleanup --archive-dir /backups/binlog_archive --retention-days 30
```

### 3. Validation

- Regularly validate your binlog chain:
```bash
dbbackup binlog validate --binlog-dir /var/lib/mysql
```

- Test restoration periodically on a test environment

### 4. Monitoring

- Monitor the `dbbackup binlog watch` process
- Set up alerts for:
  - Binlog archiver failures
  - Gaps in binlog chain
  - Low disk space on archive directory

### 5. GTID Mode

Enable GTID for:
- Easier tracking of replication position
- Automatic failover in replication setups
- Simpler point-in-time recovery

## Troubleshooting

### Binary Logging Not Enabled

**Error**: "Binary logging appears to be disabled"

**Solution**: Add to my.cnf and restart MySQL:
```ini
[mysqld]
log_bin = mysql-bin
server_id = 1
```

### Missing Binlog Files

**Error**: "Gaps detected in binlog chain"

**Causes**:
- `RESET MASTER` was executed
- `expire_logs_days` is too short
- Binlogs were manually deleted

**Solution**:
- Take a new base backup immediately
- Adjust retention settings to prevent future gaps

### Permission Denied

**Error**: "Failed to read binlog directory"

**Solution**:
```bash
# Add dbbackup user to mysql group
sudo usermod -aG mysql dbbackup_user

# Or set appropriate permissions
sudo chmod g+r /var/lib/mysql/mysql-bin.*
```

### Wrong Binlog Format

**Warning**: "binlog_format = STATEMENT (ROW recommended)"

**Impact**: STATEMENT format may not capture all changes accurately

**Solution**: Change to ROW format (requires restart):
```ini
[mysqld]
binlog_format = ROW
```

### Server ID Changes

**Warning**: "server_id changed from X to Y (possible master failover)"

This warning indicates the binlog chain contains events from different servers, which may happen during:
- Failover in a replication setup
- Restoring from a different server's backup

This is usually informational but review your topology if unexpected.

## MariaDB-Specific Notes

### GTID Format

MariaDB uses a different GTID format than MySQL:
- **MySQL**: `3E11FA47-71CA-11E1-9E33-C80AA9429562:1-5`
- **MariaDB**: `0-1-100` (domain-server_id-sequence)

### Tool Detection

dbbackup automatically detects MariaDB and uses:
- `mariadb-binlog` if available (MariaDB 10.4+)
- Falls back to `mysqlbinlog` for older versions

### Encrypted Binlogs

MariaDB supports binlog encryption. If enabled, ensure the key is available during archive and restore operations.

## See Also

- [PITR.md](PITR.md) - PostgreSQL PITR documentation
- [DOCKER.md](DOCKER.md) - Running in Docker environments
- [CLOUD.md](CLOUD.md) - Cloud storage for archives
