Start here. This is the direct spoken answer to practice first.
Why this question matters
Batch jobs often fail differently from request code. The design needs a memory budget, not only a loop that eventually finishes on a developer machine.
I would process the data in chunks instead of loading everything into memory. Each chunk would use a fresh scope or unit of work, save progress, release references, and move to the next chunk. I would cap concurrency so multiple workers do not multiply memory usage beyond the process limit. I would also log progress and failures so the job can resume or retry without starting from zero.