Skip to content

Instantly share code, notes, and snippets.

@iguoyr
Last active January 10, 2021 14:40
Show Gist options
  • Save iguoyr/c1fa54fdbe2c335e3b998afddb685e85 to your computer and use it in GitHub Desktop.
Save iguoyr/c1fa54fdbe2c335e3b998afddb685e85 to your computer and use it in GitHub Desktop.

Proposal: Foreign Data Wrappers

  • Author(s): Lfkdstiguorywph95,PureWhite
  • Last updated: 2020-01-10
  • Discussion at:

Abstract

FDW(FOREIGN DATA WRAPPER,外部数据包装器)可使得数据库可以访问不同的远程数据存储。

本提案将严格遵循 SQL/MED,通过对 Table Lifecycle 注入 Hook(复用 TiDB plan,在执行器 builder 时候使用自定义算子逻辑)使用 TiDB Plugin 等手段,使 TiDB 支持 FDW 功能。从而大幅度降低 TiDB 支持远程数据源的难度,提升 TiDB 社区活跃性。

Background

TiDB 访问远程数据源是一个经久不衰的话题。不完全统计,两届 Hackathon 中,有各类远程数据源访问的项目。例如: CSV,Elasticsearch,Prometheus,Presto 等。但这类需求存在:与 TiDB 生态结合度不强,难以进行稳定官方维护,难以合并至 master 分支落地等问题。

本草案结合前人的项目经验,设计了一套 TiDB FDW 方案。同时有极高的落地性和可扩展性,吸引更多人来构建 TiDB 生态。

Proposal

将 FDW 功能加入到 TiDB 中。严格循序 PostgreSQL FDW 与 SQL/MED

用户可以通过以下流程实现一个 FDW 扩展。

  1. 遵守 plugin framework,创建 Engine Plugin。
  2. 实现 plugin 接口(具体参考 Implementation 部分 )
  3. build plugin

后即可使用 FDW 功能

同时为了提供样例和简化实现 FDW 流程,本提案将实现一个基于 gRPC 的 FDW 实现。用户使用自己擅长的语言,只需要掌握 gRPC,无需 Fork TiDB 和自行产生 plugin 即可开箱即用。

具体流程为:

TiDB -> gRPC_FDW -> gRPC_SERVER
												READ
												WIRTE
												...

这里举使用 FDW csv 作为例子,用户的使用流程为:

# 标准 FDW 方式创建 使用 csv engine 的table
CREATE EXTENSION csv_fdw;
CREATE SERVER test_logs FOREIGN DATA WRAPPER csv_fdw;
CREATE FOREIGN TABLE people (city int, name char(255)) SERVER test_logs
OPTIONS ( filename '<YOUR_CSV_FILE_PATH>');

# 使用 MySQL 兼容方案创建 table
CREATE TABLE people(city int, name char(255)) ENGINE = csv ENGINE_ATTRIBUTE = "path=<YOUR_CSV_FILE_PATH>"
  
# 支持 insert  
INSERT INTO people values(0, 'lfkdsk');
INSERT INTO people values(1, 'john');
INSERT INTO people values(2, 'tom');
INSERT INTO people values(3, 'iguory');

# 支持 select
SELECT * FROM people;
SELECT * FROM people where city = 2;

# 支持与 TiDB 普通 table: city 进行 join 
SELECT city.cityName, people.name FROM city INNER JOIN people ON city.id=people.city


# 使用 EXPLAIN 可看到逻辑有下推到 Engine !
EXPLAIN SELECT city.cityName, people.name FROM city INNER JOIN people ON city.id=people.city
> ID                 |    task  |
  Project_0         		  root
  |_ HashRightJoin  		  root
    |- TableReader        root
      \- TableScan        cop[tikv]
  \_P 12                  root
     \_TableReader        root
        \_Selection       cop[csv]
          \_TableScan     cop[csv]

# 支持 custom string query,这里例如 TSDB
SELECT * FROM TiDB_Metric where q=`avg(rate(TiDB_Index_Size{table=*,app='*'}[15m]) `;

Rationale

Mysql 通过内置的方式实现了多个 Engine,尚未有很好的流程支持远程数据存储。MySQL 官方只支持使用 MySQL 协议的远程数据存储。具有很大的局限性。

