[MariaDB Addon] Adjust settings to improve performance and persists after reboot

With the last update you can finally adjust the parameters of the MariaDB addon and persist to a restart. Here is the change:

To have a starting point I share with you a template with the most useful parameters, all commented and also some examples to monitor directly from HA. Settings:

mariadb_server_args:
  #############################################################
  # InnoDB Buffer Pool Settings
  # The buffer pool is where MariaDB caches table and index data
  #############################################################
  
  # Controls how much RAM is allocated to InnoDB to cache table and index data
  # Higher values improve performance by reducing disk I/O
  # Recommendation: 70-80% of available RAM for dedicated servers
  - "--innodb_buffer_pool_size=12G"  
  #############################################################
  # InnoDB Log Settings
  # Controls how InnoDB handles transaction logging
  #############################################################
  
  # Size of the memory buffer InnoDB uses to write to the transaction logs
  # Larger values reduce disk I/O for transaction heavy workloads
  # If you see 'wait on innodb log buffer' in SHOW ENGINE INNODB STATUS, increase this
  - "--innodb_log_buffer_size=32M"
  
  # Size of each InnoDB redo log file
  # Larger values improve performance but increase recovery time
  # Should be large enough to handle peak write loads
  - "--innodb_log_file_size=512M"

#############################################################
  # InnoDB File and I/O Settings
  # Configures how InnoDB handles file operations and I/O
  #############################################################
  
  # Stores each InnoDB table and its indexes in a separate .ibd file
  # Enables features like table compression and faster truncate operations
  # Recommended ON for better management and flexibility
  - "--innodb_file_per_table=1"
  
  # Controls how InnoDB flushes log buffer to disk
  # 0 = write once per second (fastest but unsafe)
  # 1 = write and flush at each commit (safest but slowest)
  # 2 = write at commit, flush once per second (good compromise)
  - "--innodb_flush_log_at_trx_commit=2"
  
  # Specifies the method used for flushing files to disk
  # O_DIRECT bypasses the operating system buffer cache
  # Recommended for systems with battery-backed write cache
  - "--innodb_flush_method=O_DIRECT"
  
  # Number of I/O operations per second (IOPS) for background tasks
  # Should match your storage device's capabilities
  # For NVMe SSDs, values of 2000-4000 are reasonable
  - "--innodb_io_capacity=2000"
  
  # Maximum IOPS InnoDB can perform
  # Used for burst situations, should be about 2x innodb_io_capacity
  # Don't set too high to avoid excessive flushing
  - "--innodb_io_capacity_max=4000"
  
  # Number of background threads for read operations
  # More threads can help with parallel read operations
  # Recommended: 4-8 for modern multi-core systems
  - "--innodb_read_io_threads=8"
  
  # Number of background threads for write operations
  # Separate from read threads to prevent read/write contention
  # Recommended: equal to read_io_threads
  - "--innodb_write_io_threads=8"

  #############################################################
  # InnoDB Optimization Settings
  # Additional settings for performance optimization
  #############################################################
  
  # Keeps table statistics persistent across server restarts
  # Helps optimizer make better decisions
  # Recommended ON for stable workloads
  - "--innodb_stats_persistent=1"
  
  # Enables adaptive hash indexing for faster lookups
  # Can improve performance up to 10% but uses extra memory
  # Monitor usage through SHOW ENGINE INNODB STATUS
  - "--innodb_adaptive_hash_index=1"

  #############################################################
  # Buffer and Cache Settings
  # Memory allocations for various operations
  #############################################################
  
  # Memory allocated for sorting operations
  # Used for ORDER BY and GROUP BY operations
  # Increase if seeing many disk-based sorts in slow query log
  - "--sort_buffer_size=4M"
  
  # Buffer used for sequential table scans
  # Larger values help with table scans but allocate per-thread
  # Be careful increasing as it's allocated per-connection
  - "--read_buffer_size=2M"
  
  # Buffer used for reading rows in sorted order
  # Used after sorting to read rows
  # Also allocated per-thread, so keep reasonable
  - "--read_rnd_buffer_size=2M"
  
  # Buffer used for joins not using indexes
  # Allocated per-join per-thread
  # Increase if seeing many disk-based joins in EXPLAIN
  - "--join_buffer_size=2M"
  
  # Maximum size for internal in-memory temporary tables
  # If a temp table exceeds this, MariaDB converts it to MyISAM
  # Should match max_heap_table_size for consistency
  - "--tmp_table_size=512M"
  
  # Maximum size for user-created MEMORY tables
  # Also affects internal temporary tables
  # Should match tmp_table_size
  - "--max_heap_table_size=512M"

  #############################################################
  # Connection and Cache Settings
  # Controls connection handling and table caching
  #############################################################
  
  # Maximum number of simultaneous client connections allowed
  # Each connection uses memory, so balance with available RAM
  # Monitor 'Max_used_connections' status variable
  - "--max_connections=512"
  
  # Number of table handles to keep open
  # Higher values reduce file open/close operations
  # Monitor table_open_cache_hits/misses ratio
  - "--table_open_cache=4000"
  
  # Number of table definitions (frm files) to cache
  # Reduces need to read .frm files from disk
  # Should be higher than number of tables in all databases
  - "--table_definition_cache=2000"
  
  # How many threads to cache for reuse
  # Helps reduce thread creation overhead
  # Monitor threads_cached and threads_created status
  - "--thread_cache_size=200"
  
  # Maximum number of file descriptors server can open
  # Should be higher than max_connections + table_open_cache
  # Check 'open_files_limit' in SHOW VARIABLES
  - "--open_files_limit=65535"

  #############################################################
  # Network Settings
  # Configure network-related parameters
  #############################################################
  
  # Maximum size of one packet or generated/intermediate string
  # Increase for large BLOB columns or long IN clauses
  # Monitor 'Max_used_connections' status
  - "--max_allowed_packet=64M"
  
  # Buffer for TCP/IP and socket communication
  # Initial size of communication buffer (will be rounded up to 1K)
  # Rarely needs adjustment
  - "--net_buffer_length=1M"
  
  # Seconds server waits for activity on interactive connections
  # Helps clean up abandoned connections
  # Default is 8 hours (28800 seconds)
  - "--interactive_timeout=28800"
  
  # Seconds server waits for activity on non-interactive connections
  # Should match interactive_timeout in most cases
  # Monitor Aborted_clients status variable
  - "--wait_timeout=28800"

  #############################################################
  # Performance and Monitoring Settings
  # Settings for monitoring and performance analysis
  #############################################################
  
  # Enables the performance schema
  # Collects detailed performance metrics
  # Has small overhead but valuable for monitoring
  - "--performance_schema=ON"
  
  # Disables binary logging
  # Improves performance if you don't need replication
  # Remove if you need point-in-time recovery
  - "--skip-log-bin"
  
  # Enables logging of slow queries
  # Helps identify problematic queries
  # Check slow_query_log_file for location
  - "--slow_query_log=1"
  
  # Time in seconds to consider a query "slow"
  # Queries taking longer than this are logged
  # Balance between logging and noise
  - "--long_query_time=2"
  
  # Disables DNS lookups for connecting hosts
  # Improves connection speed and reliability
  # Use IP addresses in GRANT statements
  - "--skip-name-resolve"

  #############################################################
  # Security Settings
  # Parameters affecting server security
  #############################################################
  
  # Restricts imported/exported files to specific directory
  # Prevents reading/writing files outside this directory
  # Important for LOAD DATA INFILE security
  - "--secure-file-priv=/tmp"
  
  # Disables use of symbolic links for table files
  # Prevents symbolic link attacks
  # Recommended for security
  - "--skip-symbolic-links"
  
  # Disables LOAD DATA LOCAL INFILE command
  # Prevents reading files from client system
  # Important security feature
  - "--local-infile=0"


