从 InfluxDB 迁移
本文档将帮助你了解 GreptimeDB 和 InfluxDB 的数据模型之间的区别,并指导你完成迁移过程。
数据模型的区别
要了解 InfluxDB 和 GreptimeDB 的数据模型之间的差异,请参考写入数据文档中的数据模型。
InfluxDB 行协议数据与 GreptimeDB 表的映射关系如下:
| InfluxDB | GreptimeDB |
|---|---|
| Measurement | 表 |
| Tag | 主键列 |
| Field | 字段列 |
| Timestamp | greptime_timestamp 时间索引列 |
写入有代表性的样本数据后,请在开始完整迁移前检查每个自动创建的表。将 measurement_name 替换为样本数据中的 measurement 名称:
DESC TABLE measurement_name;
SHOW CREATE TABLE measurement_name;
确认列类型和主键列符合预期,尤其是 measurement 包含高基数 tag 时。如果需要使用不同的表结构,请在导入数据前手动创建表。
数据库连接信息
在写入或查询数据之前,需要了解 InfluxDB 和 GreptimeDB 之间的数据库连接信息的差异。
- Token:InfluxDB API 中的 token 用于身份验证,与 GreptimeDB 身份验证相同。
当使用 InfluxDB 的客户端库或 HTTP API 与 GreptimeDB 交互时,你可以使用
<greptimedb_user:greptimedb_password>作为 token。 - Organization:GreptimeDB 中没有组织。
- Bucket:InfluxDB 的 bucket 对应 GreptimeDB 数据库。v2 写入 API 使用
bucket,v1 写入 API 使用db。 - Database:写入前请确认目标数据库已经存在。自托管 GreptimeDB 默认提供
public数据库;其他数据库需要通过CREATE DATABASE创建。
写入数据
GreptimeDB 兼容 InfluxDB 的行协议格式,包括 v1 和 v2。 这意味着你可以轻松地从 InfluxDB 迁移到 GreptimeDB。
HTTP API
你可以使用以下 HTTP API 请求将 measurement 写入 GreptimeDB:
- InfluxDB line protocol v2
- InfluxDB line protocol v1
curl -X POST 'http://<host>:4000/v1/influxdb/api/v2/write?bucket=<db-name>' \
-H 'authorization: token <greptime_user:greptimedb_password>' \
-d 'census,location=klamath,scientist=anderson bees=23 1566086400000000000'
curl 'http://<host>:4000/v1/influxdb/write?db=<db-name>&u=<greptime_user>&p=<greptimedb_password>' \
-d 'census,location=klamath,scientist=anderson bees=23 1566086400000000000'
precision 参数必须与请求体中的时间戳精度一致。可选值为 ns、us、ms 和 s,默认值为 ns。详情请参考 InfluxDB 行协议文档。
Telegraf
GreptimeDB 支持 InfluxDB 行协议也意味着 GreptimeDB 与 Telegraf 兼容。 要配置 Telegraf,只需将 GreptimeDB 的 URL 添加到 Telegraf 配置中:
有关详细的配置说明,请参考 通过 Telegraf 写入数据 文档。
客户端库
使用 InfluxDB 客户端库写入数据到 GreptimeDB 非常直接且简单。 你只需在客户端配置中包含 URL 和身份验证信息。
例如:
- Node.js
- Python
- Go
- Java
- PHP
'use strict'
/** @module write
**/
import { InfluxDB, Point } from '@influxdata/influxdb-client'
/** Environment variables **/
const url = 'http://<host>:4000/v1/influxdb'
const token = '<greptime_user>:<greptimedb_password>'
const org = ''
const bucket = '<db-name>'
const influxDB = new InfluxDB({ url, token })
const writeApi = influxDB.getWriteApi(org, bucket)
writeApi.useDefaultTags({ region: 'west' })
const point1 = new Point('temperature')
.tag('sensor_id', 'TLM01')
.floatField('value', 24.0)
writeApi.writePoint(point1)
import influxdb_client
from influxdb_client.client.write_api import SYNCHRONOUS
bucket = "<db-name>"
org = ""
token = "<greptime_user>:<greptimedb_password>"
url="http://<host>:4000/v1/influxdb"
client = influxdb_client.InfluxDBClient(
url=url,
token=token,
org=org
)
# Write script
write_api = client.write_api(write_options=SYNCHRONOUS)
p = influxdb_client.Point("my_measurement").tag("location", "Prague").field("temperature", 25.3)
write_api.write(bucket=bucket, org=org, record=p)
bucket := "<db-name>"
org := ""
token := "<greptime_user>:<greptimedb_password>"
url := "http://<host>:4000/v1/influxdb"
client := influxdb2.NewClient(url, token)
writeAPI := client.WriteAPIBlocking(org, bucket)
p := influxdb2.NewPoint("stat",
map[string]string{"unit": "temperature"},
map[string]interface{}{"avg": 24.5, "max": 45},
time.Now())
writeAPI.WritePoint(context.Background(), p)
client.Close()
private static String url = "http://<host>:4000/v1/influxdb";
private static String org = "";
private static String bucket = "<db-name>";
private static char[] token = "<greptime_user>:<greptimedb_password>".toCharArray();
public static void main(final String[] args) {
InfluxDBClient influxDBClient = InfluxDBClientFactory.create(url, token, org, bucket);
WriteApiBlocking writeApi = influxDBClient.getWriteApiBlocking();
Point point = Point.measurement("temperature")
.addTag("location", "west")
.addField("value", 55D)
.time(Instant.now().toEpochMilli(), WritePrecision.MS);
writeApi.writePoint(point);
influxDBClient.close();
}
$client = new Client([
"url" => "http://<host>:4000/v1/influxdb",
"token" => "<greptime_user>:<greptimedb_password>",
"bucket" => "<db-name>",
"org" => "",
"precision" => InfluxDB2\Model\WritePrecision::S
]);
$writeApi = $client->createWriteApi();
$dateTimeNow = new DateTime('NOW');
$point = Point::measurement("weather")
->addTag("location", "Denver")
->addField("temperature", rand(0, 20))
->time($dateTimeNow->getTimestamp());
$writeApi->write($point);
除了上述语言之外,GreptimeDB 还支持其他 InfluxDB 支持的客户端库。 你可以通过参考上面提供的连接信息代码片段,使用你喜欢的语言编写代码。
查询数据
GreptimeDB 不支持 Flux 和 InfluxQL,请将这些查询迁移为 SQL 或 PromQL。
下表汇总了常见的 InfluxQL 到 GreptimeDB SQL 映射:
| InfluxQL | GreptimeDB SQL |
|---|---|
| Measurement | 表 |
| Tag 或 Field | 列 |
time | 时间索引列,例如 greptime_timestamp |
GROUP BY time() | Range Query 或 date_bin() |
| 查询每个分组的最新数据点 | row_number() 窗口函数 |
difference() 或 elapsed() | lag() 或 lead() 窗口函数 |
更多查询改写示例请参考从 InfluxDB 到 GreptimeDB 迁移指南。
PromQL(Prometheus 查询语言)允许用户实时选择和聚合时间序列数据, 表达式的结果可以显示为图形,也可以在 Prometheus 的表达式浏览器中以表格数据的形式查看, 或通过 HTTP API 传递给外部系统。
假设你要查询过去 24 小时内记录的 monitor 表中的最大 CPU。
在 InfluxQL 中,查询如下:
SELECT
MAX("cpu")
FROM
"monitor"
WHERE
time > now() - 24h
GROUP BY
time(1h), "host"
此 InfluxQL 查询计算 monitor 表中 cpu 字段的最大值,
其中时间大于当前时间减去 24 小时,结果按 host 和一小时的时间窗口分组。
该查询在 Flux 中的表达如下:
from(bucket: "public")
|> range(start: -24h)
|> filter(fn: (r) => r._measurement == "monitor")
|> aggregateWindow(every: 1h, fn: max)
在 GreptimeDB SQL 中,类似的查询为:
SELECT
greptime_timestamp,
host,
MAX(cpu) RANGE '1h' AS max_cpu
FROM
monitor
WHERE
greptime_timestamp > NOW() - '24 hours'::INTERVAL
ALIGN '1h' TO NOW BY (host)
ORDER BY greptime_timestamp DESC;
在该 SQL 查询中,
RANGE 子句确定了 MAX(cpu) 聚合函数的时间窗口,
而 ALIGN 子句设置了时间序列数据的对齐时间。
有关按时间窗口分组的更多详细信息,请参考按时间窗口聚合数据文档。
在 PromQL 中,类似的查询为:
max_over_time(monitor{__field__="cpu"}[1h])
[1h] 范围选择器定义回看窗口。要像上面的 SQL 查询一样每小时输出一个结果,请将此 PromQL 作为范围查询执行:使用 HTTP API 的 start 和 end 参数定义时间范围,并通过 step=1h 定义求值间隔。
有关 PromQL 的更多信息,请参考 PromQL 文档。
可视化数据
推荐使用 Grafana 可视化 GreptimeDB 数据, 请参考 Grafana 文档 了解如何配置 GreptimeDB。
迁移数据
你可以通过以下步骤实现从 InfluxDB 到 GreptimeDB 的数据无缝迁移:
- 同时将数据写入 GreptimeDB 和 InfluxDB,以避免迁移过程中的数据丢失。
- 从 InfluxDB 导出所有历史数据,并将数据导入 GreptimeDB。
- 验证表结构、行数、时间范围和关键查询结果。
- 将读流量逐步切换到 GreptimeDB。
- 验证通过后停止向 InfluxDB 写入数据。
双写 GreptimeDB 和 InfluxDB
将数据双写 GreptimeDB 和 InfluxDB 是迁移过程中防止数据丢失的有效策略。 当使用 InfluxDB 的客户端库时,你可以建立两个客户端实例,一个用于 GreptimeDB,另一个用于 InfluxDB。 有关如何使用 InfluxDB 行协议将数据写入 GreptimeDB 的操作,请参考写入数据部分。
如果无需保留所有历史数据, 你可以双写一段时间以积累所需的最新数据, 然后停止向 InfluxDB 写入数据并仅使用 GreptimeDB。 如果需要完整迁移所有历史数据,请按照接下来的步骤操作。
从 InfluxDB v1 服务器导出数据
创建一个临时目录来存储 InfluxDB 的导出数据。
mkdir -p /path/to/export
使用 InfluxDB 的 influx_inspect export 命令 导出数据。
influx_inspect export \
-database <db-name> \
-end <end-time> \
-lponly \
-datadir /var/lib/influxdb/data \
-waldir /var/lib/influxdb/wal \
-out /path/to/export/data
-database指定要导出的数据库。-end指定要导出的数据的结束时间。 必须是RFC3339 格式,例如2024-01-01T00:00:00Z。 你可以使用同时写入 GreptimeDB 和 InfluxDB 时的时间戳作为结束时间。-lponly指定只导出行协议数据。-datadir指定数据目录的路径,请见InfluxDB 数据设置中的配置。-waldir指定 WAL 目录的路径,请见InfluxDB 数据设置中的配置。-out指定输出目录。
导出的 InfluxDB 行协议数据类似如下:
disk,device=disk1s5s1,fstype=apfs,host=bogon,mode=ro,path=/ inodes_used=356810i 1714363350000000000
diskio,host=bogon,name=disk0 iops_in_progress=0i 1714363350000000000
disk,device=disk1s6,fstype=apfs,host=bogon,mode=rw,path=/System/Volumes/Update inodes_used_percent=0.0002391237988702021 1714363350000000000
...
从 InfluxDB v2 服务器导出数据
创建一个临时目录来存储 InfluxDB 的导出数据。
mkdir -p /path/to/export
使用 InfluxDB 的 influx inspect export-lp 命令 导出数据。
influxd inspect export-lp \
--bucket-id <bucket-id> \
--engine-path /var/lib/influxdb2/engine/ \
--end <end-time> \
--output-path /path/to/export/data
--bucket-id指定要导出的 bucket ID。--engine-path指定引擎目录的路径,请见InfluxDB 数据设置中的配置。--end指定要导出的数据的结束时间。 必须是RFC3339 格式,例如2024-01-01T00:00:00Z。 你可以使用同时写入 GreptimeDB 和 InfluxDB 时的时间戳作为结束时间。--output-path指定输出目录。
命令行的执行结果类似如下:
{"level":"info","ts":1714377321.4795408,"caller":"export_lp/export_lp.go:219","msg":"exporting TSM files","tsm_dir":"/var/lib/influxdb2/engine/data/307013e61d514f3c","file_count":1}
{"level":"info","ts":1714377321.4940555,"caller":"export_lp/export_lp.go:315","msg":"exporting WAL files","wal_dir":"/var/lib/influxdb2/engine/wal/307013e61d514f3c","file_count":1}
{"level":"info","ts":1714377321.4941633,"caller":"export_lp/export_lp.go:204","msg":"export complete"}
导出的 InfluxDB 行协议数据类似如下:
cpu,cpu=cpu-total,host=bogon usage_idle=80.4448912910468 1714376180000000000
cpu,cpu=cpu-total,host=bogon usage_idle=78.50167052182304 1714376190000000000
cpu,cpu=cpu-total,host=bogon usage_iowait=0 1714375700000000000
cpu,cpu=cpu-total,host=bogon usage_iowait=0 1714375710000000000
...
导入数据到 GreptimeDB
对于大数据集,建议按 measurement 和时间范围分批导出及导入,记录已完成的批次以便断点续传,并将大文件拆分为较小的片段:
split -l 100000 -d -a 10 data data.
# -l [line_count] 创建长度为 line_count 行的拆分文件。
# -d 使用数字后缀而不是字母后缀。
# -a [suffix_length] 使用 suffix_length 个字母来形成文件名的后缀。
你可以使用 HTTP API 导入数据,如写入数据部分所述。 下方提供的脚本将帮助你从文件中读取数据并将其导入 GreptimeDB。
假设你的当前位置是存储数据文件的目录:
.
├── data.0000000000
├── data.0000000001
├── data.0000000002
...
将 GreptimeDB 的连接信息设置到环境变量中:
export GREPTIME_USERNAME=<greptime_username>
export GREPTIME_PASSWORD=<greptime_password>
export GREPTIME_HOST=<host>
export GREPTIME_DB=<db-name>
将数据导入到 GreptimeDB:
for file in data.*; do
curl -i --retry 3 \
-X POST "http://${GREPTIME_HOST}:4000/v1/influxdb/write?db=${GREPTIME_DB}&u=${GREPTIME_USERNAME}&p=${GREPTIME_PASSWORD}" \
--data-binary @"${file}"
sleep 1
done
验证导入的数据
切换读流量前,请逐个对比 InfluxDB 和 GreptimeDB 中的 measurement。至少应验证行数、时间范围、字段非空数量和关键聚合结果。例如:
SELECT
COUNT(*) AS row_count,
COUNT(cpu) AS cpu_value_count,
MIN(greptime_timestamp) AS min_ts,
MAX(greptime_timestamp) AS max_ts
FROM monitor;
SELECT host, COUNT(*) AS row_count
FROM monitor
GROUP BY host
ORDER BY row_count DESC;
逐步切换读流量期间应保持双写。只有上述检查和应用关键查询均符合预期后,才能停止向 InfluxDB 写入数据。
如果您需要更详细的迁移方案或示例脚本,请提供具体的表结构和数据量信息。GreptimeDB 官方社区将为您提供进一步的支持。欢迎加入 Greptime Slack 社区交流。