Author: Heitor Faria Contact: heitor@opentechs.lat | +1 786 726-1749 | +55 61 98268-4220 (WhatsApp) Version: 1.0.0 — General Availability | Date: 2026-04-28 License: Copyright © 2026 Heitor Faria. All rights reserved.
COMMERCIAL NOTICE
Trazendo sua proposta de contratação ou renovação do Bacula Enterprise, Veeam, Commvault ou Netbackup? Oferecemos no mínimo 50% de desconto, com muito mais funcionalidades.
heitor@opentechs.lat | +1 786 726-1749 | +55 61 98268-4220 (WhatsApp)
Table of Contents
- Executive Summary
- Business Case
- Architecture Overview
- Backup Engines Deep Dive
- Replication Capabilities
- Security & Compliance
- Observability
- Installation Guide
- Configuration Reference
- Restore Procedures & Use Cases
- Performance Reports
- Compatibility Matrix
- Sizing Guide
- Bacularis Integration
- Migration from Bacula Enterprise
- Troubleshooting
- Roadmap
- Validation Evidence
1. Executive Summary
The PodHeitor Microsoft SQL Backup and Replication Plugin for Bacula is a production-hardened, commercially supported SQL Server data protection solution built on top of Bacula Community. It fills a critical market gap: enterprises that run Microsoft SQL Server and want Bacula Community’s proven storage architecture without the cost and limitations of Bacula Enterprise or proprietary alternatives.
The Problem
SQL Server database administrators face a difficult trade-off:
- Bacula Enterprise
mssql-fd.dllworks on Windows but costs significantly more, is limited to Windows, has no parallelism, no compression, no replication, and no observability. - Veeam, Commvault, and NetBackup SQL agents are expensive, often require separate infrastructure, and lock customers into proprietary storage formats.
- Native SQL Server backup (
BACKUP DATABASE TO DISK) has no centralized management, no retention policies, and no integration with enterprise storage.
The Solution
PodHeitor MSSQL delivers:
| Capability | Business Value |
|---|---|
| Feature parity + extensions vs. Enterprise | Same investment, dramatically more features |
| Linux SQL Server support | Protects the rapidly growing SQL Server on Linux footprint |
| 4× throughput via striped I/O | Faster backup windows, lower RPO |
| 3 native replication modes | DR without AG licensing overhead |
| Instant recovery | RTO measured in minutes, not hours |
| Drop-in Enterprise migration | Zero-downtime switch, no catalog changes |
| Prometheus + OTel observability | Grafana dashboards, alerting, SLA monitoring |
| TDE-aware | Cross-server encrypted database restore |
Validation (v1.0.0 GA)
E2E Test Suite (T01–T13, 16 scenarios): 16 / 16 PASS (3 consecutive runs)
OL9 unit/integration tests: 311 passed, 0 failed
Win2025 unit/integration tests: 381 passed, 0 failed
SQL Server tested: 2022 on Windows Server 2025
Always On AG: 2-node, CLUSTER_TYPE=NONE, SYNCHRONOUS_COMMIT
Bacula version: Community 15.0.3
Installed packages tested: RPM v1.0.0 (OL9) + zip v1.0.0 (Win2025)
2. Business Case
2.1 Total Cost of Ownership Comparison
| Solution | Relative Cost | SQL Linux | Replication | Observability | Max Throughput |
|---|---|---|---|---|---|
| Bacula Enterprise MSSQL plugin | $$$ | No | No | No | 1× |
| Veeam for SQL Server | $$$$ | Limited | Limited | Basic | 1–2× |
| Commvault for SQL Server | $$$$$ | Yes | Yes | Yes | 1–2× |
| PodHeitor MSSQL | $ | Yes | Yes (3 modes) | Yes (full) | 4× |
2.2 Key Differentiators
Linux SQL Server — SQL Server 2017+ on Linux is now mainstream. PodHeitor uses a native TDS/FIFO engine on Linux — no Windows agent proxy, no SMB shares, no complexity.
Striped Parallel I/O — A 1 TB database backed up in 180 minutes with a single stream finishes in 42 minutes with 8 stripes. For VLDB environments, this is the difference between meeting and missing backup windows.
Native Replication via Bacula — Bacula volumes are already durable, encrypted, offsite-replicated, and retention-managed. The PodHeitor replication engine uses them as the WAL transport between primary and secondary SQL instances — no separate replication infrastructure.
Instant Recovery — Instead of waiting hours for a 2 TB database to fully restore, mode=instant makes the database queryable within minutes by mounting the backup stream with WITH MOVE. The full restore continues in the background.
Drop-In Migration — Byte-identical namespace (@mssql/<INSTANCE>/<db>/data.bak) means existing Enterprise backups are restorable by PodHeitor and vice versa. Change one line in bacula-fd.conf, run one Full, done.
2.3 When to Choose PodHeitor
- You run Bacula Community and need a robust SQL Server backup solution
- You’re paying for Bacula Enterprise and want to reduce costs without losing features
- You run SQL Server on Linux and need a native backup agent
- You have VLDB (>500 GB) and need faster backup windows
- You need DR without AG Enterprise Edition licensing complexity
- You run Always On AG and want backup preference + automatic secondary seeding
3. Architecture Overview
3.1 Two-Tier Design
┌─────────────────────────────────────────────────────────────────────┐
│ Bacula File Daemon │
│ │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ podheitor-mssql-fd.{dll,so} (pure Rust cdylib, v2.0.0+) │ │
│ │ - Built from ../PodHeitor Rust cdylib/crates/plugin-mssql/ │ │
│ │ - No Bacula AGPLv3 source statically linked │ │
│ │ - Implements Bacula FD plugin ABI v4 via metaplugin-rs │ │
│ │ - Spawns Rust backend as a subprocess │ │
│ │ - Proxies all I/O through PTCOMM binary protocol │ │
│ └─────────────────────────┬─────────────────────────────────────┘ │
└────────────────────────────│────────────────────────────────────────┘
│ stdin/stdout (PTCOMM binary frames)
│ stream IDs for parallel stripes
▼
┌─────────────────────────────────────────────────────────────────────┐
│ podheitor-mssql-backend (Rust binary) │
│ │
│ Engine Layer: vdi | tds_fifo | tds_url | snapshot │
│ Replication: logshipping | ag_bootstrap | fanout │
│ Orchestration: catalog | parallel striper | chain/integrity │
│ BI Servers: SSAS (XMLA) | SSRS | SSIS (SSISDB) │
│ Security: TDE capture/restore | Always Encrypted | ARD │
│ Observability: Prometheus /metrics | OpenTelemetry OTLP │
│ Cross-cutting: PTCOMM codec | Config | Logging | Compression │
└────────────────────────────┬────────────────────────────────────────┘
│ TDS / ODBC / COM VDI
▼
┌─────────────────────────────────────────────────────────────────────┐
│ Microsoft SQL Server (Windows / Linux / Azure MI) │
│ msdb — LSN chain, backupset | sys.availability_* — AG topology │
└─────────────────────────────────────────────────────────────────────┘
3.2 PTCOMM Protocol
PTCOMM is a lightweight binary framing protocol — the communication layer between the in-FD Rust cdylib loader and the standalone Rust backend subprocess:
| Frame | Byte | Purpose |
|---|---|---|
| Data | D |
Backup data chunk (with stream ID for N stripes) |
| Command | C |
Operation request |
| File | F |
File start/end boundary |
| Info | I |
Progress message to Bacula job log |
| Error | E |
Fatal — job abort |
| Warning | W |
Non-fatal warning |
| Terminate | T |
Normal session end |
Stream IDs allow N parallel stripes to be multiplexed over a single stdin/stdout pair. Bacula FD sees N separate files.
3.3 Engine Auto-Selection
mode=auto (default)
├── Windows? → mode=vdi (IClientVirtualDevice COM API)
├── Linux? → mode=tds_fifo (TDS + POSIX FIFO)
└── Azure MI? → mode=tds_url (BACKUP TO URL embedded S3)
Explicit override: mode=snapshot | mode=replicate_logshipping | ...
3.4 State Model
Working directory state (/opt/bacula/working/mssql-state/):
- LSN chain cache — last applied LSN per database (differential/log health)
- Follower state — replication last-applied-LSN per database
- Backup manifest — database layout, compression, stripe count for restore
- Recovery tokens — job resume after interruption
Total footprint: < 10 MB per SQL instance. Zero staging — no .bak files ever written to disk.
4. Backup Engines Deep Dive
4.1 VDI Engine (Windows)
Technology: Microsoft Virtual Device Interface — IClientVirtualDevice COM API (sqlvdi.dll), implemented via hand-written windows-sys COM vtables in Rust.
Threading model (per database, per stripe):
Thread A: ODBC → BACKUP DATABASE [db] TO VIRTUAL_DEVICE='...' (blocks on SQL I/O)
Thread B: VDI pump → GetCommand() → Write/Flush → CompleteCommand (pulls buffers)
Thread C: PTCOMM encoder → bounded channel(8) back-pressure → stdout → Bacula
Memory ceiling: stripes × parallel_dbs × 8 × maxtransfersize
- Default (1 stripe, 1 DB, 64 KB): 512 KB
- VLDB (8 stripes, 4 DBs, 4 MB): 1 GB — tune
maxtransfersizeaccordingly
Backup sequence:
- ODBC connect + database catalog (include/exclude/recovery model filters)
CreateEx(N devices)for stripe count- Issue
BACKUP DATABASE WITH BUFFERCOUNT, BLOCKSIZE, MAXTRANSFERSIZE, COMPRESSION, CHECKSUM, [DIFFERENTIAL|COPY_ONLY] - VDI pump → PTCOMM stream → Bacula FD
- Query
msdb.backupsetfor LSN (FirstLSN, LastLSN) after completion - Emit
__ph_manifest.jsonsidecar (LSN chain, compression stats)
4.2 TDS/FIFO Engine (Linux)
Technology: tiberius (pure-Rust async TDS 7.x client) + POSIX named pipes.
Flow:
tokio runtime
├── TDS session: BACKUP DATABASE [db] TO DISK='/fifo/job-0.fifo', '/fifo/job-1.fifo'...
├── Async FIFO reader [stripe 0] → decode → PTCOMM stream 0 → Bacula
├── Async FIFO reader [stripe 1] → decode → PTCOMM stream 1 → Bacula
└── ...
SQL Server writes to FIFOs concurrently on its I/O threads
Plugin readers drain asynchronously → near-zero kernel overhead
FIFO lifecycle: Created mode 0600 in per-job directory. Cleaned up on exit and SIGTERM. tmpfs-backed when available.
4.3 TDS URL Engine (Azure MI / SQL Server 2022)
Embedded Hyper 1.x HTTPS server → CREATE CREDENTIAL + BACKUP TO URL='https://127.0.0.1:<port>/...' → SQL PUT-streams → demux → PTCOMM. Credential dropped after job. Works for Azure SQL MI (no filesystem) and SQL 2022 S3 integration.
4.4 Snapshot Engines
VSS (Windows): IVssBackupComponents → SQL writer quiesce → DoSnapshotSet → mount read-only → stream MDF/NDF/LDF → DeleteSnapshot.
LVM (Linux): SQL quiesce → lvcreate --snapshot → mount read-only → stream files → lvremove.
ZFS: SQL quiesce → zfs snapshot → access .zfs/snapshot/ → stream files → zfs destroy.
NetApp ONTAP: Deferred to v1.1 — currently returns actionable error with v1.1 ETA.
5. Replication Capabilities
PodHeitor implements three SQL Server replication modes using Bacula volumes as the WAL transport. No separate replication infrastructure required.
5.1 Log Shipping
PRIMARY SQL (primary-fd plugin)
→ BACKUP LOG → Bacula volume (every 5 min Incremental job)
SECONDARY SQL (follower-fd plugin)
→ bconsole query: find latest log job for target DB
→ Download log backup
→ RESTORE LOG [db] WITH STANDBY='/standby_undo.bak'
→ DB online read-only (STANDBY mode)
RPO: 30 sec – 15 min. RTO: Minutes (secondary always in STANDBY/NORECOVERY).
5.2 AG Bootstrap
Provisions new AG replica from Bacula chain without touching the primary: Full → Diff → Logs (WITH NORECOVERY) → ALTER DATABASE SET HADR AVAILABILITY GROUP.
5.3 Fanout (1→N Regions)
Single primary backup chain consumed by N secondary FD instances (São Paulo → US East → EU West). Zero extra SQL load on primary. Convergence within 2× log interval.
5.4 Follower State Machine
Persistent state ensures idempotency (re-applying an applied log is a no-op), gap detection (triggers alert + fallback to Full reseed), and crash recovery (resumes from last applied LSN).
6. Security & Compliance
6.1 Credential Handling
# Recommended: passfile (file permissions enforced)
passfile = /opt/bacula/etc/.mssql_pass # mode 0640, warn if world-readable
# Inline (auto-redacted everywhere in logs)
password = <secret> # → appears as "password=<REDACTED>" in all output
6.2 TDE Certificate Lifecycle
tde_capture=yes:
- Export DEK certificate + encrypted PVK via T-SQL → encrypt with job-derived key → transport via PTCOMM RestoreObject (never touches filesystem)
tde_restore_cert=AUTO on restore:
- Fetch from RestoreObject →
CREATE MASTER KEY(if not exists) →CREATE CERTIFICATE FROM BINARY→RESTORE DATABASE WITH MOVE
Fully automated cross-server TDE restore. No manual cert export/import.
6.3 Transport Security
| Connection | Encryption |
|---|---|
| TDS (non-localhost) | TLS 1.2+ required by default |
| Azure MI | TLS always enforced |
| PTCOMM (backend↔adapter) | OS process pipe (same host, OS-isolated) |
| Metrics endpoint | HTTP on loopback only |
6.4 Minimum SQL Permissions
CREATE LOGIN [bacula_backup] WITH PASSWORD = '<strong_password>';
GRANT BACKUP DATABASE, BACKUP LOG, VIEW SERVER STATE TO [bacula_backup];
-- For TDE capture:
GRANT CONTROL SERVER TO [bacula_backup];
-- For system DBs: BACKUP DATABASE on master, msdb, model
6.5 Ransomware Hook
ransomware_hook=yes → pre-backup query to PodHeitor ARD daemon → anomaly reported → job aborts with error frame. Prevents backing up already-ransomed data.
6.6 Release Integrity
sha256sum -c releases/SHA256SUMS --ignore-missing
rpm --checksig podheitor-mssql-plugin-1.0.0-1.el9.x86_64.rpm
7. Observability
7.1 Prometheus Metrics
Enable: metrics_endpoint=0.0.0.0:9200
podheitor_mssql_jobs_started_total{instance,db}
podheitor_mssql_jobs_succeeded_total{instance,db}
podheitor_mssql_jobs_failed_total{instance,db}
podheitor_mssql_bytes_backed_up_total{instance,db}
podheitor_mssql_last_job_duration_ms{instance,db}
podheitor_mssql_last_backup_size_bytes{instance,db,level}
podheitor_mssql_replication_lag_seconds{db,role}
podheitor_mssql_dbs_backed_up_total
Health check: GET /healthz → 200 OK {"status":"ok","version":"1.0.0"}
7.2 OpenTelemetry Traces
Enable: otel_endpoint=http://otel-collector:4318
Each job produces nested spans: backup_job → catalog_query → backup_db per database → vdi_stripe per stripe → verifyonly. Compatible with Jaeger, Zipkin, Tempo, AWS X-Ray, Azure Monitor.
8. Installation Guide
8.1 SQL Server Prerequisites
-- Create backup login
CREATE LOGIN [bacula_backup] WITH PASSWORD = '<strong_password>';
GRANT BACKUP DATABASE, BACKUP LOG, VIEW SERVER STATE TO [bacula_backup];
-- passfile (Linux)
echo '<password>' > /opt/bacula/etc/.mssql_pass
chown root:bacula /opt/bacula/etc/.mssql_pass && chmod 640 /opt/bacula/etc/.mssql_pass
Microsoft ODBC Driver 18 (Linux):
curl -fsSL https://packages.microsoft.com/config/rhel/9/prod.repo | tee /etc/yum.repos.d/mssql-release.repo
ACCEPT_EULA=Y dnf install -y msodbcsql18 unixODBC
8.2 Linux RPM Installation
# Verify SHA256
sha256sum -c SHA256SUMS --ignore-missing
# Install
rpm -Uvh podheitor-mssql-plugin-1.0.0-1.el9.x86_64.rpm
# → /opt/bacula/plugins/podheitor-mssql-fd.so
# → /opt/bacula/bin/podheitor-mssql-backend
# → /opt/bacula/etc/podheitor-mssql.conf (from .sample if absent)
# Edit config and reload FD (done automatically by %post)
vi /opt/bacula/etc/podheitor-mssql.conf
systemctl reload bacula-fd
# Verify
/opt/bacula/bin/podheitor-mssql-backend --version
# → PodHeitor MSSQL Backend v1.0.0
8.3 Windows zip Installation
Expand-Archive podheitor-mssql-plugin-1.0.0-win64.zip -DestinationPath C:ph-install
Copy-Item C:ph-installpodheitor-mssql-fd.dll "C:Program FilesBaculaplugins"
Copy-Item C:ph-installpodheitor-mssql-backend.exe "C:Program FilesBaculabin"
Copy-Item C:ph-installpodheitor-mssql.conf.sample "C:ProgramDataBaculaetcpodheitor-mssql.conf"
net stop bacula-fd && net start bacula-fd
& "C:Program FilesBaculabinpodheitor-mssql-backend.exe" --version
8.4 Bacula Director Jobs
Job {
Name = "MSSQL-Full"
Type = Backup; Level = Full
Client = mssql-server-fd
FileSet = "MSSQL-Production"
Schedule = "WeeklyCycle"
Storage = File1; Pool = Full
}
Job {
Name = "MSSQL-Diff"
Type = Backup; Level = Differential
Client = mssql-server-fd
FileSet = "MSSQL-Production"
Schedule = "DailyCycle"
Storage = File1; Pool = Differential
}
Job {
Name = "MSSQL-Log"
Type = Backup; Level = Incremental
Client = mssql-server-fd
FileSet = "MSSQL-Production"
Schedule = "HourlyCycle"
Storage = File1; Pool = Incremental
}
9. Configuration Reference
9.1 Config File
# /opt/bacula/etc/podheitor-mssql.conf
user = bacula_backup
passfile = /opt/bacula/etc/.mssql_pass
authtype = server
checksum = yes
compress = native+zstd
compress_level = 3
metrics_endpoint = 0.0.0.0:9200
9.2 FileSet Examples
All databases, VDI, integrity check:
FileSet {
Name = "MSSQL-Production"
Enable VSS = no
Include {
Options { Signature = SHA256 }
Plugin = "podheitor-mssql: checksum=yes verify_backup=yes compress=native+zstd include_server_objects=yes tde_capture=yes"
}
}
Linux, 4 stripes, specific DBs:
FileSet {
Name = "MSSQL-Linux"
Include {
Options { Signature = SHA256 }
Plugin = "podheitor-mssql: mode=tds_fifo include=prod_orders include=prod_inventory stripes=4 compress=native+zstd passfile=/opt/bacula/etc/.mssql_pass"
}
}
VLDB 8-stripe:
FileSet {
Name = "MSSQL-VLDB"
Include {
Options { Signature = SHA256 }
Plugin = "podheitor-mssql: database=data_warehouse stripes=8 parallel_dbs=1 compress=native+zstd buffercount=32 maxtransfersize=4194304 verify_backup=yes"
}
}
Always On AG, backup from readable secondary:
FileSet {
Name = "MSSQL-AG"
Enable VSS = no
Include {
Options { Signature = SHA256 }
Plugin = "podheitor-mssql: ag_preference=readable_secondary ag_failover_retry=3 copyonly tde_capture=yes"
}
}
9.3 Complete Backup Options
| # | Option | Default | Description |
|---|---|---|---|
| 1 | database |
* |
DB glob (all except tempdb) |
| 2 | include |
— | Explicit include (multi) |
| 3 | exclude |
tempdb |
Exclusion glob (multi) |
| 4 | user |
— | SQL login |
| 5 | domain |
— | AD domain |
| 6 | password |
— | Inline password (auto-redacted) |
| 7 | passfile |
— | Password file path |
| 8 | authtype |
windows |
windows / server / azure_ad / managed_identity |
| 9 | instance |
MSSQLSERVER |
Instance name (multi) |
| 10 | all_instances |
off | Auto-discover all instances |
| 11 | hostname |
(local) |
ODBC server string |
| 12 | abort_on_error |
off | Fail job on any DB error |
| 13 | copyonly |
off | COPY_ONLY: yes=all, incremental=logs only |
| 14 | fullbackup |
off | Force Full regardless of Bacula level |
| 15 | skipreadonly |
off | Skip read-only DBs on Incremental |
| 16 | dblayout |
off | Store DB layout as RestoreObject |
| 17 | lock_timeout |
0 ms |
SET LOCK_TIMEOUT |
| 18 | buffercount |
10 |
VDI I/O buffer count |
| 19 | blocksize |
65536 |
Physical block size (bytes) |
| 20 | maxtransfersize |
65536 |
Max VDI transfer (bytes) |
| 21 | connection_string |
— | Override ODBC connection string |
| 22 | checksum |
yes |
WITH CHECKSUM |
| 23 | driver |
ODBC 18 | ODBC driver name |
| 24 | target_backup_recovery_models |
all | Filter: SIMPLE, FULL, BULK_LOGGED |
| 25 | simple_recovery_models_incremental_action |
make_full |
make_full / ignore_with_error / ignore |
| 26 | mode |
auto |
Engine: auto,vdi,tds_fifo,tds_url,snapshot,replicate_* |
| 27 | stripes |
1 |
Parallel stripes (1–64) |
| 28 | parallel_dbs |
CPU cores | Max concurrent DB backups |
| 29 | compress |
native+zstd |
none,native,zstd,lz4,native+zstd |
| 30 | compress_level |
3 |
1–22 (zstd), 1–9 (gzip) |
| 31 | max_rate_kbps |
0 |
Bandwidth throttle (0=unlimited) |
| 32 | resource_governor_classifier |
— | SQL Resource Governor classifier |
| 33 | page_verify |
none |
none,page_audit,torn_page |
| 34 | verify_backup |
no |
RESTORE VERIFYONLY after backup |
| 35 | dbcc_checkdb |
no |
no,physical_only,full |
| 36 | ag_preference |
auto |
auto,primary,secondary,readable_secondary |
| 37 | ag_failover_retry |
3 |
Retries on AG failover mid-job |
| 38 | tde_capture |
no |
Capture TDE certs as RestoreObject |
| 39 | always_encrypted_cmk_report |
no |
CMK metadata snapshot |
| 40 | include_system_dbs |
master,msdb,model |
System DB granular control |
| 41 | include_server_objects |
no |
Export logins, linked servers, jobs |
| 42 | include_filestream |
yes |
FILESTREAM/FileTable consistency |
| 43 | include_hekaton_xtp |
yes |
In-Memory OLTP checkpoint files |
| 44 | include_service_broker |
no |
Service Broker queue export |
| 45 | include_ssas |
no |
SSAS servers for XMLA backup |
| 46 | include_ssrs |
no |
SSRS host:port |
| 47 | include_ssis |
no |
SSISDB + master key |
| 48 | url_endpoint |
— | S3/Azure URL for mode=tds_url |
| 49 | url_credential |
— | SAS token or MI identity |
| 50 | snapshot_provider |
platform | vss,lvm,zfs |
| 51 | dry_run |
no |
Preflight validate, no write |
| 52 | ransomware_hook |
no |
Abort on ARD anomaly |
| 53 | metrics_endpoint |
— | Prometheus /metrics host:port |
| 54 | otel_endpoint |
— | OTLP traces URI |
| 55 | use_sudo |
no |
Elevate for Linux FIFO mgmt |
| 56 | cluster_id |
— | Multi-cluster namespace prefix |
| 57 | config_file |
platform default | External config file path |
| 58 | virtual_full |
no |
Synthetic full from chain |
9.4 Complete Restore Options
| # | Option | Default | Description |
|---|---|---|---|
| 1 | instance |
MSSQLSERVER |
Target SQL instance |
| 2 | database |
source name | New database name |
| 3 | username |
— | SQL login |
| 4 | password |
— | Password |
| 5 | domain |
— | AD domain |
| 6 | hostname |
(local) |
Target server |
| 7 | authtype |
windows |
Auth mode |
| 8 | recovery |
yes |
WITH RECOVERY (no=NORECOVERY) |
| 9 | stop_before_mark |
— | STOPBEFOREMARK 'mark' |
| 10 | stop_at_mark |
— | STOPATMARK 'mark' |
| 11 | stop_at |
— | STOPAT 'datetime' |
| 12 | restricted_user |
no |
WITH RESTRICT_USER |
| 13 | verify |
off | Verify before restore |
| 14 | connection_string |
— | Override ODBC |
| 15 | checksum |
no |
WITH CHECKSUM on restore |
| 16 | driver |
ODBC 18 | ODBC driver |
| 17 | mode |
auto |
auto,instant,in_place,new_db,to_disk,single_item,ag_reseed,master_restore |
| 18 | stripes |
backup value | Must match backup stripes |
| 19 | dbcc_checkdb |
physical_only |
DBCC after restore |
| 20 | row_count_compare |
no |
Compare row counts vs manifest |
| 21 | single_item_target |
— | schema.table[,...] |
| 22 | sandbox_docker_image |
mssql/server:2022 | Docker sandbox image |
| 23 | ag_target_group |
— | AG name for ag_reseed |
| 24 | file_relocation_map |
— | JSON {logical: physical} map |
| 25 | tde_restore_cert |
— | TDE cert bundle (AUTO=from RestoreObject) |
| 26 | drop_existing |
no |
no,yes,only_if_match |
| 27 | tail_log_before |
no |
Auto tail-log before destructive restore |
| 28 | point_in_time_tz |
UTC |
Timezone for stop_at |
| 29 | parallel_restore_dbs |
CPU cores | Concurrent DB restores |
| 30 | progress_report_interval |
30 s |
Job-log progress cadence |
| 31 | regexwhere |
— | Bacula file relocation regex |
10. Restore Procedures & Use Cases
10.1 Standard In-Place Restore
# bconsole restore
Plugin Options: instance=MSSQLSERVER database=AdventureWorks recovery=yes
10.2 Point-in-Time Restore
Plugin Options: stop_at=2026-04-28T10:30:00 database=AdventureWorks_pit recovery=yes
Plugin Options: stop_at_mark=SavePoint1 database=AdventureWorks_pit recovery=yes
10.3 Rename + File Relocation
// file_relocation.json
{
"AdventureWorks_Data": "D:NewDataAdventureWorks.mdf",
"AdventureWorks_Log": "L:NewLogsAdventureWorks_log.ldf"
}
Plugin Options: database=AdventureWorks_new file_relocation_map=C:relocation.json
10.4 Instant Recovery
Plugin Options: mode=instant database=AdventureWorks_instant
# → DB queryable in ~35 sec (100 GB example)
# → Full restore continues in background
10.5 Single-Item (Table) Restore
Plugin Options: mode=single_item single_item_target=dbo.Orders,dbo.OrderItems database=recovery_sandbox
# → Ephemeral Docker SQL container
# → Full restore to container
# → Table extraction via bcp → INSERT scripts / CSV
# → Container removed after extraction
10.6 AG Reseed (Secondary Lost)
Plugin Options: mode=ag_reseed ag_target_group=AG_Production database=prod_orders tail_log_before=yes recovery=no
# → Tail-log backup → Remove from AG → Restore Full+Diff+Logs → Rejoin AG
10.7 TDE Cross-Server Restore
Plugin Options: tde_restore_cert=AUTO database=SecureDB
# → Cert fetched from RestoreObject → installed → RESTORE DATABASE WITH MOVE
11. Performance Reports
11.1 Throughput by DB Size and Stripe Count
Environment: SQL Server 2022 / Win Server 2025 / 16 cores / NVMe / 10 Gbps to Bacula
| DB Size | stripes=1 | stripes=2 | stripes=4 | stripes=8 |
|---|---|---|---|---|
| 10 GB | 52 s | 30 s | 18 s | 15 s |
| 100 GB | 8.0 min | 4.7 min | 2.8 min | 2.2 min |
| 1 TB | 180 min | 100 min | 60 min | 42 min |
11.2 Compression Ratios
| DB Type | none | native | native+zstd |
|---|---|---|---|
| OLTP (AdventureWorks) | 1× | 3.1× | 3.8× |
| Data Warehouse | 1× | 4.2× | 5.0× |
| JSON-heavy | 1× | 1.8× | 2.7× |
11.3 Restore Performance
| DB Size | Standard | Instant (queryable in) |
|---|---|---|
| 10 GB | 45 s | 8 s |
| 100 GB | 11 min | 35 s |
| 1 TB | 75 min | 3 min |
11.4 E2E Validation (v1.0.0 GA — Run 9)
PASS: 16 / 16 FAIL: 0 / 16
Duration: 22:01 to 22:13 (12 minutes, installed packages)
Environment: Bacula 15.0.3 + SQL Server 2022 + 2-node AG
12. Compatibility Matrix
SQL Server Versions
| Version | Windows VDI | Linux TDS/FIFO | Azure MI | Always On |
|---|---|---|---|---|
| 2012, 2014 | ✅ | — | — | ✅ Basic |
| 2016, 2017 | ✅ | ✅ | — | ✅ |
| 2019, 2022 | ✅ | ✅ | ✅ | ✅ |
| Azure SQL MI | — | — | ✅ | ✅ Auto |
Bacula Community Versions
| Version | Status |
|---|---|
| 14.0.x | Compatible |
| 15.0.x | ✅ Tested and certified |
13. Sizing Guide
File Daemon Host
| Workload | RAM | CPU | Network |
|---|---|---|---|
| Small (< 100 GB) | 1 GB | 2 cores | 1 Gbps |
| Medium (100 GB–1 TB) | 4 GB | 4 cores | 1 Gbps |
| Large (1–10 TB) | 8 GB | 8 cores | 10 Gbps |
| VLDB (> 10 TB) | 16 GB | 16 cores | 10 Gbps+ |
Memory formula: parallel_dbs × stripes × buffercount × maxtransfersize
Default: 1 × 1 × 10 × 64 KB = 640 KB VLDB: 4 × 8 × 32 × 4 MB = 4 GB
SQL Server Impact
| Setting | CPU | I/O | Recommendation |
|---|---|---|---|
compress=native+zstd |
+8–20% | −72% | Default — strongly recommended |
stripes=4 |
SQL parallel | +4× IOPS | VLDB standard |
dbcc_checkdb=physical_only |
+10–30% | +30% | Weekly schedule |
resource_governor_classifier |
Policy-bounded | Policy-bounded | OLTP hosts |
14. Bacularis Integration
RestoreObject Schemas
mssql-dblayout.json (emitted with dblayout=yes):
{
"schema_version": "1.0",
"instance": "MSSQLSERVER",
"databases": [{
"name": "AdventureWorks",
"level": "Full",
"compressed_bytes": 82000000,
"original_bytes": 256000000,
"first_lsn": "34000000001234500001",
"last_lsn": "34000000012345600001",
"recovery_model": "FULL",
"stripes": 4,
"compression": "native+zstd",
"files": [
{"logical": "AdventureWorks_Data", "physical": "C:DataAW.mdf", "size_bytes": 230000000},
{"logical": "AdventureWorks_Log", "physical": "C:LogAW_log.ldf", "size_bytes": 26000000}
]
}]
}
mssql-tde-{db}.json (emitted with tde_capture=yes):
{
"schema_version": "1.0",
"database": "SecureDB",
"certificate_name": "SecureDB_Cert",
"certificate_der_b64": "<base64>",
"encrypted_pvk_b64": "<base64>"
}
bconsole Access
list restoreobjects jobid=5123
llist restoreobject jobid=5123 objectname=mssql-dblayout.json
15. Migration from Bacula Enterprise
15.1 Compatibility Promise
Byte-identical namespace. Enterprise backups are 100% restorable by PodHeitor and vice versa.
15.2 Zero-Downtime Procedure
Day 1: Install PodHeitor alongside Enterprise (both present on FD)
Day 1: dry_run=yes → validate connectivity + permissions
Day 2: First Full backup with PodHeitor → verify in Bacula log
Day 3: Switch FileSet: "mssql:" → "podheitor-mssql:"
Day 30+: Remove Enterprise plugin after retention cycle (optional)
15.3 Notable Differences
| Aspect | Enterprise | PodHeitor |
|---|---|---|
Default checksum |
No |
Yes (safer) |
| Default compression | None | native+zstd (smaller backups) |
| Differential filename | data.bak |
data.diff.bak (distinguishable, both recognized) |
__ph_* sidecars |
Not produced | Produced (Enterprise ignores on restore) |
16. Troubleshooting
16.1 Diagnostic Modes
# Preflight (no write)
Plugin = "podheitor-mssql: dry_run=yes"
# VDI tracing (Windows)
set PODHEITOR_VDI_DEBUG=1 # then restart FD + run job
# Backend info
/opt/bacula/bin/podheitor-mssql-backend --version --features
16.2 Common Error Table
| Error | Cause | Fix |
|---|---|---|
Login failed for user |
Wrong creds or authtype | Check user/passfile/authtype |
VD_E_ABORT |
VDI closed by SQL | Check SQL error log; increase maxtransfersize |
PTCOMM header bad length |
Stray stdout output | Check ODBC debug env vars |
Cannot find master key |
TDE capture, no master key | CREATE MASTER KEY ENCRYPTION BY PASSWORD |
AG replica not preferred |
ag_preference not met | Set ag_preference=primary temporarily |
Address already in use |
Metrics port conflict | Change metrics_endpoint port |
FIFO permission denied |
Not in mssql group | usermod -aG mssql bacula or use_sudo=yes |
RESTORE VERIFYONLY failed |
Backup/media corruption | Investigate with DBCC |
16.3 Log Locations
| Log | Linux | Windows |
|---|---|---|
| Bacula FD | /opt/bacula/log/bacula-fd.log |
Windows Event Log |
| PodHeitor backend | /opt/bacula/log/podheitor-mssql-<jobid>.log |
%PROGRAMDATA%Baculalog |
| SQL Server | /var/opt/mssql/log/errorlog |
Windows Event Log |
17. Roadmap
| Version | ETA | Highlights |
|---|---|---|
| v1.0.0 GA | 2026-04-28 | All F0–F10. 16/16 E2E PASS. |
| v1.1 | Q3 2026 | Linux ARM64, DEB package, NSIS installer, NetApp ONTAP |
| v1.2 | Q4 2026 | Kubernetes sidecar (Helm chart) |
| v1.3 | Q1 2027 | Query Store backup, Extended Events |
| v1.4 | Q2 2027 | Bacularis UI full plugin descriptor |
| v1.5 | Q3 2027 | Cross-version replication (2019→2022) |
18. Validation Evidence
E2E Test Results (Run 9 — Installed Packages)
[22:01:XX] ✓ PASS: T01-Full-backup
[22:02:XX] ✓ PASS: T02-Diff-backup
[22:03:XX] ✓ PASS: T03-Log-backup
[22:04:XX] ✓ PASS: T04-Restore-inplace
[22:05:XX] ✓ PASS: T05-Restore-new-db
[22:06:XX] ✓ PASS: T06-Compressed-backup
[22:07:XX] ✓ PASS: T07-Verify-backup
[22:08:XX] ✓ PASS: T08-MultiDB-backup
[22:08:XX] ✓ PASS: T09-Instant-restore
[22:08:XX] ✓ PASS: T10-Replicate-Primary-Full
[22:08:XX] ✓ PASS: T10b-Replicate-Primary-Log
[22:09:XX] ✓ PASS: T11-Replicate-Follower-Apply
[22:09:XX] ✓ PASS: T11b-Follower-Idempotency
[22:13:XX] ✓ PASS: T11c-RPO-Simulation
[22:13:XX] ✓ PASS: T12-AG-Bootstrap
[22:13:XX] ✓ PASS: T13-Fanout-Primary-Full
PASS: 16 / 16 | FAIL: 0 / 16
Cargo Test Results
OL9: test result: ok. 311 passed; 0 failed
Win2025: test result: ok. 381 passed; 0 failed
Release Artifacts
podheitor-mssql-plugin-1.0.0-1.el9.x86_64.rpm (562 KB)
SHA256: 4b785ae74e6e9a5151eb9b10304daae8daf9402888cce6ae5459533b668e809d
podheitor-mssql-plugin-1.0.0-win64.zip (772 KB)
SHA256: 33a6883df08f52ab8f881d70f1efa1760d753dc09067da8f19476bdfa1de2973
Contact & Licensing
Author: Heitor Faria | Email: heitor@opentechs.lat Phone: +1 786 726-1749 | WhatsApp: +55 61 98268-4220
Copyright © 2026 Heitor Faria. All rights reserved. Proprietary and confidential. Commercial licensing available.
Trazendo sua proposta do Bacula Enterprise, Veeam, Commvault ou Netbackup? Mínimo 50% de desconto + muito mais funcionalidades. heitor@opentechs.lat | +1 786 726-1749 | +55 61 98268-4220
Disponível em:
Português (Portuguese (Brazil))
English
Español (Spanish)