HornOptimizer.java
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.doris.horn;
import org.apache.doris.common.UserException;
import org.apache.doris.horn.doris2horn.HornLogicalPlanThriftBuilder;
import org.apache.doris.horn.horn2doris.DorisPhysicalPlanBuilder;
import org.apache.horn4j.thrift.TFlattenedExpression;
import org.apache.horn4j.thrift.THornOptimizeProfile;
import org.apache.horn4j.thrift.THornOptimizeResult;
import org.apache.horn4j.HornNative;
import org.apache.doris.metric.HornMetric;
import org.apache.doris.metric.MetricRepo;
import org.apache.doris.nereids.CascadesContext;
import org.apache.doris.nereids.trees.plans.logical.LogicalPlan;
import org.apache.doris.nereids.trees.plans.physical.PhysicalPlan;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.thrift.TDeserializer;
import org.apache.thrift.TSerializer;
/** Doris-side entry to the Horn CBO optimizer */
public class HornOptimizer {
private static final Logger LOG = LogManager.getLogger(HornOptimizer.class);
/** Horn 优化失败的分类,对齐 Horn 三段式流水线(正向翻译 → Horn 优化 → 反向翻译) */
public enum HornFailureKind {
// A: Doris→Horn 正向翻译失败 → 计 translate_plan_error(Doris 集成翻译层缺口)
HORN_FORWARD_ERROR,
// B: Horn 声明不处理(HornNotHandleError/External) → 计 fallback(对齐 kernel countHornFallBack)
HORN_NOT_HANDLE,
// C: Horn 优化器内部错误(HornOptimizerError) → 计 error(对齐 kernel countHornError)
HORN_OPTIMIZE_ERROR,
// D: Horn→Doris 反向翻译失败 → 计 translate_plan_error(Doris 集成翻译层缺口)
HORN_BACKWARD_ERROR,
// 兜底:native 抛异常但分类前缀识别不出(JNI 胶水错 / 未知 std 异常 / 契约漂移) → 计 error
UNKNOWN
}
private final CascadesContext cascadesContext;
private String hornExplainString;
private String fallbackReason;
// 仅在 optimize() 失败(返回 null)时赋值并被 NereidsPlanner 读取;成功路径不读它。
private HornFailureKind failureKind;
public HornOptimizer(CascadesContext cascadesContext) {
this.cascadesContext = cascadesContext;
}
public String getHornExplainString() {
return hornExplainString;
}
public String getFallbackReason() {
return fallbackReason;
}
public HornFailureKind getFailureKind() {
return failureKind;
}
public PhysicalPlan optimize(LogicalPlan logicalPlan) throws UserException {
HornOptimizationContext hornCtx = new HornOptimizationContext(cascadesContext);
// 阶段 A:Doris LogicalPlan → TFlattenedExpression → 序列化。
byte[] planBytes;
byte[] tableBatchBytes;
byte[] queryOptionBytes;
try {
LOG.info("Horn CBO: translating Doris plan to Horn input");
HornLogicalPlanThriftBuilder planVisitor = new HornLogicalPlanThriftBuilder(hornCtx);
TFlattenedExpression plan = planVisitor.translate(logicalPlan);
TSerializer serializer = new TSerializer();
planBytes = serializer.serialize(plan);
tableBatchBytes = serializer.serialize(hornCtx.getTableBatch());
queryOptionBytes = serializer.serialize(hornCtx.getQueryOptionConfig());
} catch (Exception e) {
failureKind = HornFailureKind.HORN_FORWARD_ERROR;
fallbackReason = e.getMessage();
LOG.info("Horn CBO: forward-translation failed, fallback: {}", fallbackReason);
return null;
}
// 阶段 B/C:JNI 调 Horn native(in-process horn4j)。
byte[] resultBytes;
try {
LOG.info("Horn CBO: calling Horn via horn4j JNI");
resultBytes = new HornNative().optimize(planBytes, tableBatchBytes, queryOptionBytes);
} catch (Exception e) {
// 依据 kernel HornError 的分类前缀(既定契约:horn_error.h GetErrorTypeString 把类型拼进 message)
String msg = e.getMessage();
if (msg != null && msg.contains("HornNotHandle")) {
failureKind = HornFailureKind.HORN_NOT_HANDLE;
} else if (msg != null && msg.contains("HornOptimizerError")) {
failureKind = HornFailureKind.HORN_OPTIMIZE_ERROR;
} else {
failureKind = HornFailureKind.UNKNOWN;
}
fallbackReason = msg;
LOG.warn("Horn CBO: native failed ({}), fallback: {}", failureKind, fallbackReason);
return null;
}
// 阶段 D:反序列化 + 消费 profile + Horn output → Doris PhysicalPlan。
try {
LOG.info("Horn CBO: rebuilding Doris plan from Horn output");
THornOptimizeResult hornResult = new THornOptimizeResult();
TDeserializer deserializer = new TDeserializer();
deserializer.deserialize(hornResult, resultBytes);
hornExplainString = hornResult.getExplain_string();
if (hornExplainString != null) {
LOG.info("Horn CBO explain:\n{}", hornExplainString);
}
consumeProfile(hornResult);
DorisPhysicalPlanBuilder builder = new DorisPhysicalPlanBuilder(hornCtx);
PhysicalPlan physicalPlan = builder.build(hornResult.getTfexpr());
LOG.info("Horn CBO: optimization complete");
return physicalPlan;
} catch (Exception e) {
failureKind = HornFailureKind.HORN_BACKWARD_ERROR;
// 反译失败时 horn C++ 已产出计划文本,getHornExplainString() 已被上面赋值,
fallbackReason = e.getMessage();
LOG.warn("Horn CBO: backward-translation failed, fallback: {}", fallbackReason, e);
return null;
}
}
/** 消费 Horn profile → 更新 FE 量表指标(profile 无条件产出,无需任何 session 开关) */
private static void consumeProfile(THornOptimizeResult hornResult) {
if (hornResult.isSetProfile() && MetricRepo.isInit) {
THornOptimizeProfile p = hornResult.getProfile();
HornMetric.GAUGE_HORN_OPTIMIZE_LATENCY_NS.setValue(p.getHorn_optimize_latency()); // #6
HornMetric.GAUGE_HORN_CASCADE_GROUP_COUNT.setValue(p.getCascade_group_count()); // #7
HornMetric.GAUGE_HORN_CASCADE_GROUP_EXPR_COUNT.setValue(
p.getCascade_group_expression_count()); // #8
HornMetric.GAUGE_HORN_CASCADE_SAFE_TO_PRUNE_RATIO.setValue(
p.getCascade_safe_to_prune_ratio()); // #9
HornMetric.GAUGE_HORN_SCHEDULER_JOB_COUNT.setValue(p.getScheduler_job_count()); // #10
HornMetric.GAUGE_HORN_DPHYPER_CSG_CMP_COUNT.setValue(p.getDphyper_csg_cmp_count()); // #11
HornMetric.GAUGE_HORN_DPHYPER_ENUM_SUCCESS_RATIO.setValue(
p.getDphyper_enumeration_successful_ratio()); // #12
}
}
}