[MONITORING]

As an example, you can add these SQL queries in configuration.yaml (no plugin is needed, it comes integrated in HA):

sql:
  # MariaDB Status
  - name: "MariaDB Status"
    query: >-
      SELECT 'running' as status 
      FROM information_schema.GLOBAL_STATUS 
      WHERE VARIABLE_NAME = 'Uptime' 
      AND CAST(VARIABLE_VALUE AS UNSIGNED) > 0;
    column: "status"
    value_template: "{{ value if value else 'stopped' }}"

  # MariaDB Version
  - name: "MariaDB Version"
    query: >-
      SELECT @@version as version;
    column: "version"

  # MariaDB Performance
  - name: "MariaDB Performance"
    query: >-
      SELECT CONCAT(
        ROUND(
          (SELECT VARIABLE_VALUE 
           FROM information_schema.GLOBAL_STATUS 
           WHERE VARIABLE_NAME = 'Queries') / 
          (SELECT VARIABLE_VALUE 
           FROM information_schema.GLOBAL_STATUS 
           WHERE VARIABLE_NAME = 'Uptime')
        ), ' q/s') as performance;
    column: "performance"
    
  # Database size
  - name: "Database Size"
    query: >-
      SELECT ROUND(SUM(data_length + index_length) / 1024 / 1024, 2) as size 
      FROM information_schema.tables 
      WHERE table_schema = 'homeassistant';
    column: "size"
    unit_of_measurement: "MB"
    value_template: "{{ value | float }}"

  # Table count
  - name: "Database Tables Count"
    query: >-
      SELECT COUNT(*) as count 
      FROM information_schema.tables 
      WHERE table_schema = 'homeassistant';
    column: "count"
    unit_of_measurement: "tables"

  # Total records
  - name: "Database Total Records"
    query: >-
      SELECT COUNT(*) as count 
      FROM states;
    column: "count"
    unit_of_measurement: "records"

  # MariaDB uptime
  - name: "MariaDB Uptime"
    query: >-
      SELECT VARIABLE_VALUE as value
      FROM information_schema.GLOBAL_STATUS 
      WHERE VARIABLE_NAME = 'Uptime';
    column: "value"
    unit_of_measurement: "seconds"

  # Active connections
  - name: "MariaDB Connections"
    query: >-
      SELECT VARIABLE_VALUE as value
      FROM information_schema.GLOBAL_STATUS 
      WHERE VARIABLE_NAME = 'Threads_connected';
    column: "value"
    unit_of_measurement: "connections"

  # Total queries
  - name: "MariaDB Questions"
    query: >-
      SELECT VARIABLE_VALUE as value
      FROM information_schema.GLOBAL_STATUS 
      WHERE VARIABLE_NAME = 'Questions';
    column: "value"
    unit_of_measurement: "queries"

  # MariaDB Buffer Pool Size
  - name: "MariaDB Buffer Pool Size"
    query: >-
      SELECT CONCAT(ROUND(@@innodb_buffer_pool_size/1024/1024/1024, 1), ' GB') as value;
    column: "value"

  # MariaDB Max Connections
  - name: "MariaDB Max Connections"
    query: >-
      SELECT @@max_connections as value;
    column: "value"

  # MariaDB InnoDB Log File Size
  - name: "MariaDB Log File Size"
    query: >-
      SELECT CONCAT(ROUND(@@innodb_log_file_size/1024/1024, 0), ' MB') as value;
    column: "value"

  # MariaDB Tmp Table Size
  - name: "MariaDB Tmp Table Size"
    query: >-
      SELECT CONCAT(ROUND(@@tmp_table_size/1024/1024, 0), ' MB') as value;
    column: "value"

  # MariaDB IO Capacity
  - name: "MariaDB IO Capacity"
    query: >-
      SELECT @@innodb_io_capacity as value;
    column: "value"

  # MariaDB IO Threads
  - name: "MariaDB IO Threads"
    query: >-
      SELECT CONCAT(
        'Read: ', @@innodb_read_io_threads,
        ', Write: ', @@innodb_write_io_threads
      ) as value;
    column: "value"

  # MariaDB Table Cache
  - name: "MariaDB Table Cache"
    query: >-
      SELECT @@table_open_cache as value;
    column: "value"

  # MariaDB Buffer Sizes
  - name: "MariaDB Buffer Sizes"
    query: >-
      SELECT CONCAT(
        'Sort: ', ROUND(@@sort_buffer_size/1024/1024, 0), 'M, ',
        'Read: ', ROUND(@@read_buffer_size/1024/1024, 0), 'M, ',
        'Join: ', ROUND(@@join_buffer_size/1024/1024, 0), 'M'
      ) as value;
    column: "value"

