# Clinic Data Proxy 支持 Azure 改造方案

## 1. 背景与目标

`clinic_data_service` 当前为 Clinic Data Proxy 提供统一查询能力，主要覆盖三类 API：

- Data Query API：`POST /data-proxy/query`，查询 slow query、TopSQL 等 parquet/Delta 数据。
- Schema API：`GET /data-proxy/schema`，返回标准表 schema。
- Metrics API：`/data-proxy/metrics/{cluster_id}/api/v1/...`，兼容 Prometheus 查询。

现有 parquet/对象存储查询已经支持：

- AWS：S3 + Athena
- GCP：GCS + BigQuery
- AliCloud：OSS + MaxCompute

本次目标是在保持现有 API 入参基本不变的前提下，让同一套 Data Proxy API 支持 Azure 集群的数据查询。

## 2. 现有实现梳理

### 2.1 查询链路

当前 Data Query 的主链路是：

1. Handler 解析请求，识别 SQL 中的标准表。
2. 如果请求未显式传 `options.dataSource`，通过 Clinic internal API 获取 cluster data URLs。
3. `ParseAndDetectDataSource` 根据 URI scheme 识别云厂商。
4. Router 根据 vendor 选择查询引擎。
5. SQL rewrite 将逻辑库表改写为实际库表，例如：
   - `clinic_data_proxy.slow_query_logs` -> `clinic_data_proxy_xxx.slow_query_logs_{clusterId}`
   - `clinic_data_proxy.topsql_tidb` -> `clinic_data_proxy_xxx.topsql_tidb_{clusterId}`
6. 表管理器按云厂商自动创建或检查外部表。
7. 引擎执行 SQL 并返回统一结果。

### 2.2 当前云厂商入口

当前 datasource scheme 与 vendor 映射如下：

| Scheme | Vendor | Query Engine |
| --- | --- | --- |
| `s3://` | `aws` | Athena |
| `gs://` | `gcp` | BigQuery |
| `oss://` | `alicloud` | MaxCompute |

`clinic_data_service` 目前没有 Azure 配置、Azure datasource 解析、Azure engine，也没有 Synapse 方言 rewrite。

另外，主 Clinic 工程的 o11y URL resolver 已经有 Azure 分支，会把 Azure 对象存储 scheme 设置成 `abfss`。这意味着如果 Azure cluster data URL 自动回填到 `clinic_data_service`，当前服务会因为不支持 `abfss://` 直接报 `unsupported scheme`。

### 2.3 Metrics API 现状

Metrics API 不走对象存储引擎。它通过 Clinic internal API 拿到：

- `ClusterMetricsURL`
- `DataPlaneMetricsURL`

然后转发 Prometheus-compatible 请求，并按 deploy type 注入必要 label。因此 Azure Metrics 支持的主要前置条件是：Clinic internal API 能为 Azure 集群返回可用的 o11y metrics URL。`clinic_data_service` 侧不需要新增 Synapse/Azure Storage 查询引擎来支持 Metrics API。

## 3. Azure 查询调研结论

基于 Azure 查询调研 PDF，推荐使用 Azure Synapse Serverless SQL 查询 Blob/ADLS 上的 Parquet 文件。

Azure 与 AWS Athena 的核心差异是：

| 项目 | AWS Athena | Azure Synapse Serverless SQL |
| --- | --- | --- |
| 查询模型 | 表驱动 | 文件驱动 |
| 分区裁剪 | SQL `WHERE date = ...` 可配合分区投影裁剪 | 主要依赖 `OPENROWSET` 的 `BULK` 路径裁剪 |
| 数据入口 | Glue/Athena external table | `OPENROWSET` 或 external table |
| 关键风险 | 表/分区配置 | 路径未裁剪导致全量扫描 |

Azure 侧最关键的设计点是：不能只依赖 SQL 中的 `WHERE date = ...`，必须把 date 分区条件转换为 Blob 路径，例如：

```sql
FROM OPENROWSET(
    BULK 'https://<account>.blob.core.windows.net/<container>/slowlogs/date=2026-04-14/*.parquet',
    FORMAT = 'PARQUET'
) AS t
```

## 4. 推荐总体方案

