I'm having a hard time locating the exact cause, however, I know you were doing something with the on_research_finished event, so starting with you since I'm running your suite.
When a research completes, there is a significant hitch in the game, which is magnified if I have the research window open. If this is from you, would it be more performant to capture buildings placed via the on_entity_placed event, store that in the table, and then just parse that much smaller table to find which buildings to upgrade? Additionally maybe do the process on Nth tick, to spread the load some? (Build table, then start an on Nth tick using another function that runs and removes the upgrades as it processes them, so that each Nth tick that table gets smaller, then when it reaches 0, it stops until it's initiated again by a new research completion.)