I was looking at a flame graph of an ingestion workload with @dliappis, and it looks like we parse the JSON representation of the index request twice when a document has both a pipeline and a final pipeline.

It's hard to know how much we would save on this benchmark since not all data streams in this benchmark had both a pipeline and a final pipeline, but the cost of parsing index request into maps of maps via IngestService represented 4.06% of overall CPU time according to the flame graph, so this might not be negligible.
server/src/main/java/org/elasticsearch/ingest/IngestService.java
ActionListener<AcknowledgedResponse> listener,
        Consumer<ActionListener<NodesInfoResponse>> nodeInfoListener
    ) throws Exception {
        if (isNoOpPipelineUpdate(state, request)) {
            
            listener.onResponse(AcknowledgedResponse.TRUE);
            return;
        }
        nodeInfoListener.accept(ActionListener.wrap(nodeInfos -> {
            validatePipelineRequest(request, nodeInfos);
            clusterService.submitStateUpdateTask(
                "put-pipeline-" + request.getId(),
                new PutPipelineClusterStateUpdateTask(listener, request),
                ClusterStateTaskConfig.build(Priority.NORMAL, request.masterNodeTimeout()),
                PIPELINE_TASK_EXECUTOR
            );
        }, listener::onFailure));
    }
    public void validatePipelineRequest(PutPipelineRequest request, NodesInfoResponse nodeInfos) throws Exception {
        final Map<String, Object> config = XContentHelper.convertToMap(request.getSource(), false, request.getXContentType()).v2();
        Map<DiscoveryNode, IngestInfo> ingestInfos = new HashMap<>();
        for (NodeInfo nodeInfo : nodeInfos.getNodes()) {
            ingestInfos.put(nodeInfo.getNode(), nodeInfo.getInfo(IngestInfo.class));
        }
        validatePipeline(ingestInfos, request.getId(), config);
    }
    public static boolean isNoOpPipelineUpdate(ClusterState state, PutPipelineRequest request) {
        IngestMetadata currentIngestMetadata = state.metadata().custom(IngestMetadata.TYPE);
        if (request.getVersion() == null
            && currentIngestMetadata != null