Skip to content

MDEV-37605 Extend mariadb-binlog to convert innodb based binary logs to legacy - #5276

Draft
tarunw07 wants to merge 1 commit into
MariaDB:mainfrom
tarunw07:MDEV-37605-convert-innodb-binlog-to-legacy
Draft

tarunw07 wants to merge 1 commit into
MariaDB:mainfrom
tarunw07:MDEV-37605-convert-innodb-binlog-to-legacy

Conversation

@tarunw07

@tarunw07 tarunw07 commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR implements MDEV-37605: Convert InnoDB Binary Logs to Legacy Format.

The goal of this work is to extend mariadb-binlog so that it can read InnoDB-based binary log files (.ibb) and generate legacy-format binary log files as output. This is intended to support compatibility use cases such as downgrades, migrations, and tooling that expects the legacy binlog format.

The current approach is to avoid fully deserializing and reserializing regular events. Since most event contents are already encoded in the .ibb file, the converter should mostly copy event bytes into the generated legacy binlog, while handling FORMAT_DESCRIPTION_EVENT, GTID_LIST_EVENT, BINLOG_CHECKPOINT_EVENT & ROTATE_EVENT.

Current status

This PR currently includes:

  • New option: --convert-engine-binlog
  • Initial legacy binlog writer/conversion path
  • FORMAT_DESCRIPTION_EVENT handling
  • Generate/write GTID_LIST_EVENT
  • Use the initial FSP_BINLOG_TYPE_GTID_STATE from the first page of the .ibb file to initialize the converter’s GTID state which is used for GTID_LIST_EVENT
  • Rotate the output binlog based on the size specified by --max-binlog-size.
  • Handle ROTATE_EVENT
  • Generate/write BINLOG_CHECKPOINT_EVENT
  • Update end_log_pos correctly in generated legacy binlog events
  • Test coverage for generating a .ibb file, converting it to legacy binlog format, and checking the converted output

Tests included

  • Basic test: Converts a basic InnoDB binlog and checks that the converted file can be replayed correctly.
  • Max-size test: Checks that the output binlog rotates when --max-binlog-size is provided.
  • GTID list test: Checks that the right GTID state is added to each converted binlog.
  • Replication test: Checks that a slave can replicate converted events.
  • Unsupported-options test: Checks that unsupported options and legacy binlog inputs are rejected.

Design notes

