private Operator createNewPipelineForChildNode(
      LocalExecutionPlanContext context,
      LocalExecutionPlanContext subContext,
      PlanNode childNode,
      PlanNodeId parentNodeId) {
    Operator childOperation = childNode.accept(this, subContext);
    ISinkChannel localSinkChannel =
        MPP_DATA_EXCHANGE_MANAGER.createLocalSinkChannelForPipeline(
            // Attention, there is no parent node, use first child node instead
            subContext.getDriverContext(), childNode.getPlanNodeId().getId());
    subContext.setISink(localSinkChannel);
    subContext.addPipelineDriverFactory(childOperation, subContext.getDriverContext(), 0);
    subContext.constructPipelineMemoryEstimator(childOperation, parentNodeId, childNode, -1);

    ExchangeOperator sourceOperator =
        new ExchangeOperator(
            context
                .getDriverContext()
                .addOperatorContext(
                    context.getNextOperatorId(), null, ExchangeOperator.class.getSimpleName()),
            MPP_DATA_EXCHANGE_MANAGER.createLocalSourceHandleForPipeline(
                ((LocalSinkChannel) localSinkChannel).getSharedTsBlockQueue(),
                context.getDriverContext()),
            childNode.getPlanNodeId(),
            childOperation.calculateMaxReturnSize());

    context.addExchangeOperator(sourceOperator);
    return sourceOperator;
  }
Now, we allocate children to each pipeline as average as possible. For example, 5 children with
3 dop, the children group will be [1, 2, 2]. After we can estimate the workload of each
operator, maybe we can allocate based on workload rather than child number.

<p>If child is ExchangeNode, it won't affect the children number of current group.

<p>This method can only be invoked when dop <= localChildrenSize.
iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/OperatorTreeGenerator.java