And, as a basic example, it is possible to see all these SQL queries on the dashboard by creating a card like this:

Card yaml
type: vertical-stack
cards:
  - type: entities
    title: Database Statistics
    entities:
      - entity: sensor.database_size
        name: Database Size
        icon: mdi:database
      - entity: sensor.database_tables_count
        name: Number of Tables
        icon: mdi:table
      - entity: sensor.database_total_records
        name: Total Records
        icon: mdi:counter
  - type: entities
    title: MariaDB Status
    entities:
      - entity: sensor.mariadb_status
        name: Server Status
        icon: mdi:database
      - entity: sensor.mariadb_version
        name: Version
        icon: mdi:database-check
      - entity: sensor.mariadb_performance
        name: Performance
        icon: mdi:chart-line
      - entity: sensor.mariadb_uptime
        name: Uptime
        icon: mdi:timer-outline
      - entity: sensor.mariadb_connections
        name: Active Connections
        icon: mdi:connection
      - entity: sensor.mariadb_questions
        name: Total Queries
        icon: mdi:database-search
  - type: grid
    cards:
      - type: custom:mushroom-template-card
        primary: InnoDB Buffer Pool
        secondary: "{{ states('sensor.mariadb_buffer_pool_size') }}"
        icon: mdi:memory
        layout: vertical
        icon_color: blue
      - type: custom:mushroom-template-card
        primary: Max Connections
        secondary: "{{ states('sensor.mariadb_max_connections') }}"
        icon: mdi:account-multiple
        layout: vertical
        icon_color: green
      - type: custom:mushroom-template-card
        primary: Log File Size
        secondary: "{{ states('sensor.mariadb_log_file_size') }}"
        icon: mdi:file
        layout: vertical
        icon_color: orange
      - type: custom:mushroom-template-card
        primary: Temp Table Size
        secondary: "{{ states('sensor.mariadb_tmp_table_size') }}"
        icon: mdi:table
        layout: vertical
        icon_color: purple
      - type: custom:mushroom-template-card
        primary: IO Capacity
        secondary: "{{ states('sensor.mariadb_io_capacity') }}"
        icon: mdi:speedometer
        layout: vertical
        icon_color: red
      - type: custom:mushroom-template-card
        primary: IO Threads
        secondary: "{{ states('sensor.mariadb_io_threads') }}"
        icon: mdi:developer-board
        layout: vertical
        icon_color: cyan
      - type: custom:mushroom-template-card
        primary: Table Cache
        secondary: "{{ states('sensor.mariadb_table_cache') }}"
        icon: mdi:cached
        layout: vertical
        icon_color: amber
      - type: custom:mushroom-template-card
        primary: Buffer Sizes
        secondary: "{{ states('sensor.mariadb_buffer_sizes') }}"
        icon: mdi:buffer
        layout: vertical
        icon_color: teal