The current design direction is:

  • Copy regular events byte-by-byte where possible.
  • Add serializer/generation logic for events(FORMAT_DESCRIPTION_EVENT, GTID_LIST_EVENT, BINLOG_CHECKPOINT_EVENT & ROTATE_EVENT) that must be synthesized for legacy binlog output.
  • Use the initial FSP_BINLOG_TYPE_GTID_STATE from the first page of the .ibb file to initialize the converter’s GTID state.
  • Maintain GTID state during conversion, using rpl_binlog_state_base, so that GTID_LIST_EVENT can be generated correctly at the beginning of each output legacy binlog file.
  • Write a synthetic/dummy BINLOG_CHECKPOINT_EVENT near the beginning of each generated legacy binlog file.
  • Update end_log_pos for all the generated legacy binlog events.
  • Rotate output legacy binlogs when needed

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces the --convert-engine-binlog option to mariadb-binlog for converting InnoDB-based engine binlog files into legacy-format binary logs, along with helper functions for event serialization and new test suites. The code review identified several critical issues, including potential NULL pointer dereferences of gtid_state, fdev, gtid_set, and list, as well as a memory leak on write failures in write_event_to_legacy_binlog. Additionally, the feedback highlights return type mismatches, inconsistent boolean usage, and multiple coding standard violations regarding spacing after if keywords.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread client/mysqlbinlog.cc Outdated
Comment on lines +1951 to +1957
if (ev->get_type_code() == GTID_EVENT) {
rpl_gtid ev_gtid;
Gtid_log_event *gle= (Gtid_log_event*) ev;
ev_gtid= {gle->domain_id, gle->server_id, gle->seq_no};

gtid_state->update_nolock(&ev_gtid);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

If gtid_state is not initialized (e.g., if the conversion starts or encounters a GTID_EVENT before gtid_state is allocated), dereferencing it via gtid_state->update_nolock(&ev_gtid) will cause a crash. We should ensure gtid_state is initialized or add a defensive NULL check.

  if (ev->get_type_code() == GTID_EVENT) {
    rpl_gtid ev_gtid;
    Gtid_log_event *gle= (Gtid_log_event*) ev;
    ev_gtid= {gle->domain_id, gle->server_id, gle->seq_no};

    if (gtid_state)
      gtid_state->update_nolock(&ev_gtid);
  }

Comment thread client/mysqlbinlog.cc Outdated
Comment thread sql/log_event.cc Outdated
Comment on lines +2838 to +2866
bool
Gtid_list_log_event::to_packet(String *packet)
{
uint32 i;
uchar *p;
uint32 needed_length;

DBUG_ASSERT(count < 1<<28);

needed_length= packet->length() + get_data_size();
if (packet->reserve(needed_length))
return true;
p= (uchar *)packet->ptr() + packet->length();;
packet->length(needed_length);
int4store(p, (count & ((1<<28)-1)) | gl_flags);
p += 4;
/* Initialise the padding for empty Gtid_list. */
if (count == 0)
int2store(p, 0);
for (i= 0; i < count; ++i)
{
int4store(p, list[i].domain_id);
int4store(p+4, list[i].server_id);
int8store(p+8, list[i].seq_no);
p += 16;
}

return false;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

If list is NULL (which can happen if my_malloc failed during construction or if the event is invalid), dereferencing list[i] in the loop will cause a crash. We should check is_valid() or list != NULL before proceeding. This also cleans up a double semicolon typo on line 2850.

bool
Gtid_list_log_event::to_packet(String *packet)
{
  uint32 i;
  uchar *p;
  uint32 needed_length;

  if (!is_valid())
    return true;

  DBUG_ASSERT(count < 1<<28);

  needed_length= packet->length() + get_data_size();
  if (packet->reserve(needed_length))
    return true;
  p= (uchar *)packet->ptr() + packet->length();
  packet->length(needed_length);
  int4store(p, (count & ((1<<28)-1)) | gl_flags);
  p += 4;
  /* Initialise the padding for empty Gtid_list. */
  if (count == 0)
    int2store(p, 0);
  for (i= 0; i < count; ++i)
  {
    int4store(p, list[i].domain_id);
    int4store(p+4, list[i].server_id);
    int8store(p+8, list[i].seq_no);
    p += 16;
  }

  return false;
}

Comment thread sql/log_event_client.cc
Comment on lines +2362 to +2373
Gtid_list_log_event::Gtid_list_log_event(rpl_binlog_state_base *gtid_set)
: count(gtid_set->count_nolock()), gl_flags(0), list(0), sub_id_list(0)
{
cache_type= EVENT_NO_CACHE;
/* Failure to allocate memory will be caught by is_valid() returning false. */
if (count < (1<<28) &&
(list = (rpl_gtid *)my_malloc(PSI_INSTRUMENT_ME,
count * sizeof(*list) + (count == 0), MYF(MY_WME))))
{
gtid_set->get_gtid_list_nolock(list, count);
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

If gtid_set is NULL, calling gtid_set->count_nolock() in the initializer list will result in a NULL pointer dereference and crash. We should add a defensive check to handle a NULL gtid_set gracefully.

Gtid_list_log_event::Gtid_list_log_event(rpl_binlog_state_base *gtid_set)
  : count(gtid_set ? gtid_set->count_nolock() : 0), gl_flags(0), list(0), sub_id_list(0)
{
  cache_type= EVENT_NO_CACHE;
  /* Failure to allocate memory will be caught by is_valid() returning false. */
  if (gtid_set && count < (1<<28) &&
      (list = (rpl_gtid *)my_malloc(PSI_INSTRUMENT_ME,
                          count * sizeof(*list) + (count == 0), MYF(MY_WME))))
  {
    gtid_set->get_gtid_list_nolock(list, count);
  }
}

Comment thread client/mysqlbinlog.cc Outdated
Comment thread client/mysqlbinlog.cc Outdated
Comment thread client/mysqlbinlog.cc Outdated
Comment thread client/mysqlbinlog.cc Outdated
Comment thread client/mysqlbinlog.cc Outdated
Comment thread client/mysqlbinlog.cc Outdated
@ParadoxV5
ParadoxV5 marked this pull request as draft June 25, 2026 05:22
@ParadoxV5 ParadoxV5 added GSoC External Contribution All PRs from entities outside of MariaDB Foundation, Corporation, Codership agreements. Replication Patches involved in replication labels Jun 25, 2026
@tarunw07
tarunw07 force-pushed the MDEV-37605-convert-innodb-binlog-to-legacy branch 3 times, most recently from 528c9e1 to da82682 Compare June 29, 2026 22:54
Comment thread client/mysqlbinlog.cc Outdated
Comment on lines +1940 to +1944
static bool init_output_legacy_binlog(FILE **out_file, char *out_name,
size_t out_name_len)
size_t out_name_len, bool rotate)
{
if (rotate)
{

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having a separate rotate_output_legacy_binlog() that calls init_output_legacy_binlog() sounds better than shoving rotation inside “reïnitialization”, right?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, thanks for your input. Your suggestion seems like a much better design, so I’ll go ahead and make these changes. Thanks! :)

@tarunw07
tarunw07 force-pushed the MDEV-37605-convert-innodb-binlog-to-legacy branch 2 times, most recently from 2251204 to 937845a Compare July 16, 2026 10:04
@@ -1 +1,2 @@
--source include/not_embedded.inc

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ParadoxV5 @bnestere Can you review/confirm this change? mysqlbinlog_convert_engine_binlog_basic test was failing because it was running on embedded server. I could've added this line in the test but this felt like a better option.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, it makes sense.
Compare with have_log_bin.inc, which literally has:

source include/not_embedded.inc;

In your tests, all but mysqlbinlog_convert_engine_binlog_basic calls have_binlog_format_row.inc, which in turn calls have_log_bin.inc.
I see that most have_innodb_binlog.inc callers aren’t “affected” either because they transitively call have_log_bin.inc.

The other tests I found call not_embedded.inc directly, like your alternative solution; they are:

  • binlog_in_engine.mariabackup_binlog_dir
  • binlog_in_engine.mariabackup_binlogs
  • mariabackup_slave_provision.inc callers

I wonder if it even makes sense to include the entire have_log_bin.inc in have_innodb_binlog.inc, not just --log-bin in have_innodb_binlog.opt?

Suggested change
--source include/not_embedded.inc
--source include/have_log_bin.inc

Though a shortcoming with have_log_bin.inc is that it will run the test in each of the three content formats (Mixed (default), Statement, Row).
This is unnecessary for tests that are more about the container than the contained contents, including yours, hence the calls to have_binlog_format_X.inc.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you elaborate a bit more on the shortcoming? I’m not sure I fully understood it.
My intention was for mysqlbinlog_convert_engine_binlog_basic to run with all three binlog formats: ROW, STATEMENT, and MIXED.

I did consider the change you suggested, but I was hesitant because have_log_bin.inc internally sources include/no_view_protocol.inc, and I wasn’t sure whether that could introduce any unexpected side effects. That said, logically, including have_log_bin.inc from have_innodb_binlog.inc does seem coherent.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(answered in our fortnightly call)

@tarunw07
tarunw07 force-pushed the MDEV-37605-convert-innodb-binlog-to-legacy branch from 6e98f55 to 1557281 Compare July 26, 2026 20:38
@tarunw07
tarunw07 force-pushed the MDEV-37605-convert-innodb-binlog-to-legacy branch 2 times, most recently from 6748fdc to 842ff82 Compare August 6, 2026 20:45

@knielsen knielsen left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some initial review comments on the draft patch in email comment.

@knielsen

knielsen commented Aug 11, 2026 via email

Copy link
Copy Markdown
Member

@knielsen

knielsen commented Aug 12, 2026 via email

Copy link
Copy Markdown
Member

@knielsen

knielsen commented Aug 12, 2026 via email

Copy link
Copy Markdown
Member

@knielsen

knielsen commented Aug 13, 2026 via email

Copy link
Copy Markdown
Member

@tarunw07
tarunw07 force-pushed the MDEV-37605-convert-innodb-binlog-to-legacy branch from 5091a7e to b6eae6e Compare August 16, 2026 05:27
@knielsen

knielsen commented Aug 16, 2026 via email

Copy link
Copy Markdown
Member

@knielsen

knielsen commented Aug 18, 2026 via email

Copy link
Copy Markdown
Member

@ParadoxV5

ParadoxV5 commented Aug 18, 2026 via email

Copy link
Copy Markdown
Contributor

@tarunw07

Copy link
Copy Markdown
Contributor Author

Hi Kristian,

tarun wadhwa @.***> writes:
While implementing your suggested changes for the PR <#5276>, I ran into a few questions that I wanted to discuss with you before proceeding.
Thanks for explaining these points, Tarun. At the root of these issues is the awkward way that code is being shared between mysqlbinlog and the server, with a mess of #include of .cc sources and sprinkled #ifdef. This is causing a number of difficulties when trying to share code, including these two issues that I hadn't realised when I first commented:

Here, it would be good to introduce Rotate_log_event::to_packet() and use it > in Rotate_log_event::write(), similar to how it's done for eg. > Gtid_list_log_event. Then we don't duplicate the logic for serialising the > data for Rotate_log_event. > Here, you recommended extracting Rotate_log_event::to_packet(). However, instead of creating a Rotate_log_event object, I am currently generating its serialized representation directly and writing it to the output binlog file.
Aha, right. Generally, we have the code shared for reading Log_event subclasses shared with the mysqlbinlog, but not the ability to create new Log_event objects. I'd forgotten that the to_packet() methods work off an existing object. Then I think your original code is better than trying to introduce Log_event constructors into the mysqlbinlog tool. The serialisation of the Rotate_log_event is very simple, it's not a huge issue to have it duplicated here, no reason to introduce the complexity of trying to import the Log_event constructors just for this. If we wanted to share the code for the serialisation of ROTATE_EVENT (and BINLOG_CHECKPOINT_EVENT?), the approach we could take is to introduce the counterparts to the static methods like Gtid_log_event::peek(), and reuse such methods both in mysqlbinlog and inside the server in Rotate_log_event::to_packet(). But for now it's probably simpler to just accept a bit of code duplication here as the best compromise.

Thanks for the explanations. I'm fine with your original code in these two cases then. - Kristian.

Thanks for the clarification. That makes sense.

I will go ahead with my original code and proceed with remaining review changes.

Thanks,
Tarun

@tarunw07
tarunw07 force-pushed the MDEV-37605-convert-innodb-binlog-to-legacy branch from b6eae6e to d664f90 Compare August 20, 2026 01:24

@bnestere bnestere left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @tarunw07 !

This is looking very nice, it is so close to the finish line :) Thanks for all your hard work!

I've left a few notes, though overall, I'd like @knielsen to verify a few of my questions before you actually fix any of the bigger points I raise.

Comment thread client/mysqlbinlog.cc
{
if (rotate_output_legacy_binlog(&output_legacy_binlog_file,
out_file_name, sizeof(out_file_name)))
goto err;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will result in a double-free of the glob_description_event (i.e. at label err, ev is deleted; and then in cleanup(), glob_description_event is freed again). You should just be able to return ERROR_STOP here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noted. And is fixed now.

Comment thread client/mysqlbinlog.cc
}

/* Write the GTID_LIST_EVENT to the output legacy binlog file */
Gtid_list_log_event gle= Gtid_list_log_event(gtid_state);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After instantiating this, it should be validated (i.e.

Suggested change
Gtid_list_log_event gle= Gtid_list_log_event(gtid_state);
Gtid_list_log_event gle= Gtid_list_log_event(gtid_state);
if (!gle.is_valid())
{
...

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noted. I’ve added the validation now. Thanks for catching this! :)

Comment thread client/mysqlbinlog.cc
error("The --convert-engine-binlog option does not support GTID values "
"for --start-position or --stop-position");
die(1);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should be comprehensive in this list, i.e. also deny: --database, --table, --start-position (you already deny --stop-position earlier), --start-datetime, --offset, --do-domain-ids, --ignore-domain-ids, --do-server-ids, --ignore-server-ids,

Comment thread client/mysqlbinlog.cc Outdated
OK_CONTINUE)
goto end;

if (opt_convert_engine_binlog) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bracket should be on new line (and on the else at 4046)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for catching this. I’ve fixed it. I’ll run the formatter on all my changes when I squash my commits into a single commit.

Comment thread client/mysqlbinlog.cc Outdated
end:
if (output_legacy_binlog_file)
{
if (my_fclose(output_legacy_binlog_file, MYF(0)))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm - something doesn't seem right here. dump_local_log_entries would convert one binlog-in-innodb file to legacy. Though IIRC, we decided we didn't want a 1-to-1 relationship between innodb-binlog files to legacy binlog files. So here you close and nullify the output_legacy_binlog_flie, though IIUC, that initializes a new legacy file (with new counter) when the next innodb binlog file is read (or around then)

So what is this hunk trying to do?

FOUND 1 /Gtid list/ in conv_listing.txt
FOUND 1 /Binlog checkpoint conv.000001/ in conv_listing.txt
FOUND 1 /Start: binlog v 4/ in conv_listing.txt
FOUND 1 /server v 13.1.0-MariaDB/ in conv_listing.txt

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't output any hard server versions in result files, any time we have a new release, it would cause this to fail. I'd instead change this to use assert_grep.inc and assert the version is "correct" (there should be tons of examples of this in our existing suite).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense. I’ll replace the hardcoded server version with assert_grep.inc and validate that the version is correct. Thanks for pointing this out!

--source include/reset_master.inc

# Fixed timestamp for deterministic event listings (with --timezone=GMT-3
# from the -master.opt file, matching binlog_in_engine.mysqlbinlog).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm there is no mysqlbinlog_ocnvet_engine_binlog_basic-master.opt file, did you miss uploading it?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My bad. This comment was left over from the initial version of the test, and I missed removing it. I’ve removed it now.

Comment thread client/mysqlbinlog.cc Outdated
{

delete glob_description_event;
glob_description_event= (Format_description_log_event *) ev;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My understanding is that if the innodb binlog writes an FDE, it was due to a restart, and will have some created timestamp. This will drop temporary tables. Which is what we want for this FDE, but if we overwrite the glob_description_event with this FDE, then that means any future legacy binlogs we open (say due to reaching --max-binlog-size) will be written with a created field filled out to this value. This will cause temporary tables to drop for each legacy log rotation.

I think we don't want to delete/overwrite the glob_description_event - I think all we want to do is simply print the new found FDE (or maybe actually rotate? I'm not sure it matters either way, but a simple print is cheaper). This would also override my just-below comment about a double-free on the glob_description_event.

Though I'd like to defer to @knielsen to confirm

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have made the changes to not overwrite the glob_description_event when we encounter a FDE in innodb binlog. And therefore, we will use the dynamically generated FDE with updated timestamp, whenever we rotate the output legacy binlog based on the max-file-size.

@bnestere

Copy link
Copy Markdown
Contributor

@tarunw07 ah a couple more quick comments:

  • You have a lot of code comments like TODO: Tarun - these should be resolved now :) Please remove them
  • Please look through your full patch series on the command line with git show - you'll see a number of places with extraneous spaces at the end of lines. Please remove these.

@knielsen

knielsen commented Aug 24, 2026 via email

Copy link
Copy Markdown
Member

@tarunw07

tarunw07 commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

Hi @knielsen ,

Thank you for your input and for the detailed explanations.

The second approach is the correct one. The innodb binlog cannot meaningfully be considered as individual files like the legacy one can. It is possible for a single transaction to be spread across arbitrary number of binlog files. The split into individual files is there to make rotation simpler, basically, but the innodb binlog needs to be considered as a whole thing in its entirety. I do not think we can meaningfully try to handle .ibb files in the wrong order. Even if the user would specify eg. mysqlbinlog ... binlog-000003.ibb binlog-000001.ibb it may still be the case that both binlog-000001.ibb and binlog-000002.ibb are read first, in case binlog-000003.ibb refers back to out-of-band data in those two files. So the innodb binlog should be read continuously from one file into the other, a switch to the next file should not affect the output. Rotation of the output legacy binlog should occur only based on size (or before a restart FD event), and only at the end of an event group.

I have updated the implementation accordingly. The InnoDB binlog is now treated as one continuous stream, and crossing an .ibb file boundary no longer triggers rotation of the output legacy binlog. The output is rotated only when the size limit is reached or we encounter a FDE in the InnoDB binlog.

We need a test case that tests the conversion to legacy format when a large event group crosses from one file into the other, to see that the entire event group is included exactly once in the output, and that rotation of the legacy binlog is delayed until the end of the event group.

I have also added the test case which tests the conversion for InnoDB binlog with out of band (OOB) data.

Agree, good point Brandon. I didn't check the code in detail, but yes, the restart FD must only be output once (per occurrence in the innodb binlog).

Alright, this makes sense. I have made the changes to not overwrite the glob_description_event when we encounter a FDE in innodb binlog. And therefore, we will use the dynamically generated FDE with updated timestamp, whenever we rotate the output legacy binlog based on the max-file-size.

Thank you again for the detailed feedback. Please let me know if I’ve missed anything.

@tarunw07
tarunw07 force-pushed the MDEV-37605-convert-innodb-binlog-to-legacy branch from 2a4767f to 31a9c1f Compare September 1, 2026 21:51

@bnestere bnestere left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @tarunw07 , approved for our 13.2 preview, though we might actually want this in 12.3 instead, so those who upgrade to our LTS can have this downgrade capability. But we have some time to decide that, and having in the 13.2 preview will allow us some time to formally test this too.

Please, before September 17th, squash all of your commits into a single one, which describes:

  1. What problem this solves
  2. How this feature solves the problem
  3. How to use this feature
  4. What errors may be expected (you may generalize here, as you add a lot of errors :))
  5. Any caveats

As well as anything else that may be useful. Commit messages are quite helpful for a lot of folks. Testers, users, managers, etc. Please write it with a wide audience in mind (ie not just developers).

@tarunw07
tarunw07 force-pushed the MDEV-37605-convert-innodb-binlog-to-legacy branch 2 times, most recently from c35b119 to baa3f18 Compare September 17, 2026 05:20
…legacy format

MariaDB 12.3 added support for storing binary logs inside InnoDB
tablespace files (.ibb) instead of traditional flat sequential files.
There was previously no way to convert these back to the legacy
format, which is needed for downgrades, migrations, and third-party
tooling that only understands classic binlogs.

This adds a --convert-engine-binlog option to mariadb-binlog that
reads one or more InnoDB-based binlogs and writes out equivalent
legacy-format binlog files.

Usage:
  mariadb-binlog --convert-engine-binlog -r output_legacy_binlog \
      input1.ibb input2.ibb

This produces output_legacy_binlog.0000001, .0000002, etc. Note
there isn't a direct correlation between the number of input files
and output files. The output is split whenever the current file
hits --max-binlog-size (default 1GB, since legacy binlogs cannot
exceed 4GB), and also on every server restart recorded in the
InnoDB binlog, so a single input file can produce several output
files and a restart boundary can force a split even before the
size limit is reached.

Additionally, --max-binlog-size is also supported as a standalone
option to control this rotation threshold directly.

Only --convert-engine-binlog, -r, and --max-binlog-size are
supported alongside this mode; any other option will cause
mariadb-binlog to throw an error.

Caveats:
- Encryption: InnoDB-based binlogs don't currently support
  encryption, so the converter doesn't handle it either. Output
  legacy binlogs are never encrypted by this mode.
- Checksums: InnoDB-based binlogs rely on page-level checksums
  instead of the legacy per-event checksums, so the initial
  implementation generates legacy output with no event-level
  checksum.
@tarunw07
tarunw07 force-pushed the MDEV-37605-convert-innodb-binlog-to-legacy branch from baa3f18 to 8907c10 Compare September 18, 2026 04:31
@tarunw07

Copy link
Copy Markdown
Contributor Author

I have squashed all my commits into a single commit and pushed the latest changes with an updated commit message. Two CI checks are still failing, but I reviewed the logs and both failures appear to be unrelated to my changes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

External Contribution All PRs from entities outside of MariaDB Foundation, Corporation, Codership agreements. GSoC Replication Patches involved in replication

Development

Successfully merging this pull request may close these issues.

4 participants