Reuse MySQL connection across flushes; never swallow flush errors - #73
Reuse MySQL connection across flushes; never swallow flush errors#73y-ken wants to merge 1 commit into
Conversation
out_mysql_bulk's write/1 opened a fresh MySQL connection on every flush and closed it only on the success path. That had two problems: * A failing INSERT (e.g. a NOT NULL violation on bad input) skipped the close and leaked the connection on every Fluentd retry. * Even on success, reconnecting for each flush is wasteful. Now the connection is kept open and reused across successful flushes, and reconnected only when there is no usable handler or when the target database changed (the INSERT uses an unqualified table name, so it must run on a connection bound to the right database). On error the connection is dropped so the next retry reconnects (this also recovers from a server-side "gone away"), and the held connection is released on plugin shutdown via #close. The error is deliberately NOT rescued-and-swallowed. PR #65 proposed `rescue Exception` + log.warn to stop endless retries, but swallowing the error makes Fluentd treat the flush as successful and purge the chunk, silently dropping every buffered record in it (a whole bulk batch, not just the bad row). Letting it propagate keeps Fluentd's retry / secondary handling intact; operators who want to cap retries on permanently-bad data should use retry_max_times / <secondary>, which this change leaves working. Note: as before, write/1 uses a single @handler instance variable and is not safe for flush_thread_count > 1; that pre-existing limitation is unchanged. Add regression tests: - unit (no live MySQL): a stubbed handler proves write/1 reuses one connection across successful flushes and closes it on shutdown, and that a failing INSERT re-raises while closing and dropping the connection. - integration: a NOT NULL violation (the exact error from PR #65) must raise out of write/1 and insert nothing; a second successful flush reuses the same connection. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
cfc62bb to
dd683d7
Compare
Design rationale (for reviewers)Two coupled changes here. Writing the reasoning down so the trade-offs are explicit. 1. Errors must propagate out of
|
Summary
out_mysql_bulk'swrite/1opened a fresh MySQL connection on every flush and closed it only on the success path. Two problems:INSERT(e.g. aNOT NULLviolation on bad input) skipped the close and leaked the connection on every Fluentd retry.This PR makes the connection persist and be reused across successful flushes, while keeping errors propagating correctly.
Behavior
INSERTuses an unqualified table name, so it must run on a connection bound to the right database (placeholder-drivendatabasecan vary per chunk).#close.Why not swallow the error (re: #65)
#65 proposes
rescue Exception+log.warnto stop the endless retries the author saw. That removes the symptom but introduces a worse one:write/1raises. Swallowing makes Fluentd treat the flush as successful and purge the chunk → every buffered record is silently dropped. Because this is a bulk insert, one bad row fails the whole statement, so the entire batch (potentially thousands of good rows) is lost.rescue Exception(vsStandardError) also swallowsInterrupt/SignalException, and the proposed log line drops the exception class/message.Operators who want to cap retries on permanently-bad data should use Fluentd's own
retry_max_times/<secondary>, which this change leaves working.Known limitation (pre-existing)
As before,
write/1uses a single@handlerinstance variable and is not safe forflush_thread_count > 1. This PR does not change that; thread-local/pooled connections would be a separate, larger change.Tests
test_out_mysql_bulk.rb, no live MySQL): a stubbed handler proveswrite/1reuses one connection across successful flushes and closes it on shutdown, and that a failingINSERTre-raises while closing and dropping the connection.test_out_mysql_bulk_integration.rb): aNOT NULLviolation (the exact error from Add try,catch statement for query #65) must raise out ofwrite/1and insert nothing; a second successful flush reuses the same connection object.Supersedes #65.