That's literally what triggers are designed for. A trigger that runs but "does nothing" will run in literal milliseconds. Hardly any stress at all. Your code would look like this:
Campaign[] completedCampaigns = new Campaign[0];
for(Integer index = 0; index < Trigger.size; index++) {
if(Trigger.old[index].Status != Trigger.new[index].Status && Trigger.new[index].Status == 'Completed') {
completedCampaigns.add(Trigger.new[index]);
}
}
if(completedCampaigns.size() > 0) {
YourApexClass.doSomething(completedCampaigns);
}
(Of course, it'd be better as a trigger framework, this is only illustrative).
This code will run in sub-millisecond time if there's only one campaign being updated and it doesn't match the criteria, or about 10-20 ms for 200 records in the list (based on some tests I wrote up to try it out). That's not even 1/10th of one second.
You could also use a Flow with an Apex Action, but realistically, that's overkill for what you're trying to do.