Unfortunately even doing the copying in small batches of 100 rows generates significant lag after some time.
Are you adding any delay between each batch, or just batching up the updates and running each batch directly after the previous one?
If so then try scripting the conversion in your favourite language with something like:
copy oldest 100 rows that haven't been copied yet to new table
sleep for as long as that update took
until there are <100 rows unprocessed
stop logging service
move the last few rows
delete the old table when you are sure the conversion has worked
This should ensure that that the conversion doesn't take more than more-or-less half your server's capacity even allowing for differences in load imposed as the system's use varies with time.
Or if you want to use as much time as possible when the service is relatively idle but back off (potentially pausing for quite a length of time) when the database needs to do some work for its users, replace
sleep for as long as the update took with
if the server's load is above <upper measure>, sleep for some seconds then check again, loop around the sleep/check until the load drops below <lower measure>. This will mean it can steam ahead in quiet times but will pause completely when the server is busy performing it's normal workload. Determining load will depend on your OS - under Linux and similar the 1-minute load average value from
/proc/loadavg or the output of
uptime should do.
<lower measure> and
<upper measure> may be the same value, though it is usual in controls like this to have a difference so your process doesn't keep starting then immediately pausing because of its own restarting having an influence on the load measure.
Of course this would not work for tables where old rows may get modified, but will work fine for a log table like the one you describe.
You will want to ignore the usual wisdom of creating indexes after populating the new table in this case. While that is indeed more efficient when you want things to be as fast as possible (the effect on the rest of the system be damned), in this case you don't want the big glut of load at the end of the process as the indexes are completely created in one go as this is a process you can't pause when things get busy.