建议采用“先 OPENROWSET MVP，后 External Table 增强”的两阶段方案。

### 4.1 MVP：OPENROWSET 方案

MVP 不做 Synapse external table 自动建表，而是在执行 Azure 查询时：

1. 保持外部 API 不变。
2. 支持 Azure datasource URI 解析。
3. 从 SQL AST 中提取标准表和 date 分区条件。
4. 根据表类型、clusterId、component、date 条件生成 Blob 路径。
5. 将逻辑表替换为 `OPENROWSET(BULK ..., FORMAT='PARQUET')` 派生表或 CTE。
6. 通过 Synapse Serverless SQL 执行改写后的 SQL。

这个方案最贴合 Azure 调研结论，优点是：

- 不需要维护大量 Synapse external table。
- Azure 分区裁剪可以强制落到路径层。
- 对当前 API 使用方侵入最小。

主要代价是 SQL rewrite 和路径规划会比 Athena/GCP/AliCloud 更复杂。

### 4.2 后续增强：External Table 方案

如果后续需要 BI 报表、固定 schema、高频重复查询，可以增加 Azure table manager：

- 创建 database scoped credential。
- 创建 external data source。
- 创建 external file format。
- 创建 external table 或 view。

这个方案更像 Athena 表管理，但要注意 Synapse external table 对分区裁剪的能力与路径组织方式，仍然不能假设 `WHERE date = ...` 一定能像 Athena 一样自动降扫描量。

## 5. 具体改造点

### 5.1 配置层

新增 Azure 配置段，建议结构如下：

```yaml
azure:
  enabled: true
  synapse:
    server: "<workspace>.sql.azuresynapse.net"
    database: "clinic_data_proxy_dev"
    auth_type: "sql" # sql | aad_service_principal | managed_identity
    username: "${AZURE_SYNAPSE_USERNAME}"
    password: "${AZURE_SYNAPSE_PASSWORD}"
    tenant_id: "${AZURE_TENANT_ID}"
    client_id: "${AZURE_CLIENT_ID}"
    client_secret: "${AZURE_CLIENT_SECRET}"
    query_timeout_seconds: 300
  storage:
    default_account: "<storage-account>"
    default_container: "<container>"
    region_accounts:
      eastus:
        account: "<storage-account>"
        container: "<container>"
  table_manager:
    enabled: false
```

配置代码需要改造：

- `DataProxyConfig` 增加 `Azure AzureConfig`。
- `Validate()` 中把 Azure 计入“至少启用一个 engine”判断。
- 校验 Synapse server/database/auth 配置。
- `DefaultConfig()` 增加 Azure 默认关闭配置。

认证建议：

- 开发/验证阶段可以先支持 SQL Auth。
- 生产优先支持 Managed Identity 或 AAD service principal。
- Synapse 访问 Blob 的权限需要在 Azure 侧授予 Storage Blob Data Reader。

### 5.2 Datasource 解析

建议 Azure 支持以下 datasource 格式：

| 格式 | 用途 |
| --- | --- |
| `https://<account>.blob.core.windows.net/<container>/<path>` | 推荐作为 Azure Blob canonical URI |
| `abfss://<container>@<account>.dfs.core.windows.net/<path>` | ADLS Gen2 标准 URI |
| `abfss://<bucket-like-name>/<path>` | 兼容主 Clinic resolver 当前可能生成的简化格式 |

需要改造：

- `dto.DataSourceInfo` 增加 Azure 需要的字段，例如 `Account`、`Container`，或约定复用 `Bucket` 表示 container。
- `ParseDataSource` 支持 `https` + Azure Blob host、`abfs`、`abfss`。
- `ParseAndDetectDataSource` 增加 vendor `azure`。
- Azure region 不建议靠 storage URL 猜测，优先级建议为：
  1. 请求 `options.region`
  2. Clinic internal API 返回的 cluster region
  3. config 中 storage account/container 映射

如果依赖 clusterId 自动回填 dataSource，还需要同步确认主 Clinic internal API 返回的 Azure URL 是 Synapse 可用的 Blob HTTPS URL，还是 `abfss://` URI。若返回 `abfss://o11y-prod-shared-<region>/...` 这类简化格式，`clinic_data_service` 需要能从配置补齐 account/container。