Post created from my comment in this thread:

https://community.home-assistant.io/t/improve-your-home-assistant-performance-with-mariadb-tuning

2 Likes

Very nice work! Copy-pasted your code in the new MariaDB add-on configuration and it works great. Also really nice SQL sensors + dashboard. Thanks a bunch!

For those who have trouble finding the new mariadb_server_args, in UI mode, it looks like this:

But it’s a lot easier to switch to YAML mode and just copy-paste the code like so:

2 Likes

This is the post I needed.

ChatGPT whittled down the list to 14 items it says will actually make a difference. I don’t have the inclination to argue.

mariadb_server_args:
  - "--innodb_buffer_pool_size=1G"         # Cache for data/indexes (adjust if RAM-limited)
  - "--innodb_log_buffer_size=32M"         # Buffer for transaction logs
  - "--innodb_log_file_size=256M"          # Less frequent log writes
  - "--innodb_file_per_table=1"            # Isolate table data into separate files
  - "--innodb_flush_log_at_trx_commit=2"   # Fast + safe enough for HA
  - "--innodb_io_capacity=1000"            # SSD-optimized
  - "--innodb_io_capacity_max=2000"        # Burst IOPS ceiling
  - "--innodb_read_io_threads=4"
  - "--innodb_write_io_threads=4"
  - "--performance_schema=OFF"             # Less overhead
  - "--skip-log-bin"                       # Disables binary logs (not used by HA)
  - "--skip-name-resolve"                  # Faster connections
  - "--slow_query_log=1"                   # Optional: helpful for profiling
  - "--long_query_time=2"

Thanks for the maria-db config, sql sensors and dashboard-card.
But I’m wondering if I’m the only one who gets in trouble when configuring the SQL entities for db monitoring. Those entities are pushing my cpu load by roughly 30% up and I’m getting log entries telliing me, that the query execution takes longer than the configured polling rate of 30 seconds. Did you tweak something to get it to reliably work?