Skip to main content

MariaDB

MariaDB speaks the MySQL wire protocol, so the OpenTelemetry Collector's mysql receiver monitors it without a separate exporter. It connects over TCP on port 3306, runs SHOW GLOBAL STATUS / SHOW GLOBAL VARIABLES and performance_schema queries, and emits 31 metrics per scrape - query throughput, connection health, lock contention, buffer-pool pressure, and InnoDB internals. This guide sets up a monitoring user, configures the receiver, and ships metrics to base14 Scout.

Prerequisites​

RequirementMinimumRecommended
MariaDB10.512.3+
OTel Collector Contrib0.90.0latest
base14 ScoutAny—

Before starting:

  • MariaDB must be reachable from the host running the Collector.
  • A superuser account to create the monitoring user once.
  • performance_schema enabled for statement-event and table-lock-wait coverage.
  • OTel Collector installed - see Docker Compose Setup.
  • A Scout account and OTLP endpoint.

What You'll Monitor​

Metrics are grouped into three tiers by how you use them. Scrape Core always, alert on Operational, and reach for Diagnostic during an incident or capacity review.

Core - is it up and serving​

MetricWhat it tells you
mysql.uptimeSeconds since server start - reachability and restart detection.
mysql.query.countStatements executed; the headline throughput KPI.

Operational - what to alert on​

GroupMetricsWhat it tells you
Connection healthmysql.connection.count, mysql.connection.errorsConnections opened and connection failures by cause.
Latencymysql.query.slow.countQueries over long_query_time - regressions and missing indexes.
Contentionmysql.locks, mysql.row_locksTable locks (immediate vs waited) and InnoDB row-lock waits / time.
Saturationmysql.threadsThreads connected / running / cached against the server max.
Cache pressuremysql.buffer_pool.usageBuffer-pool bytes by state; working set vs pool size.
Query spillmysql.tmp_resourcesTemp tables and files, memory vs disk - sort and join spill.

Diagnostic - for investigation and tuning​

Higher cardinality (these series drive most of the per-scrape data-point count). Keep them available for incident work; drop them in production with a filter processor if you need to control volume.

GroupMetricsWhen you reach for it
Command / handler breakdownmysql.commands, mysql.handlers, mysql.query.client.countWhich statement and storage-engine handler calls dominate.
InnoDB internalsmysql.operations, mysql.page_operations, mysql.log_operations, mysql.double_writesRow, page, redo-log, and doublewrite-buffer activity.
Buffer-pool detailmysql.buffer_pool.data_pages, mysql.buffer_pool.limit, mysql.buffer_pool.operations, mysql.buffer_pool.page_flushes, mysql.buffer_pool.pagesClean/dirty pages, capacity, read/write requests, flush rate.
Query workloadmysql.sorts, mysql.joins, mysql.prepared_statementsSort and join types, prepared-statement load.
Resource accountingmysql.opened_resources, mysql.table_open_cache, mysql.client.network.ioFiles / tables / definitions opened, cache hits/misses, client bytes.
Per-table statsmysql.table.rows, mysql.table.size, mysql.table.average_row_lengthRow count, data/index size, and average row length per table.

The following are enabled in the config but only emit in specific contexts - keep them on so they surface when those conditions arise:

  • mysql.replica.sql_delay, mysql.replica.time_behind_source - emit only when a replica is configured.
  • mysql.statement_event.count, mysql.statement_event.wait.time - require populated performance_schema digest tables.
  • mysql.table.lock_wait.read.count / .read.time / .write.count / .write.time - emit only when contended table-lock waits occur.

Full metric reference: OTel MySQL Receiver.

Key Alerts to Configure​

Threshold guidance for the most useful Operational-tier series. These are starting points; tune them to your workload.

MetricThresholdWhy it matters
rate(mysql.connection.errors)Rising above 0Clients can't connect; check max_connections, auth, and network.
mysql.threads{kind=connected}Near the server maxApproaching max_connections; add pooling or raise the cap.
rate(mysql.query.slow.count)Rising vs baselineQuery regressions or missing indexes; inspect the slow log.
mysql.row_locks (wait time)RisingTransactions blocking; review lock ordering and hot rows.
mysql.tmp_resources{kind=disk_tables}Risingtmp_table_size too small or unindexed sorts; tune queries.
mysql.buffer_pool.usage (dirty/free ratio)Shifting toward fullWorking set exceeds the pool; consider innodb_buffer_pool_size.