PostgreSQL 制定 SQL/MED,实现了 FDW,通过 extension 的方式可以动态的开启相关 FDW 功能。

Prometheus 自身 store 支持 remote read/write,proms 生态存在大量的围绕该功能对 proms 进行拓展的第三方库。

TiDB 通过 Mysql 协议的兼容,实现了部分 engine API,但自身 Engine API 和自身生态 (TiKV/TiTan) 强绑定。无良好的插拔性。

Compatibility and Limitations

虽然本提案的计划只对 TiDB 主分支代码进行一些 Hook 埋点。作为一种对 Mysql SQL 的扩展,对已有功能未有任何修改与破坏,不存在兼容性问题。

但因为以下原因,需要额外工作量。

  • 但因为部分函数存在私有方法,需要批量重命名。
  • 部分库和 Plugin 存在循环引用,需要针对性调整。
  • 当前版本 TestSuits 不支持与 Plugin Framework 的协同工作,需要针对性的优化。
  • Mysql 的语法中并没有针对 SQL/MED 的设计,本提案遵循了 Postgresql 接口,使得 TiDB FDW 功能并未遵循 mysql 的时间。 针对该问题,本提案中的实施细节中有方案进行解决与兼容。

同时因为涉及到 plugin lib 的修改,原有 plugin 都需要重新 build 后方可使用。 (源于 plugin framework 的局限)

Implementation

实现分为两部分

  1. 对 parser 改造,支持新的扩增 FDW 语法

  2. 对 Engine 和 SQL Lifecycle 进行 hook 注入。可 engine 可以参与 SQL 执行,实现注入算子下推等功能。

  • 扩展 SQL 语法,支持 PostgreSQL FDW 增加的语法。

    对 TiDB Parser 进行提升,新增 CREATE EXTENSIONCREATE SERVERCREATE FOREIGN TABLE 等功能。

    对 where 语句实现 where q=`custon query` 功能。

  • 通过少量增加 TiDB engine hook。

    • 对 DDL 进行 Hook 埋点,用于控制创建/删除 基于 FDW 的使用自定义 engine Table。
  • 创建 Custom Engine 算子,实现数据的读与写

    • CustomEngineScanExec,对应 PhysicalTableScan
    • CustomEnginSelectionExec,对应 PhysicalSelection
    • CustomEngineInsertExec,对应 Insert
    • CustomEngineAggExec,对应 PhysicalHashAgg
    • CustomEngineInedexReaderExec,对应 PhysicalIndexReader
    • CustomEngineInedexLookUpReaderExec,对应 PhysicalIndexLookUpReader
  • 创建 Custom Engine Plan

    TODO 还没想到用什么优雅的方式生成新的 Plan

  • 使用 plugin framework,定义 engine plugin。

    plugin 增加一个新的类型: Engine

    实现接口

    # DDL
    OnDropTable   func(tb *model.TableInfo) error
    OnCreateTable func(tb *model.TableInfo) error
    
    # CustomEngineInsertExec
    OnInsertOpen  func(ctx context.Context, meta *ExecutorMeta) error
    OnInsertNext  func(ctx context.Context, rows [][]expression.Expression, meta *ExecutorMeta) error
    OnInsertClose func(meta *ExecutorMeta) error
    
    # CustomEngineScanExec
    OnReaderOpen  func(ctx context.Context, meta *ExecutorMeta) error
    OnReaderNext  func(ctx context.Context, chk *chunk.Chunk, meta *ExecutorMeta) error
    OnReaderClose func(meta ExecutorMeta)
    
    # CustomEnginSelectionExec
    OnSelectReaderNext func(ctx context.Context, chk *chunk.Chunk, filter []expression.Expression, meta *ExecutorMeta) error
    OnSelectReaderOpen func(ctx context.Context, filter []expression.Expression, meta *ExecutorMeta) error
    
    ...
    

    优化已有的 test suits (当前调研发现,TiDB 当前的测试无法在使用 plugin 的情况下正常运行,将会在 hackathon 中修复该问题),一些库循环应用的修复。

Testing Plan

TODO

Open issues (if applicable)

pingcap/tidb#3890

pingcap/tidb#6459

pingcap/tidb#15935

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment