
# UDF Actor放置策略
scheduling_strategy用于控制UDF Actor的放置策略，支持两种策略类型。
#### 策略1：InstanceSchedulingStrategy实例共置/分散
控制同一查询的多个Actor实例的放置关系：
```
from aura_frame.ibis.expr.operations.scheduling_strategy import (
    InstanceSchedulingStrategy, InstanceAffinityMode
)
# 多实例必须同节点（共享缓存/通信）
InstanceSchedulingStrategy(mode=InstanceAffinityMode.SAME_REQUIRED)
# 多实例必须不同节点（容错/负载均衡）
InstanceSchedulingStrategy(mode=InstanceAffinityMode.DIFF_REQUIRED)
# 优先同节点，允许回退
InstanceSchedulingStrategy(mode=InstanceAffinityMode.SAME_PREFERRED)
# 优先不同节点，允许回退
InstanceSchedulingStrategy(mode=InstanceAffinityMode.DIFF_PREFERRED)
```
**SAME_REQUIRED首次分发限制** ：首次分发时pod上不存在同查询已分发实例标签，无法满足required约束，会返回ERROR。仅在多个Actor按序分发时有效。生产推荐SAME_PREFERRED或DIFF_PREFERRED，示例如下：
```
from aura_frame.ibis.expr.operations.scheduling_strategy import (
    InstanceSchedulingStrategy,
    InstanceAffinityMode,
)
# 场景：数据预处理（分散优先）+ 模型推理（必须共置）
# Actor-A 先分发落位，Actor-B 通过相同 label 追踪
# Actor-A：DIFF_PREFERRED 先分发，分散到不同节点
ds = ds.map(
    fn="my_schema.preprocess",
    on=["raw_col"],
    as_col="preprocessed",
    scheduling_strategy=InstanceSchedulingStrategy(
        mode=InstanceAffinityMode.DIFF_PREFERRED,
        label="inference-pipeline",
    ),
)
# Actor-B：SAME_REQUIRED 追到 Actor-A 所在节点，共享预处理缓存
ds = ds.map(
    fn="my_schema.inference",
    on=["preprocessed"],
    as_col="score",
    scheduling_strategy=InstanceSchedulingStrategy(
        mode=InstanceAffinityMode.SAME_REQUIRED,
        label="inference-pipeline",
    ),
)
```
关键点：
- **相同的label**：两个Actor必须使用完全相同的label字符串。
- **非REQUIRED先分发**：用DIFF_PREFERRED或SAME_PREFERRED的Actor先落位。
- **REQUIRED后分发**：用SAME_REQUIRED的Actor通过label追过去。
如果不需要强制共置，直接用SAME_PREFERRED即可，无需自定义label：
```
# 优先共置，允许回退——不需要 SAME_REQUIRED 的简单场景
scheduling_strategy=InstanceSchedulingStrategy(mode=InstanceAffinityMode.SAME_PREFERRED),
```
Label命名规范：
- 最大63个字符
- 必须以字母或数字开头和结尾
- 中间只能使用字母、数字、-、_、.
- 禁止使用保留前缀 actor_
#### 策略2：NodeLabelSchedulingStrategy 节点选择
基于Kubernetes节点标签，约束Actor放置位置：
```
from aura_frame.ibis.expr.operations.scheduling_strategy import (
    NodeLabelSchedulingStrategy, In, NotIn, Exists, DoesNotExist
)
# 硬约束：必须在 pool=gpu 的节点
NodeLabelSchedulingStrategy(hard={"pool": In("gpu")})
# 软约束：优先高带宽节点，不满足时回退
NodeLabelSchedulingStrategy(soft={"high_speed_net": Exists()})
# 组合使用
NodeLabelSchedulingStrategy(
    hard={"pool": In("gpu")},            # 必须满足
    soft={"high_mem": In("true")},       # 优先满足
)
```
四个操作符：
| 操作符          | 写法                              | 含义        |
|:---|:---|:---|
| In           | {"pool": In("gpu", "npu")}      | 标签值匹配其中之一 |
| NotIn        | {"env": NotIn("test")}          | 标签值不在指定范围 |
| Exists       | {"npu_type": Exists()}          | 标签key存在   |
| DoesNotExist | {"maintenance": DoesNotExist()} | 标签key不存在  |
   
#### 多Workgroup场景下的调度
集群中同一类型设备（如 GPU）可能被划分为多个workgroup（资源池），每个workgroup在节点上以**endpointId**作为标签 key 标识，标签value为workgroup名称。当存在多个workgroup时，device="GPU" 等参数只能将Actor调度到GPU类型的节点，但无法区分具体哪个workgroup。
![](https://support.huaweicloud.com/sqlref-aura-aidatalake/public_sys-resources/note_3.0-zh-cn.png)
endpointId是Aura endpoint的标识。不同集群的endpointId可能不同，用户可在租户面查看自己的endpointId。
**通过NodeLabelSchedulingStrategy可以在运行时指定目标workgroup** ：
```
from aura_frame.ibis.expr.operations.scheduling_strategy import (
    NodeLabelSchedulingStrategy, InstanceSchedulingStrategy,
    InstanceAffinityMode, In,
)
# 指定调度到名为 "gpu-pool-a" 的 GPU workgroup，同时实例分散
# 标签 key 为 endpointId（从租户面获取），value 为 workgroup 名称
ds = ds.map(
    fn="my_schema.inference",
    on=["feature_col"],
    as_col="result",
    device="GPU",
    model="A100",
    num_models=1,
    scheduling_strategy=[
        NodeLabelSchedulingStrategy(hard={"<endpointId>": In("gpu-pool-a")}),
        InstanceSchedulingStrategy(mode=InstanceAffinityMode.DIFF_PREFERRED),
    ],
)
```
表1workgroup调度的优先级和共存规则 
| 来源                          | 级别     | 优先级 | 说明                                                                              |
|:---|:---|:---|:---|
| work_group_name             | DDL注册级 | 最高  | CREATE FUNCTION 中指定，内核以LabelInOperator(endpointId, {workgroup_name}) 追加为 AND约束。 |
| NodeLabelSchedulingStrategy | 运行时调用级 | 中   | WITH ARGUMENTS中指定，灵活可变。                                                         |
   
**关键规则：**
- **work_group_name与scheduling_strategy共存**：work_group_name作为AND约束追加到标签列表中（key为endpointId），不会覆盖或清除 scheduling_strategy 的约束。
- **多workgroup必须显式选择**：同类型存在多个workgroup时，需要通过NodeLabelSchedulingStrategy或work_group_name指定目标，否则调度结果不可预测。
表2注册时与运行时：两种设置方式的差异 
| 维度     | work_group_name         | scheduling_strategy   |
|:---|:---|:---|
| 设置时机   | UDF注册时（CREATE FUNCTION） | 查询调用时（WITH ARGUMENTS） |
| 灵活性    | 一次注册，永久生效               | 每次查询可不同               |
| 生效范围   | 该UDF的所有调用               | 仅当前查询                 |
| 变更成本   | 需重新注册UDF                | 修改代码即可                |
| SQL 层级 | DDL 级                   | DML级                  |
   
表3选型建议 
| 场景                               | 推荐做法                                                   |
|:---|:---|
| UDF**始终**运行在同一个workgroup         | 注册时通过work_group_name指定，避免每次查询重复设置                      |
| UDF需要在**不同workgroup间切换**（灰度、多租户） | 注册时**不设置**work_group_name，通过scheduling_strategy运行时灵活指定 |
   
