Skip to content

Commit e8d2751

Browse files
committed
feat: release 2.8.41 final polish
- fix: workload-based InnoDB Redo Log Capacity for MySQL >= 8.0.30 (major#714, major#737, major#777) - fix: guards against division by zero in calculations() (major#435) - feat: table_open_cache_instances recommendation based on CPU cores (major#480) - fix: initialize $mysqllogin to avoid uninitialized value warnings (major#490) - docs: update PERLDOC and MEMORY_DB.md - test: add/update unit tests for v2.8.41 features
1 parent bac36cb commit e8d2751

7 files changed

Lines changed: 355 additions & 112 deletions

File tree

Changelog

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@
1818
- fix: include `tmp_table_size` in per-thread memory calculation for better accuracy (#864).
1919
- fix: add retry mechanism for initial `SELECT VERSION()` query to improve connection resilience (#782).
2020
- fix: prevent `AUTO_INCREMENT` capacity false positives for empty tables (#37).
21+
- fix: refactor InnoDB Redo Log Capacity logic to be workload-based and avoid false positives (#714, #737, #777).
22+
- fix: add guards against division by zero in calculations for improved stability (#435).
23+
- feat: add recommendation for `table_open_cache_instances` based on CPU cores (#480).
24+
- feat: improve syslog and systemd journal detection for error logs (#440).
25+
- feat: initialize `$mysqllogin` to avoid uninitialized value warnings (#490).
2126
- chore: automated project maintenance and cleanup (extracted `RULES.md`, `MEMORY_DB.md`, `TESTS.md`).
2227
- ci: migrate maintenance script to GitHub Actions.
2328
- chore(deps): update docker/setup-buildx-action action to v4.

MEMORY_DB.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ Migrated several external commands to native Core Perl to reduce fork overhead a
2626
- Resolved `AUTO_INCREMENT` capacity false positives for empty tables (#37).
2727
- Corrected `check_removed_innodb_variables` false positives for injected variables (#32).
2828
- Fixed `--defaults-file` usage to prevent dropping other connection options (#605).
29+
- Refactored InnoDB Redo Log Capacity logic to be workload-based for modern MySQL (#714, #737, #777).
30+
- Added recommendation for `table_open_cache_instances` based on CPU cores (#480).
31+
- Improved connection resilience with retry mechanism and fixed uninitialized `$mysqllogin` (#782, #490).
32+
- Added guards against division by zero in calculations for AWS Aurora compatibility (#435).
2933
- **v2.8.40**: Enhanced SSL/TLS security checks and cloud discovery (AWS RDS/Aurora, GCP, Azure).
3034
- **v2.8.38**: Fixed container startup failures in lab environments.
3135
- **v2.8.31**: Improved SQL check syntax for redundant index detection.

mysqltuner.pl

Lines changed: 153 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -1970,6 +1970,7 @@ sub execute_system_command {
19701970
}
19711971
19721972
sub mysql_setup {
1973+
$mysqllogin = '';
19731974
$doremote = 0;
19741975
$remotestring = '';
19751976
my $transport_prefix = get_transport_prefix();
@@ -5105,20 +5106,36 @@ sub calculations {
51055106
. $mycalc{'pct_max_physical_memory'} . "%";
51065107

51075108
# Slow queries
5108-
$mycalc{'pct_slow_queries'} =
5109-
int( ( $mystat{'Slow_queries'} / $mystat{'Questions'} ) * 100 );
5109+
if ( $mystat{'Questions'} > 0 ) {
5110+
$mycalc{'pct_slow_queries'} =
5111+
int( ( $mystat{'Slow_queries'} / $mystat{'Questions'} ) * 100 );
5112+
}
5113+
else {
5114+
$mycalc{'pct_slow_queries'} = 0;
5115+
}
51105116

51115117
# Connections
5112-
$mycalc{'pct_connections_used'} = int(
5113-
( $mystat{'Max_used_connections'} / $myvar{'max_connections'} ) * 100 );
5118+
if ( $myvar{'max_connections'} > 0 ) {
5119+
$mycalc{'pct_connections_used'} = int(
5120+
( $mystat{'Max_used_connections'} / $myvar{'max_connections'} ) *
5121+
100 );
5122+
}
5123+
else {
5124+
$mycalc{'pct_connections_used'} = 0;
5125+
}
51145126
$mycalc{'pct_connections_used'} =
51155127
( $mycalc{'pct_connections_used'} > 100 )
51165128
? 100
51175129
: $mycalc{'pct_connections_used'};
51185130

51195131
# Aborted Connections
5120-
$mycalc{'pct_connections_aborted'} =
5121-
percentage( $mystat{'Aborted_connects'}, $mystat{'Connections'} );
5132+
if ( $mystat{'Connections'} > 0 ) {
5133+
$mycalc{'pct_connections_aborted'} =
5134+
percentage( $mystat{'Aborted_connects'}, $mystat{'Connections'} );
5135+
}
5136+
else {
5137+
$mycalc{'pct_connections_aborted'} = 0;
5138+
}
51225139
debugprint "Aborted_connects: " . $mystat{'Aborted_connects'} . "";
51235140
debugprint "Connections: " . $mystat{'Connections'} . "";
51245141
debugprint "pct_connections_aborted: "
@@ -5239,13 +5256,20 @@ sub calculations {
52395256
$mycalc{'query_cache_efficiency'} = 0;
52405257
}
52415258
elsif ( mysql_version_ge(4) ) {
5242-
$mycalc{'query_cache_efficiency'} = sprintf(
5243-
"%.1f",
5244-
(
5245-
$mystat{'Qcache_hits'} /
5246-
( $mystat{'Com_select'} + $mystat{'Qcache_hits'} )
5247-
) * 100
5248-
);
5259+
if ( ( $mystat{'Com_select'} || 0 ) + ( $mystat{'Qcache_hits'} || 0 ) >
5260+
0 )
5261+
{
5262+
$mycalc{'query_cache_efficiency'} = sprintf(
5263+
"%.1f",
5264+
(
5265+
$mystat{'Qcache_hits'} /
5266+
( $mystat{'Com_select'} + $mystat{'Qcache_hits'} )
5267+
) * 100
5268+
);
5269+
}
5270+
else {
5271+
$mycalc{'query_cache_efficiency'} = 0;
5272+
}
52495273
if ( $myvar{'query_cache_size'} ) {
52505274
$mycalc{'pct_query_cache_used'} = sprintf(
52515275
"%.1f",
@@ -5254,28 +5278,40 @@ sub calculations {
52545278
) * 100
52555279
);
52565280
}
5257-
if ( $mystat{'Qcache_lowmem_prunes'} == 0 ) {
5281+
if ( ( $mystat{'Qcache_lowmem_prunes'} || 0 ) == 0 ) {
52585282
$mycalc{'query_cache_prunes_per_day'} = 0;
52595283
}
52605284
else {
5261-
$mycalc{'query_cache_prunes_per_day'} = int(
5262-
$mystat{'Qcache_lowmem_prunes'} / ( $mystat{'Uptime'} / 86400 )
5263-
);
5285+
if ( ( $mystat{'Uptime'} || 0 ) > 0 ) {
5286+
$mycalc{'query_cache_prunes_per_day'} = int(
5287+
$mystat{'Qcache_lowmem_prunes'} /
5288+
( $mystat{'Uptime'} / 86400 ) );
5289+
}
5290+
else {
5291+
$mycalc{'query_cache_prunes_per_day'} = 0;
5292+
}
52645293
}
52655294
}
52665295

52675296
# Sorting
5268-
$mycalc{'total_sorts'} = $mystat{'Sort_scan'} + $mystat{'Sort_range'};
5297+
$mycalc{'total_sorts'} =
5298+
( $mystat{'Sort_scan'} || 0 ) + ( $mystat{'Sort_range'} || 0 );
52695299
if ( $mycalc{'total_sorts'} > 0 ) {
52705300
$mycalc{'pct_temp_sort_table'} = int(
52715301
( $mystat{'Sort_merge_passes'} / $mycalc{'total_sorts'} ) * 100 );
52725302
}
52735303

52745304
# Joins
52755305
$mycalc{'joins_without_indexes'} =
5276-
$mystat{'Select_range_check'} + $mystat{'Select_full_join'};
5277-
$mycalc{'joins_without_indexes_per_day'} =
5278-
int( $mycalc{'joins_without_indexes'} / ( $mystat{'Uptime'} / 86400 ) );
5306+
( $mystat{'Select_range_check'} || 0 ) +
5307+
( $mystat{'Select_full_join'} || 0 );
5308+
if ( ( $mystat{'Uptime'} || 0 ) > 0 ) {
5309+
$mycalc{'joins_without_indexes_per_day'} = int(
5310+
$mycalc{'joins_without_indexes'} / ( $mystat{'Uptime'} / 86400 ) );
5311+
}
5312+
else {
5313+
$mycalc{'joins_without_indexes_per_day'} = 0;
5314+
}
52795315

52805316
# Temporary tables
52815317
if ( $mystat{'Created_tmp_tables'} > 0 ) {
@@ -5333,9 +5369,15 @@ sub calculations {
53335369
}
53345370

53355371
# Thread cache
5336-
$mycalc{'thread_cache_hit_rate'} =
5337-
int( 100 -
5338-
( ( $mystat{'Threads_created'} / $mystat{'Connections'} ) * 100 ) );
5372+
if ( ( $mystat{'Connections'} || 0 ) > 0 ) {
5373+
$mycalc{'thread_cache_hit_rate'} =
5374+
int( 100 -
5375+
( ( $mystat{'Threads_created'} / $mystat{'Connections'} ) * 100 )
5376+
);
5377+
}
5378+
else {
5379+
$mycalc{'thread_cache_hit_rate'} = 100;
5380+
}
53395381

53405382
# Other
53415383
if ( $mystat{'Connections'} > 0 ) {
@@ -5945,6 +5987,41 @@ sub mysql_stats {
59455987

59465988
push( @adjvars,
59475989
$table_cache_var . " (> " . $myvar{$table_cache_var} . ")" );
5990+
5991+
# table_open_cache_instances recommendation (#480)
5992+
if ( mysql_version_ge( 5, 6, 6 )
5993+
and $myvar{'version'} !~ /mariadb/i
5994+
and defined $myvar{'table_open_cache_instances'} )
5995+
{
5996+
my $logical_cpus = logical_cpu_cores();
5997+
if ( $logical_cpus > 1 ) {
5998+
my $recommended_instances =
5999+
POSIX::ceil( $logical_cpus * 0.5 );
6000+
6001+
# Cap at 16 (default for 8.0+)
6002+
$recommended_instances = 16 if $recommended_instances > 16;
6003+
if ( $myvar{'table_open_cache_instances'} <
6004+
$recommended_instances )
6005+
{
6006+
badprint
6007+
"table_open_cache_instances is set to $myvar{'table_open_cache_instances'} but $recommended_instances is recommended based on CPU cores.";
6008+
push( @adjvars,
6009+
"table_open_cache_instances (=$recommended_instances)"
6010+
);
6011+
}
6012+
}
6013+
}
6014+
elsif ( $myvar{'version'} =~ /mariadb/i
6015+
and mysql_version_ge( 10, 2, 2 ) )
6016+
{
6017+
if ( defined $myvar{'table_open_cache_instances'}
6018+
and $myvar{'table_open_cache_instances'} > 0 )
6019+
{
6020+
infoprint
6021+
"MariaDB 10.2.2+ autosizes table_open_cache_instances. Current value is $myvar{'table_open_cache_instances'}.";
6022+
}
6023+
}
6024+
59486025
push( @generalrec,
59496026
"Increase "
59506027
. $table_cache_var
@@ -9091,98 +9168,72 @@ sub mysql_innodb {
90919168
# InnoDB Log File Size / InnoDB Redo Log Capacity Recommendations
90929169
# For MySQL < 8.0.30, the recommendation is based on innodb_log_file_size and innodb_log_files_in_group.
90939170
# For MySQL >= 8.0.30, innodb_redo_log_capacity replaces the old system.
9094-
if ( mysql_version_ge( 8, 0, 30 )
9095-
&& defined $myvar{'innodb_redo_log_capacity'} )
9096-
{
9097-
# Recalculate ratio if needed (ensure accuracy for modern systems)
9098-
if ( defined $myvar{'innodb_buffer_pool_size'}
9099-
&& $myvar{'innodb_buffer_pool_size'} > 0 )
9100-
{
9101-
$mycalc{'innodb_log_size_pct'} =
9102-
( $myvar{'innodb_redo_log_capacity'} /
9103-
$myvar{'innodb_buffer_pool_size'} ) * 100;
9104-
}
9105-
9106-
infoprint "InnoDB Redo Log Capacity is set to "
9107-
. hr_bytes( $myvar{'innodb_redo_log_capacity'} );
9171+
if ( mysql_version_ge( 8, 0, 30 ) ) {
9172+
if ( defined $myvar{'innodb_redo_log_capacity'} ) {
9173+
infoprint "InnoDB Redo Log Capacity is set to "
9174+
. hr_bytes( $myvar{'innodb_redo_log_capacity'} );
91089175

9109-
if ( defined $myvar{'innodb_dedicated_server'}
9110-
and $myvar{'innodb_dedicated_server'} eq 'ON' )
9111-
{
9112-
goodprint
9176+
if ( defined $myvar{'innodb_dedicated_server'}
9177+
and $myvar{'innodb_dedicated_server'} eq 'ON' )
9178+
{
9179+
goodprint
91139180
"innodb_dedicated_server is ON. MySQL is managing Redo Log Capacity automatically.";
9114-
}
9115-
else {
9116-
my $innodb_os_log_written = $mystat{'Innodb_os_log_written'} || 0;
9117-
my $uptime = $mystat{'Uptime'} || 1;
9181+
if ( defined $opt{'defaults-file'} || defined $opt{'defaults-extra-file'} ) {
9182+
infoprint "If innodb_redo_log_capacity is manually set in config, consider removing it.";
9183+
}
9184+
}
9185+
else {
9186+
my $innodb_os_log_written = $mystat{'Innodb_os_log_written'} || 0;
9187+
my $uptime = $mystat{'Uptime'} || 1;
91189188

9119-
if ( $uptime > 3600 ) {
9120-
my $hourly_rate = $innodb_os_log_written / ( $uptime / 3600 );
9121-
infoprint "Hourly InnoDB log write rate: "
9122-
. hr_bytes($hourly_rate) . "/hour";
9189+
if ( $uptime > 3600 ) {
9190+
my $hourly_rate = $innodb_os_log_written / ( $uptime / 3600 );
9191+
infoprint "Hourly InnoDB log write rate: "
9192+
. hr_bytes($hourly_rate) . "/hour";
91239193

9124-
# Determine recommendation based on RAM and workload
9125-
my $recommended_bytes = $hourly_rate;
9126-
my $recommended_str = "";
9194+
# Determine recommendation based on workload
9195+
my $recommended_bytes = $hourly_rate;
91279196

9128-
if ( $physical_memory < 2 * 1024 * 1024 * 1024 ) {
9197+
# Sensible minimum based on RAM
9198+
my $min_bytes = 100 * 1024 * 1024;
9199+
if ( $physical_memory > 8 * 1024 * 1024 * 1024 ) {
9200+
$min_bytes = 1 * 1024 * 1024 * 1024;
9201+
}
9202+
$recommended_bytes = $min_bytes if $recommended_bytes < $min_bytes;
91299203

9130-
# < 2GB RAM: Default to 100MB if workload is low
9131-
$recommended_bytes = 100 * 1024 * 1024
9132-
if $recommended_bytes < 100 * 1024 * 1024;
9204+
# Cap at 16GB
9205+
my $max_bytes = 16 * 1024 * 1024 * 1024;
9206+
$recommended_bytes = $max_bytes if $recommended_bytes > $max_bytes;
91339207

9134-
# Round to 100MB
9135-
$recommended_bytes =
9136-
POSIX::ceil( $recommended_bytes / ( 100 * 1024 * 1024 ) )
9137-
* ( 100 * 1024 * 1024 );
9138-
$recommended_str = hr_bytes($recommended_bytes);
9139-
}
9140-
elsif ( $physical_memory < 8 * 1024 * 1024 * 1024 ) {
9141-
9142-
# < 8GB RAM: Round to 100MB, max 1GB
9143-
$recommended_bytes = 100 * 1024 * 1024
9144-
if $recommended_bytes < 100 * 1024 * 1024;
9145-
$recommended_bytes =
9146-
POSIX::ceil( $recommended_bytes / ( 100 * 1024 * 1024 ) )
9147-
* ( 100 * 1024 * 1024 );
9148-
$recommended_bytes = 1 * 1024 * 1024 * 1024
9149-
if $recommended_bytes > 1 * 1024 * 1024 * 1024;
9150-
$recommended_str = hr_bytes($recommended_bytes);
9151-
}
9152-
else {
9153-
# >= 8GB RAM: Round to 1GB, max 16GB
9154-
$recommended_bytes = 1 * 1024 * 1024 * 1024
9155-
if $recommended_bytes < 1 * 1024 * 1024 * 1024;
9156-
$recommended_bytes =
9157-
POSIX::ceil( $recommended_bytes / ( 1024 * 1024 * 1024 ) )
9158-
* ( 1024 * 1024 * 1024 );
9159-
$recommended_bytes = 16 * 1024 * 1024 * 1024
9160-
if $recommended_bytes > 16 * 1024 * 1024 * 1024;
9161-
$recommended_str = hr_bytes($recommended_bytes);
9162-
}
9208+
# Rounding
9209+
if ( $recommended_bytes < 1024 * 1024 * 1024 ) {
9210+
$recommended_bytes =
9211+
POSIX::ceil( $recommended_bytes / ( 100 * 1024 * 1024 ) )
9212+
* ( 100 * 1024 * 1024 );
9213+
}
9214+
else {
9215+
$recommended_bytes =
9216+
POSIX::ceil( $recommended_bytes / ( 1024 * 1024 * 1024 ) )
9217+
* ( 1024 * 1024 * 1024 );
9218+
}
91639219

9164-
if ( $myvar{'innodb_redo_log_capacity'} <
9165-
$recommended_bytes * 0.9 )
9166-
{ # 10% tolerance
9167-
badprint
9168-
"Your innodb_redo_log_capacity is smaller than the recommended $recommended_str based on your workload and RAM.";
9169-
push( @adjvars,
9170-
"innodb_redo_log_capacity (>= $recommended_str)" );
9220+
my $recommended_str = hr_bytes($recommended_bytes);
9221+
if ( $myvar{'innodb_redo_log_capacity'} < $recommended_bytes * 0.9 ) {
9222+
badprint "Your innodb_redo_log_capacity is smaller than the recommended $recommended_str based on your workload.";
9223+
push @adjvars, "innodb_redo_log_capacity (>= $recommended_str)";
9224+
}
9225+
else {
9226+
goodprint "Your innodb_redo_log_capacity is sized correctly for your workload ($recommended_str recommended).";
9227+
}
91719228
}
91729229
else {
9173-
goodprint
9174-
"Your innodb_redo_log_capacity is sized correctly for your workload ($recommended_str recommended).";
9230+
infoprint "Server uptime is less than 1 hour. Cannot make a reliable recommendation for innodb_redo_log_capacity.";
91759231
}
91769232
}
9177-
else {
9178-
infoprint
9179-
"Server uptime is less than 1 hour. Cannot make a reliable recommendation for innodb_redo_log_capacity.";
9180-
}
91819233
}
91829234
}
9183-
elsif ( !mysql_version_ge( 8, 0, 30 ) ) {
9184-
9185-
# Keep existing logic for older versions
9235+
else {
9236+
# MySQL < 8.0.30: logic based on 25% ratio of buffer pool
91869237
if ( $mycalc{'innodb_log_size_pct'} < 20
91879238
or $mycalc{'innodb_log_size_pct'} > 30 )
91889239
{

releases/v2.8.41.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@
2323
- fix: include `tmp_table_size` in per-thread memory calculation for better accuracy (#864).
2424
- fix: add retry mechanism for initial `SELECT VERSION()` query to improve connection resilience (#782).
2525
- fix: prevent `AUTO_INCREMENT` capacity false positives for empty tables (#37).
26+
- fix: refactor InnoDB Redo Log Capacity logic to be workload-based and avoid false positives (#714, #737, #777).
27+
- fix: add guards against division by zero in calculations for improved stability (#435).
28+
- feat: add recommendation for `table_open_cache_instances` based on CPU cores (#480).
29+
- feat: improve syslog and systemd journal detection for error logs (#440).
30+
- feat: initialize `$mysqllogin` to avoid uninitialized value warnings (#490).
2631
- chore: automated project maintenance and cleanup (extracted `RULES.md`, `MEMORY_DB.md`, `TESTS.md`).
2732
- ci: migrate maintenance script to GitHub Actions.
2833
- chore(deps): update docker/setup-buildx-action action to v4.
@@ -56,6 +61,11 @@
5661
- fix: include tmp_table_size in per-thread memory calculation (#864)
5762
- fix: add retry mechanism for initial SELECT VERSION() query (#782)
5863
- fix: prevent AUTO_INCREMENT capacity false positives for empty tables (#37)
64+
- fix: refactor InnoDB Redo Log Capacity logic to be workload-based and avoid false positives (#714, #737, #777)
65+
- fix: add guards against division by zero in calculations for improved stability (#435)
66+
- feat: add recommendation for table_open_cache_instances based on CPU cores (#480)
67+
- feat: improve syslog and systemd journal detection for error logs (#440)
68+
- feat: initialize $mysqllogin to avoid uninitialized value warnings (#490)
5969
- fix: wrap template loading in get_template_model() to avoid warnings during require
6070
- fix: Restore compatibility with older Perl versions
6171
- chore: automated project maintenance and cleanup (extracted RULES.md, MEMORY_DB.md, TESTS.md)

tests/innodb_redo_log_capacity_logic.t

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,8 @@ subtest 'Large RAM, Low Workload' => sub {
6969

7070
main::mysql_innodb();
7171

72-
ok(grep(/innodb_redo_log_capacity \(>= 1.0G\)/, @main::adjvars), 'Suggested 1.0G instead of 16GB (25% of BP)');
72+
# Workload is 500MB/h, Rounded to 1GB minimum since RAM > 8GB
73+
ok(grep(/innodb_redo_log_capacity \(>= 1.0G\)/, @main::adjvars), 'Suggested 1.0G based on workload and RAM minimum');
7374
};
7475

7576
# Test Case 3: Dedicated Server

0 commit comments

Comments
 (0)