In https://github.com/elastic/elasticsearch/pull/78547 we introduced batching for the ILM master tasks that occur on the happy path. However if a high-shard-count cluster encounters problems while doing ILM-related things—perhaps some nodes are temporarily unavailable for taking a snapshot—then we process the resulting `ilm-retry-failed-step` and `ilm-move-to-error-step` tasks one-by-one which can significantly delay the cluster's recovery from its problems.

We should batch these things together too.

It looks like we also enqueue duplicate `ilm-retry-failed-step` on each poll interval too, although we do appear to treat the duplicates as no-ops at execution time.

Relates https://github.com/elastic/elasticsearch/issues/77466
x-pack/plugin/ilm/src/main/java/org/elasticsearch/xpack/ilm/IndexLifecycleRunner.java
for (final var taskContext : taskContexts) {
            try {
                final var task = taskContext.getTask();
                state = task.execute(state);
                taskContext.success(new ClusterStateTaskExecutor.LegacyClusterTaskResultActionListener(task, currentState));
            } catch (Exception e) {
                taskContext.onFailure(e);
            }
        }
        return state;
    };
    IndexLifecycleRunner(
        PolicyStepsRegistry stepRegistry,
        ILMHistoryStore ilmHistoryStore,
        ClusterService clusterService,
        ThreadPool threadPool,
        LongSupplier nowSupplier
    ) {
        this.stepRegistry = stepRegistry;
        this.ilmHistoryStore = ilmHistoryStore;
        this.clusterService = clusterService;
        this.nowSupplier = nowSupplier;
        this.threadPool = threadPool;
    }
    /**
     * Retrieve the index's current step.
     */
    static Step getCurrentStep(PolicyStepsRegistry stepRegistry, String policy, IndexMetadata indexMetadata) {
        LifecycleExecutionState lifecycleState = indexMetadata.getLifecycleExecutionState();
        return getCurrentStep(stepRegistry, policy, indexMetadata, lifecycleState);