### 5.3 Router 与 Engine

新增 `SynapseEngine`，并在 router 中注册：

```text
vendor=azure -> engine=synapse
```

`SynapseEngine` 需要实现当前 `QueryEngine` 接口：

- `Execute(ctx, sql, dataSource)`
- `Name() string`，建议返回 `synapse`
- `Health(ctx)`
- `GetDatabase(region)`
- `GetTableSchema(ctx, database, table, region)`

实现建议：

- 用 `database/sql` + SQL Server driver 连接 Synapse Serverless SQL。
- Query timeout 通过 context 控制。
- 结果转换复用当前统一 `DataProxyQueryResult`。
- metadata 中填充 `engine=synapse`、`vendor=azure`、`region=<cluster region>`。

### 5.4 Azure SQL rewrite 与路径规划

这是本次改造的核心。

当前 rewrite 只负责逻辑库表名替换和方言转换。Azure 还需要额外做“表引用 -> OPENROWSET 路径”的转换。

推荐新增一个 Azure query planner：

```text
原始 SQL
  -> 解析 AST
  -> 校验标准表和 date 分区条件
  -> 表名替换为 clusterId 后缀表名
  -> 根据表类型解析数据路径
  -> 生成 OPENROWSET CTE/派生表
  -> Synapse T-SQL 方言转换
  -> 执行
```

路径规划需要覆盖的表：

| 逻辑表 | Azure 数据路径要求 |
| --- | --- |
| `slow_query_logs` | slowlogs 基础路径 + date 分区 |
| `topsql` / `topsql_tidb` | TopSQL 基础路径 + `component=tidb` + instance/date 分区 |
| `topsql_tikv` | TopSQL 基础路径 + `component=tikv` + instance/date 分区 |
| `topsql_plan_meta` | TopSQL 基础路径 + `component=topsql_plan_meta` + date 分区 |
| `topsql_sql_meta` | TopSQL 基础路径 + `component=topsql_sql_meta` + date 分区 |
| `topsql_topru` / `topru` | TopSQL 基础路径 + `component=topru` + instance/date 分区 |

需要特别处理：

- `date = '...'`
- `date IN (...)`
- `date BETWEEN ... AND ...`
- CTE/subquery/join 中不同表的 date 条件
- `date` 格式兼容，当前示例里同时存在 `20260310` 和 `2026-03-24` 风格，Azure 路径生成需要配置或规范化。

对于多 date 查询，建议策略：

- date 数量较少时，为每个 date 生成一个 `OPENROWSET`，再 `UNION ALL`。
- date 范围过大时直接拒绝或要求缩小范围，避免误扫大量 Blob。
- 如果必须用 wildcard，要有明确扫描范围上限。

由于 Synapse 不是 Athena，不能只保留：

```sql
WHERE date = '2026-04-14'
```

而是要确保 Blob 路径已经裁剪到：

```text
.../date=2026-04-14/*.parquet
```

如果 parquet 文件里没有物理 `date` 列，则需要在 `OPENROWSET` 外层补出分区列，例如：

```sql
SELECT *, '2026-04-14' AS [date]
FROM OPENROWSET(...)
```

### 5.5 Synapse 方言适配

需要在 `pkg/dialect` 中增加 Synapse/T-SQL 方言能力，至少覆盖 API 文档中的常见 SQL：

- `LIMIT n` -> `TOP n` 或 `OFFSET/FETCH`
- 标识符引用：反引号/双引号 -> T-SQL 可接受形式，必要时使用 `[column]`
- `any_value(x)` -> 可接受替代，例如 `MIN(x)` 或 `MAX(x)`，需要确认语义
- `CAST` 类型映射，例如 `DOUBLE`/`BIGINT`/`STRING` 到 Synapse 类型
- 脏数据场景下必要时支持 `TRY_CAST`
- 日期、字符串、聚合函数兼容

MVP 可以限定支持 SELECT/CTE/JOIN/聚合/ORDER BY/LIMIT，复杂 SQL 先明确返回不支持，避免生成错误或高成本查询。

### 5.6 Schema API