Access Setup​

Create a dedicated MariaDB user with minimal monitoring privileges:

MariaDB monitoring user setup
CREATE USER 'otel_monitor'@'%' IDENTIFIED BY '<your_password>';
GRANT PROCESS, REPLICATION CLIENT ON *.* TO 'otel_monitor'@'%';
GRANT SELECT ON performance_schema.* TO 'otel_monitor'@'%';
FLUSH PRIVILEGES;

Minimum required permissions:

PermissionPurpose
PROCESSAccess to SHOW GLOBAL STATUS and SHOW GLOBAL VARIABLES
REPLICATION CLIENTAccess to SHOW REPLICA STATUS for replication metrics
SELECT ON performance_schema.*Statement events, table I/O, and lock-wait metrics

No write permissions are needed. The Collector only reads metrics.

Ensure performance_schema and the slow query log are enabled:

my.cnf
[mysqld]
performance_schema = ON
slow_query_log = ON
long_query_time = 1

Test connectivity with the monitoring user:

Verify access
mariadb -h <mariadb-host> -P 3306 -u otel_monitor -p \
-e "SELECT version();"

Configuration​

config/otel-collector.yaml
receivers:
mysql:
endpoint: ${env:MARIADB_HOST}:3306
username: ${env:MYSQL_USER}
password: ${env:MYSQL_PASSWORD}
collection_interval: 10s
allow_native_passwords: true

tls:
insecure: true
insecure_skip_verify: true

metrics:
# Disabled by default - enable for full observability
mysql.client.network.io:
enabled: true
mysql.commands:
enabled: true
mysql.connection.count:
enabled: true
mysql.connection.errors:
enabled: true
mysql.joins:
enabled: true
mysql.query.client.count:
enabled: true
mysql.query.count:
enabled: true
mysql.query.slow.count:
enabled: true
mysql.replica.sql_delay:
enabled: true
mysql.replica.time_behind_source:
enabled: true
mysql.statement_event.count:
enabled: true
mysql.statement_event.wait.time:
enabled: true
mysql.table.average_row_length:
enabled: true
mysql.table.lock_wait.read.count:
enabled: true
mysql.table.lock_wait.read.time:
enabled: true
mysql.table.lock_wait.write.count:
enabled: true
mysql.table.lock_wait.write.time:
enabled: true
mysql.table.rows:
enabled: true
mysql.table.size:
enabled: true
mysql.table_open_cache:
enabled: true

statement_events: {}

processors:
resource:
attributes:
- key: environment
value: ${env:ENVIRONMENT}
action: upsert
- key: service.name
value: ${env:SERVICE_NAME}
action: upsert

batch:
timeout: 10s
send_batch_size: 1024

# Export to base14 Scout
exporters:
otlphttp/b14:
endpoint: ${env:OTEL_EXPORTER_OTLP_ENDPOINT}
tls:
insecure_skip_verify: true

service:
pipelines:
metrics:
receivers: [mysql]
processors: [resource, batch]
exporters: [otlphttp/b14]

allow_native_passwords: true lets the receiver authenticate against MariaDB's native password plugin. Leave the TLS block as shown only for an in-cluster or local stack with TLS disabled; against a TLS-enabled server, configure the CA and drop insecure.

To control metric volume in production, drop the Diagnostic tier with a filter processor while keeping the Core and Operational series.

Environment Variables​

.env
MARIADB_HOST=localhost
MYSQL_USER=otel_monitor
MYSQL_PASSWORD=your_password
ENVIRONMENT=your_environment
SERVICE_NAME=your_service_name
OTEL_EXPORTER_OTLP_ENDPOINT=https://<your-tenant>.base14.io

Verify the Setup​

Start the Collector and check for metrics within 60 seconds:

# Check Collector logs for scraped MariaDB metrics
docker logs otel-collector 2>&1 | grep -i "mysql"

# Verify MariaDB connectivity and uptime
mariadb -h ${MARIADB_HOST} -P 3306 -u otel_monitor -p \
-e "SHOW GLOBAL STATUS LIKE 'Uptime';"
-- Thread states (mysql.threads)
SHOW GLOBAL STATUS LIKE 'Threads_%';

