Andrei Elkin wrote:
> Dao-Gang, hello.
>
> Although the patch looks really good - I only have some cosmetics-wise comments -
> I have one question for the whole idea.
>
> The patch introduces the global limit whereas a limit per session might be a better
> solution.
> The global limitting has a drawback that if for instance two connectons try logging
> the limit value can be hit by the first-and-only error message of the 2nd session.
> Therefore the error-log observer would miss that 2nd session error message.
>
> Was that really disscussed before?
>
Yes. We have discussed it. The 'a limit per session' solution will make the
volume of messages become uncontrollable, because the number of session is
inconstant.
>
> Please find my comments inlined.
>
> cheers,
>
> Andrei
>
>
>
>> #At file:///home/daogangqu/mysql/bzrwork/bug42851/mysql-5.1-bugteam/ based on
> revid:davi.arnaut@stripped
>>
>> 3192 Dao-Gang.Qu@stripped 2009-11-11
>> Bug #42851 Spurious "Statement is not safe to log in statement format."
> warnings
>>
>> Warnings in error log make error log grow too large.
>>
>> The problem can be resolved by limiting the rate of messages that are
>> written to the log. A volume of messages that is less than or equal to
>> the specified rate is written to the log, whereas the volume of messages
>> that exceeds the rate is discarded.
>>
>> For example,
>>
>> log-warnings-ratelimit-interval = 10
>> log-warnings-ratelimit-burst = 5
>>
>> This allows 5 log messages per 10 seconds. The sixth (and posterior)
>> attempts to write a log message within a 10 seconds interval are
>> discarded.
>>
>
> good.
>
>
>> @ mysql-test/suite/rpl/r/rpl_ratelimit_warnings.result
>> Test Result for BUG#42851.
>> @ mysql-test/suite/rpl/t/rpl_ratelimit_warnings.test
>> Added the test file to verify if the rate limit works fine.
>> @ sql/log.cc
>> Added the implementation of the 'Rate_limit' class.
>> @ sql/log.h
>> Added the definition of the 'Rate_limit' class.
>> @ sql/mysqld.cc
>> Added OPT_LOG_WARNINGS_RATELIMIT_INTERVAL and
> OPT_LOG_WARNINGS_RATELIMIT_BURST
>> options for rate limit.
>>
>> added:
>> mysql-test/suite/rpl/r/rpl_ratelimit_warnings.result
>> mysql-test/suite/rpl/t/rpl_ratelimit_warnings-master.opt
>> mysql-test/suite/rpl/t/rpl_ratelimit_warnings-slave.opt
>> mysql-test/suite/rpl/t/rpl_ratelimit_warnings.test
>> modified:
>> sql/log.cc
>> sql/log.h
>> sql/mysqld.cc
>> === added file 'mysql-test/suite/rpl/r/rpl_ratelimit_warnings.result'
>> --- a/mysql-test/suite/rpl/r/rpl_ratelimit_warnings.result 1970-01-01 00:00:00
> +0000
>> +++ b/mysql-test/suite/rpl/r/rpl_ratelimit_warnings.result 2009-11-11 06:45:07
> +0000
>> @@ -0,0 +1,26 @@
>> +stop slave;
>> +drop table if exists t1,t2,t3,t4,t5,t6,t7,t8,t9;
>> +reset master;
>> +reset slave;
>> +drop table if exists t1,t2,t3,t4,t5,t6,t7,t8,t9;
>> +start slave;
>> +call mtr.add_suppression("Burst exceeded, rate limiting.");
>> +call mtr.add_suppression("Rate limit lifted, .* warning messages were
> suppressed.");
>> +CREATE TABLE `t1` (
>> +`recNo` int(10) unsigned NOT NULL AUTO_INCREMENT,
>> +`string` varchar(64) NOT NULL,
>> +`inUseBy` varchar(38) NOT NULL DEFAULT '',
>> +`tsLastUpdated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE
>> +CURRENT_TIMESTAMP,
>> +PRIMARY KEY (`recNo`),
>> +KEY `tsLastUpdated` (`tsLastUpdated`),
>> +KEY `inUseBy` (`inUseBy`)
>> +);
>> +INSERT INTO t1 SET string='one';
>> +INSERT INTO t1 SET string='two';
>> +INSERT INTO t1 SET string='three';
>> +# above query will produce a warning
>> +SHOW WARNINGS;
>> +Level Code Message
>> +Note 1592 Statement may not be safe to log in statement format.
>> +drop table t1;
>>
>> === added file 'mysql-test/suite/rpl/t/rpl_ratelimit_warnings-master.opt'
>> --- a/mysql-test/suite/rpl/t/rpl_ratelimit_warnings-master.opt 1970-01-01
> 00:00:00 +0000
>> +++ b/mysql-test/suite/rpl/t/rpl_ratelimit_warnings-master.opt 2009-11-11
> 06:45:07 +0000
>> @@ -0,0 +1 @@
>> +--log-warnings-ratelimit-interval=10 --log-warnings-ratelimit-burst=5
>>
>> === added file 'mysql-test/suite/rpl/t/rpl_ratelimit_warnings-slave.opt'
>> --- a/mysql-test/suite/rpl/t/rpl_ratelimit_warnings-slave.opt 1970-01-01 00:00:00
> +0000
>> +++ b/mysql-test/suite/rpl/t/rpl_ratelimit_warnings-slave.opt 2009-11-11 06:45:07
> +0000
>> @@ -0,0 +1 @@
>> +--log-warnings-ratelimit-interval=10 --log-warnings-ratelimit-burst=5
>>
>> === added file 'mysql-test/suite/rpl/t/rpl_ratelimit_warnings.test'
>> --- a/mysql-test/suite/rpl/t/rpl_ratelimit_warnings.test 1970-01-01 00:00:00
> +0000
>> +++ b/mysql-test/suite/rpl/t/rpl_ratelimit_warnings.test 2009-11-11 06:45:07
> +0000
>> @@ -0,0 +1,44 @@
>> +#
>> +# BUG#42851
>> +# This test verifies if rate limit allows at most 5 log messages
>> +# per 10 seconds. The sixth (and posterior) attempts to write a
>> +# log message within a 10 seconds interval are discarded.
>> +#
>> +
>> +source include/master-slave.inc;
>> +source include/have_binlog_format_statement.inc;
>> +
>> +call mtr.add_suppression("Burst exceeded, rate limiting.");
>> +call mtr.add_suppression("Rate limit lifted, .* warning messages were
> suppressed.");
>> +# create table
>> +CREATE TABLE `t1` (
>> + `recNo` int(10) unsigned NOT NULL AUTO_INCREMENT,
>> + `string` varchar(64) NOT NULL,
>> + `inUseBy` varchar(38) NOT NULL DEFAULT '',
>> + `tsLastUpdated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE
>> +CURRENT_TIMESTAMP,
>> + PRIMARY KEY (`recNo`),
>> + KEY `tsLastUpdated` (`tsLastUpdated`),
>> + KEY `inUseBy` (`inUseBy`)
>> +);
>> +# insert test data
>> +INSERT INTO t1 SET string='one';
>> +INSERT INTO t1 SET string='two';
>> +INSERT INTO t1 SET string='three';
>> +# grab one record
>> +let $count=5000;
>> +--disable_warnings
>> +--disable_query_log
>> +while ($count)
>> +{
>> + UPDATE t1 SET inUseBy='me' WHERE inUseBy='' limit 1;
>> + dec $count;
>> +}
>> +--enable_query_log
>> +--enable_warnings
>> +--echo # above query will produce a warning
>> +SHOW WARNINGS;
>> +
>> +drop table t1;
>> +sync_slave_with_master;
>> +
>>
>> === modified file 'sql/log.cc'
>> --- a/sql/log.cc 2009-10-23 00:03:41 +0000
>> +++ b/sql/log.cc 2009-11-11 06:45:07 +0000
>> @@ -51,6 +51,8 @@ LOGGER logger;
>>
>> MYSQL_BIN_LOG mysql_bin_log;
>> ulong sync_binlog_counter= 0;
>> +ulong log_warnings_ratelimit_interval;
>> +ulong log_warnings_ratelimit_burst;
>>
>> static bool test_if_number(const char *str,
>> long *res, bool allow_wildcards);
>> @@ -737,11 +739,80 @@ bool Log_to_csv_event_handler::
>> return FALSE;
>> }
>>
>> +
>> +Rate_limit::Rate_limit()
>> + : m_count(0), m_suppressed(0), m_begin(0),
>> + interval(0), burst(0)
>> +{}
>> +
>> +Rate_limit::~Rate_limit()
>> +{}
>> +
>> +void Rate_limit::rate_limit_exceeded()
>> +{}
>> +
>> +void Rate_limit::rate_limit_reset(unsigned int suppressed)
>> +{}
>> +
>> +bool Rate_limit::rate_limit_impl()
>> +{
>> + unsigned int suppressed= 0;
>> + bool exceeds, notify_exceeded= false;
>> +
>> + if (!m_begin)
>> + m_begin= my_time(0);
>> +
>> + if ((m_begin + interval) < my_time(0))
>> + {
>> + /* Interval has elapsed, reset counters. */
>> + suppressed= m_suppressed;
>> + m_begin= 0;
>> + m_count= m_suppressed= 0;
>> + }
>> +
>> + /* Whether burst limit has been surpassed. */
>> + exceeds= (burst && burst <= m_count);
>> +
>> + /*
>> + Issue a notification callback only if the the
>> + limit has been surpassed for the first time.
>> + */
>> + if (exceeds)
>> + notify_exceeded= !(m_suppressed++);
>> + else
>> + m_count++;
>> +
>> + if (suppressed)
>> + rate_limit_reset(suppressed);
>> +
>> + if (notify_exceeded)
>> + rate_limit_exceeded();
>> +
>> + return !exceeds;
>> +}
>> +
>> +
>> bool Log_to_file_event_handler::
>> log_error(enum loglevel level, const char *format,
>> va_list args)
>> {
>> - return vprint_msg_to_log(level, format, args);
>> + bool rv= false;
>> +
>> + if ((level != WARNING_LEVEL) || rate_limit())
>> + rv= vprint_msg_to_log(level, format, args);
>> +
>> + return rv;
>> +}
>> +
>> +void Log_to_file_event_handler::rate_limit_exceeded()
>> +{
>>
>
>
>> + sql_print_error("Burst exceeded, rate limiting.");
>>
>
> I suggest slightly extend the text to make is certainly readable by
> a human being not just a programmer.
>
> " log-warnings-ratelimit-burst parameter's value (N) exceeded; discarding
> the rest of error messages "
>
It's better.
> where N stands for the actual value of the parameter.
>
>
>
>> +}
>> +
>> +void Log_to_file_event_handler::rate_limit_reset(unsigned int suppressed)
>> +{
>> + sql_print_error("Rate limit lifted, %u warning messages were suppressed.",
>> + suppressed);
>> }
>>
>> void Log_to_file_event_handler::init_pthread_objects()
>>
>> === modified file 'sql/log.h'
>> --- a/sql/log.h 2009-06-18 13:52:46 +0000
>> +++ b/sql/log.h 2009-11-11 06:45:07 +0000
>> @@ -16,6 +16,9 @@
>> #ifndef LOG_H
>> #define LOG_H
>>
>> +extern ulong log_warnings_ratelimit_interval;
>> +extern ulong log_warnings_ratelimit_burst;
>> +
>> class Relay_log_info;
>>
>> class Format_description_log_event;
>> @@ -459,15 +462,87 @@ public:
>> };
>>
>>
>> +/**
>> + Rate limiter.
>> +*/
>> +
>> +class Rate_limit
>> +{
>> + public:
>> + Rate_limit();
>> + virtual ~Rate_limit();
>> +
>> + private:
>> + unsigned int interval;
>> + unsigned int burst;
>> + unsigned int m_count;
>> + unsigned int m_suppressed;
>> + time_t m_begin;
>> +
>> + private:
>> + bool rate_limit_impl();
>> +
>> + protected:
>> + /** Rate limit exceeded notification. */
>> + virtual void rate_limit_exceeded();
>> +
>> + /**
>> + Rate limit lifted notification.
>> +
>> + @param suppressed Number of suppressed attempts.
>> + */
>> + virtual void rate_limit_reset(unsigned int suppressed);
>> +
>> + public:
>> + /**
>> + Enforce a rate limit based on the number of successive
>> + calls per time interval, e.g. Rate_limit::burst calls
>> + per Rate_limit::interval.
>> +
>> + @remark Designed to mitigate DoS attacks.
>> +
>> + @return Whether a attempt falls within the limit.
>> + @retval true Within rate limit.
>> + @retval false Rate limit exceeded.
>> + */
>> + bool rate_limit()
>> + {
>> + return interval ? rate_limit_impl() : true;
>> + }
>> +
>> + /**
>> + Set interval of rate limit.
>> + @param The interval of rate limit.
>> + */
>> + void set_interval(unsigned int ratelimit_interval)
>> + {
>> + interval= ratelimit_interval;
>> + }
>> +
>> + /**
>> + Set burst of rate limit.
>> + @param The burst of rate limit.
>> + */
>> + void set_burst(unsigned int ratelimit_burst)
>> + {
>> + burst= ratelimit_burst;
>> + }
>> +};
>> +
>> +
>> /* type of the log table */
>> #define QUERY_LOG_SLOW 1
>> #define QUERY_LOG_GENERAL 2
>>
>> -class Log_to_file_event_handler: public Log_event_handler
>> +class Log_to_file_event_handler: public Log_event_handler,
>> + public Rate_limit
>> {
>> MYSQL_QUERY_LOG mysql_log;
>> MYSQL_QUERY_LOG mysql_slow_log;
>> bool is_initialized;
>> +protected:
>> + void rate_limit_exceeded();
>> + void rate_limit_reset(unsigned int suppressed);
>> public:
>> Log_to_file_event_handler(): is_initialized(FALSE)
>> {}
>> @@ -565,6 +640,10 @@ public:
>> return file_log_handler->get_mysql_log();
>> return NULL;
>> }
>> + Log_to_file_event_handler *get_log_file_event_handler()
>> + {
>> + return file_log_handler;
>> + }
>> };
>>
>> enum enum_binlog_format {
>>
>> === modified file 'sql/mysqld.cc'
>> --- a/sql/mysqld.cc 2009-11-03 00:52:57 +0000
>> +++ b/sql/mysqld.cc 2009-11-11 06:45:07 +0000
>> @@ -5700,7 +5700,9 @@ enum options_mysqld
>> OPT_SLAVE_EXEC_MODE,
>> OPT_GENERAL_LOG_FILE,
>> OPT_SLOW_QUERY_LOG_FILE,
>> - OPT_IGNORE_BUILTIN_INNODB
>> + OPT_IGNORE_BUILTIN_INNODB,
>> + OPT_LOG_WARNINGS_RATELIMIT_INTERVAL,
>> + OPT_LOG_WARNINGS_RATELIMIT_BURST
>> };
>>
>>
>> @@ -6046,6 +6048,18 @@ log and this option justs turns on --log
>> (uchar**) &global_system_variables.log_warnings,
>> (uchar**) &max_system_variables.log_warnings, 0, GET_ULONG, OPT_ARG, 1,
> 0, 0,
>> 0, 0, 0},
>>
>
> Explanations of parameters for mysqld --help I would suggest to discuss with docs
> people. Jon would be great to master short english text here.
> I dare to suggest my personal version for the 2 params.
>
>
>> + {"log-warnings-ratelimit-interval", OPT_LOG_WARNINGS_RATELIMIT_INTERVAL,
>> + "Minimum length of time between which successive warning messages are "
>> + "written to the log file.",
>>
>
> "Time interval within which error messages logging is limitted according to
> --log-warnings-ratelimit-burst value"
>
>
It's better. I will take it if Jon has no advice for it.
>> + (uchar**) &log_warnings_ratelimit_interval,
>> + (uchar**) &log_warnings_ratelimit_interval,
>> + 0, GET_ULONG, REQUIRED_ARG, 5, 0, 0, 0, 0, 0},
>>
>
>
>> + {"log-warnings-ratelimit-burst", OPT_LOG_WARNINGS_RATELIMIT_BURST,
>> + "Number of warning messages within a interval that shall trigger "
>> + "rate limiting.",
>>
>
> "The limit for the number of warnings that can be error-logged within
> --log-warnings-ratelimit-interval value; exceeding messages are not logged"
>
It's better. I will take it if Jon has no advice for it.
>
>> + (uchar**) &log_warnings_ratelimit_burst,
>> + (uchar**) &log_warnings_ratelimit_burst,
>> + 0, GET_ULONG, REQUIRED_ARG, 25, 0, 0, 0, 0, 0},
>> {"low-priority-updates", OPT_LOW_PRIORITY_UPDATES,
>> "INSERT/DELETE/UPDATE has lower priority than selects.",
>> (uchar**) &global_system_variables.low_priority_updates,
>> @@ -7860,6 +7874,7 @@ mysqld_get_one_option(int optid,
>> char *argument)
>> {
>> int error;
>> + Log_to_file_event_handler * log_file_event_handler;
>>
>> switch(optid) {
>> case '#':
>> @@ -8396,6 +8411,18 @@ mysqld_get_one_option(int optid,
>> lower_case_table_names= argument ? atoi(argument) : 1;
>> lower_case_table_names_used= 1;
>> break;
>> + case OPT_LOG_WARNINGS_RATELIMIT_INTERVAL:
>> + log_warnings_ratelimit_interval= atoi(argument);
>> + log_file_event_handler = logger.get_log_file_event_handler();
>> + if (log_file_event_handler)
>> + log_file_event_handler->set_interval(log_warnings_ratelimit_interval);
>> + break;
>> + case OPT_LOG_WARNINGS_RATELIMIT_BURST:
>> + log_warnings_ratelimit_burst= atoi(argument);
>> + log_file_event_handler = logger.get_log_file_event_handler();
>> + if (log_file_event_handler)
>> + log_file_event_handler->set_burst(log_warnings_ratelimit_burst);
>> + break;
>> #if defined(ENABLED_DEBUG_SYNC)
>> case OPT_DEBUG_SYNC_TIMEOUT:
>> /*
>>
>>
>> # Bazaar merge directive format 2 (Bazaar 0.90)
>> # revision_id: dao-gang.qu@stripped
>> # target_branch: file:///home/daogangqu/mysql/bzrwork/bug42851/mysql-\
>> # 5.1-bugteam/
>> # testament_sha1: 84e778735be5586e779c2f921f4dba84a204c22a
>> # timestamp: 2009-11-11 14:45:47 +0800
>> # base_revision_id: davi.arnaut@stripped\
>> # 4zfm2j149rypot06
>> #
>> # Begin bundle
>> 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
>> 7kkQHxlKYl8CJhhX1G6I/+LuSKcKEgWSIeOg
>>