因为 MVP 使用 `OPENROWSET`，没有稳定的 Synapse external table 元数据可直接读取。Schema API 推荐两种实现方式：

1. 优先推荐：从现有 table template/schema 定义中抽象出一份标准 schema registry，AWS/GCP/AliCloud/Azure 共用。
2. 备选：对 Azure 生成一个小范围 `OPENROWSET` 查询，用 Synapse 描述结果集，但这依赖样本文件存在，稳定性较弱。

建议 MVP 使用标准 schema registry，保证：

- `GET /data-proxy/schema?clusterId=...&tables=slow_query_logs`
- `GET /data-proxy/schema?clusterId=...&tables=topsql,topsql_sql_meta`

在 Azure 下返回与现有云厂商一致的 logical schema。

### 5.7 Metrics API

Metrics API 侧建议做轻量改造或验证：

- 确认 Clinic internal API 对 Azure cluster 返回 `ClusterMetricsURL` 和 `DataPlaneMetricsURL`。
- `clinic_data_service` 继续按现有逻辑转发 Prometheus 请求。
- 如果 Azure 集群使用不同 deploy type 或 label 体系，需要补充 label 注入策略。
- 如果 Azure node/disk 指标需要走 dataplane endpoint，需要补充 `metrics_mapping.dataPlaneMetrics`。

结论：Metrics API 不需要 Synapse/Blob 查询引擎，但需要端到端验证 Azure cluster data URLs 和 label 注入是否正确。

### 5.8 主 Clinic 工程联动

如果要求“只传 clusterId，不传 options.dataSource”也能查 Azure 数据，则需要主 Clinic internal API 同步支持 Azure data URL 生成。

重点确认：

- Azure slow query URL 返回格式。
- Azure TopSQL URL 返回格式。
- Azure metrics URL 返回格式。
- `Provider=azure`、`Region`、`DeployTypeV2` 等字段是否完整。

当前 `pkg/o11y/urls/urls.go` 有 Azure scheme 分支，但生成的 `abfss://...` 是否能直接映射到 Synapse `OPENROWSET` 所需的 Blob HTTPS URL，需要进一步确认。

## 6. API 兼容设计

### 6.1 Data Query API

建议保持现有请求格式不变：

```json
{
  "sql": "SELECT digest, count(*) FROM clinic_data_proxy.slow_query_logs WHERE date = '2026-04-14' GROUP BY digest LIMIT 20",
  "timeout": 300,
  "clusterId": "10988667376937531168",
  "options": {
    "vendor": "azure",
    "region": "eastus",
    "dataSource": "https://<account>.blob.core.windows.net/<container>/deltalake/.../slowlogs"
  }
}
```

如果 `options.dataSource` 为空，则沿用当前自动回填逻辑，从 Clinic internal API 获取 cluster data URLs。

响应保持不变，只是 metadata 变为：

```json
{
  "engine": "synapse",
  "vendor": "azure",
  "region": "eastus"
}
```

### 6.2 Schema API

保持现有请求格式：

```text
GET /data-proxy/schema?clusterId=<clusterId>&tables=slow_query_logs,topsql
```

Azure 下返回 logical schema，`dataSource` 返回实际 Azure datasource。

### 6.3 Metrics API

保持现有 Prometheus-compatible API：

```text
GET  /data-proxy/metrics/{cluster_id}/api/v1/query
POST /data-proxy/metrics/{cluster_id}/api/v1/query
GET  /data-proxy/metrics/{cluster_id}/api/v1/query_range
POST /data-proxy/metrics/{cluster_id}/api/v1/query_range
```

## 7. 测试计划

### 7.1 单元测试

- Azure datasource 解析：
  - Blob HTTPS URL
  - 标准 `abfss://container@account.dfs.core.windows.net/path`
  - 兼容型 `abfss://bucket/path`
- Azure region/vendor override。
- SQL date 分区提取：
  - `date =`
  - `date IN`
  - `date BETWEEN`
  - CTE/subquery/join
- TopSQL component 路径生成。
- Synapse SQL rewrite：
  - `LIMIT`
  - identifier quote
  - aggregate/function mapping
- Schema registry 返回。

### 7.2 集成测试