-- InnoDB buffer pool (mysql.buffer_pool.*)
SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool%';

-- Slow queries (mysql.query.slow.count)
SHOW GLOBAL STATUS LIKE 'Slow_queries';

Troubleshooting​

Connection refused​

Cause: The Collector cannot reach MariaDB at the configured endpoint.

Fix:

  1. Verify MariaDB is running: systemctl status mariadb or docker ps | grep mariadb.
  2. Confirm the endpoint address and port (default 3306) in your config.
  3. Check bind-address in my.cnf - set it to 0.0.0.0 if the Collector runs on a separate host.

Authentication failed​

Cause: Monitoring credentials are wrong, or the user lacks grants.

Fix:

  1. Test credentials directly: mariadb -h localhost -u otel_monitor -p -e "SELECT 1;".
  2. Verify the grants: SHOW GRANTS FOR 'otel_monitor'@'%';.
  3. Confirm MYSQL_USER and MYSQL_PASSWORD are set, and that allow_native_passwords: true is present if the server uses native passwords.

Statement-event metrics always zero​

Cause: performance_schema is disabled, its statement consumers are off, or the digest tables haven't populated yet.

Look at: the Diagnostic mysql.statement_event.count and mysql.statement_event.wait.time series - both stay flat at zero until performance_schema digests exist.

Fix:

  1. Verify it's enabled: SHOW VARIABLES LIKE 'performance_schema';.
  2. Check the statement consumers, and enable them if they are off:
SELECT * FROM performance_schema.setup_consumers
WHERE name LIKE 'events_statements%';

UPDATE performance_schema.setup_consumers SET ENABLED = 'YES'
WHERE name LIKE 'events_statements%';

Table-lock-wait metrics stay flat​

Cause: No contended table-lock waits have occurred.

Look at: the Diagnostic mysql.table.lock_wait.read.* / .write.* series - they emit only when sessions actually wait on a table lock, so a quiet server reports nothing here. This is expected, not a misconfiguration.

Replica metrics missing​

Cause: The server has no replica configured.

Look at: mysql.replica.sql_delay and mysql.replica.time_behind_source - these emit only on a server with an active replication channel.

Fix: No action needed on a standalone server. They surface automatically once replication is configured.

No metrics appearing in Scout​

Cause: Metrics are collected but not exported.

Fix:

  1. Check Collector logs for export errors: docker logs otel-collector.
  2. Verify OTEL_EXPORTER_OTLP_ENDPOINT is set correctly.
  3. Confirm the pipeline includes both the receiver and the exporter.

FAQ​

Why does this use the MySQL receiver?​

MariaDB is wire-compatible with MySQL - both use the MySQL protocol for client connections. The Collector's mysql receiver works against MariaDB without modification. There is no separate MariaDB receiver.

Does this work with MariaDB Galera Cluster?​

Yes. Add a receiver block per node with distinct names:

receivers:
mysql/node1:
endpoint: node1:3306
username: ${env:MYSQL_USER}
password: ${env:MYSQL_PASSWORD}
mysql/node2:
endpoint: node2:3306
username: ${env:MYSQL_USER}
password: ${env:MYSQL_PASSWORD}

Then include both in the pipeline: receivers: [mysql/node1, mysql/node2].

Does this work with MariaDB running in Kubernetes?​

Yes. Set endpoint to the MariaDB service DNS (e.g., mariadb.default.svc.cluster.local:3306) and inject credentials via a Kubernetes secret. The Collector can run as a sidecar or DaemonSet.

What permissions does the monitoring account need?​

PROCESS, REPLICATION CLIENT, and SELECT on performance_schema. No write access is required - the Collector only reads metrics.

Where is per-query latency?​

The receiver exposes counters and gauges, not per-request timing. mysql.query.slow.count flags queries over long_query_time; for digest breakdowns enable the mysql.statement_event.* metrics, which read performance_schema digests.

What's Next?​

  • Create Dashboards: Explore pre-built dashboards or build your own. See Create Your First Dashboard.
  • Monitor More Components: Add monitoring for MySQL, PostgreSQL, and other components.
  • Fine-tune Collection: Drop the Diagnostic tier in production with a filter processor to control volume; adjust collection_interval and statement_events limits to your query workload.
Was this page helpful?