- 使用 mocked `database/sql` 或测试 Synapse workspace 验证 `SynapseEngine.Execute`。
- 使用 httptest mock Clinic internal API，验证 clusterId 自动回填 dataSource。
- 验证没有 date 分区条件时拒绝查询。
- 验证多 date 查询不会生成无界 wildcard 路径。
- 验证 Metrics API 对 Azure cluster URLs 的转发。

### 7.3 端到端验证

- Azure slow query 查询。
- Azure TopSQL tidb/tikv/sql_meta/plan_meta/topru 查询。
- Grafana 或调用方通过现有 API 查询 metrics。
- 验证扫描路径和 Synapse 查询成本。

## 8. 工作量评估

### MVP 版本

| 模块 | 工作量 |
| --- | --- |
| Azure config/auth 设计与实现 | 1-2 人日 |
| datasource 解析与 router 接入 | 1-2 人日 |
| SynapseEngine 执行/健康检查/结果转换 | 2-4 人日 |
| Azure OPENROWSET path planner 与 SQL rewrite | 4-6 人日 |
| Schema API 支持 | 1-2 人日 |
| Metrics API Azure 验证与少量适配 | 0.5-1 人日 |
| 单元测试、集成测试、文档 | 2-3 人日 |

MVP 合计约 11-20 人日。若 Azure 存储路径、date 格式、slowlogs Delta/Parquet 格式都比较明确，预计更接近 2 周；如果需要反复验证 Synapse 对现有文件格式的兼容性，可能接近 3-4 周。

### 增强版本

如果继续做 Azure external table/table manager：

- 额外增加 5-8 人日。
- 如果要支持多 region、多 storage account、多 credential 隔离和权限自动化，整体可能扩展到 4-6 周。

## 9. 风险与待确认事项

1. Azure slow query 文件到底是裸 Parquet 还是 Delta Lake。如果是 Delta，需要确认 Synapse Serverless SQL 是否兼容当前 Delta layout。
2. Azure URL canonical 格式需要确认。建议最终使用 Blob HTTPS URL，减少 `abfss` 到 HTTPS 的歧义转换。
3. date 分区格式需要统一或配置化，当前示例里存在 `yyyyMMdd` 和 `yyyy-MM-dd` 两种风格。
4. Synapse 不会像 Athena 一样靠表分区自动降扫描量，必须强制路径裁剪。
5. `date IN` 或 `BETWEEN` 可能生成多个 `OPENROWSET`，需要限制日期范围，防止成本失控。
6. SQL 方言兼容是主要复杂点，尤其是 `LIMIT`、identifier quote、函数映射、类型转换。
7. Schema API 如果不建 external table，需要维护 logical schema registry。
8. Metrics API 依赖主 Clinic internal API 返回 Azure metrics URL，这部分需要联调。

## 10. 推荐实施里程碑

### Milestone 1：接口与配置打通

- 加 Azure config。
- 加 Azure datasource parser。
- Router 注册 SynapseEngine。
- 支持显式 `options.vendor=azure` + `options.dataSource`。

### Milestone 2：Azure Data Query MVP

- 实现 SynapseEngine。
- 实现 slow_query_logs 的 date path pruning。
- 支持最常用 SELECT/聚合/ORDER BY/LIMIT。
- 完成基础单测与一条真实 Azure 查询。

### Milestone 3：TopSQL 支持

- 支持 TopSQL component 路径。
- 支持多表 join 和 metadata 表。
- 补齐 CTE/subquery 场景。

### Milestone 4：Schema 与自动 dataSource

- 抽象 schema registry。
- Schema API 支持 Azure。
- 与主 Clinic internal API 联调 clusterId 自动回填。

### Milestone 5：Metrics 与生产化

- 验证 Azure metrics URL 转发。
- 补充必要 metrics mapping。
- 增加成本保护、日志、错误提示和告警。

## 11. 建议结论

建议先做 OPENROWSET MVP，不建议一开始就做 Azure external table/table manager。

原因是 Azure 的核心差异在路径裁剪，直接用 OPENROWSET 更容易保证每次查询只扫描目标 date/component 路径，也更符合调研文档里的最佳实践。等 Azure 查询稳定、schema 和路径格式确认后，再决定是否引入 external table 增强。

