分享:DolphinDB分区策略:RANGE分区详解

开发过程中有些细节容易被忽略,今天挑几个重点聊一聊。

目录

二、创建RANGE分区 三、RANGE分区查询 四、动态分区管理 五、RANGE分区最佳实践 六、组合RANGE分区 七、实战案例 八、总结参考资料

摘要

这里深入讲解DolphinDB RANGE分区策略。从RANGE分区原理到设计方法,从时间范围分区到数值范围分区,从动态分区到分区管理,全面介绍RANGE分区的应用场景和优化技巧。通过丰富的代码示例,帮助读者掌握RANGE分区设计和管理的核心技能。


一、RANGE分区概述

1.1 什么是RANGE分区

RANGE分区按照列值的范围进行分区,每个分区包含一个值域范围的数据:

RANGE分区原理

原始数据

按范围分区

分区1: 1-100

分区2: 101-200

分区3: 201-300

分区4: 301-400

1.2 RANGE分区特点

特点说明范围分区按值域范围分区顺序存储数据按范围有序存储适合连续值适合时间、数值等支持扩展可动态添加新分区

1.3 适用场景

场景说明时间序列数据按日期/时间范围分区数值范围数据按数值区间分区历史数据管理便于按时间归档数据生命周期便于冷热数据分离


二、创建RANGE分区

2.1 基本语法

// RANGE分区语法
db = database("dfs://db_name", RANGE, partition_vector)

// 参数说明:
// - partition_vector: 分区边界向量
//   例如 [1, 100, 200, 300] 表示三个分区:
//   [1,100), [100,200), [200,300)

2.2 时间范围分区

// 按日期范围分区
dates = 2024.01.01..2024.12.31
db = database("dfs://time_db", RANGE, dates)

// 创建分区表
schema = table(1:0,
    `device_id`timestamp`temperature`humidity,
    [INT, TIMESTAMP, DOUBLE, DOUBLE])
db.createPartitionedTable(schema, `sensor_data, `timestamp)

// 插入数据
t = table(
    take(1..100, 10000) as device_id,
    take(2024.01.01T00:00:00 + 0..9999 * 60000, 10000) as timestamp,
    rand(20.0..30.0, 10000) as temperature,
    rand(40.0..60.0, 10000) as humidity
)
loadTable("dfs://time_db", "sensor_data").append!(t)

2.3 数值范围分区

// 按数值范围分区
ranges = [0, 100, 200, 300, 400, 500]
db = database("dfs://range_db", RANGE, ranges)

// 创建分区表
schema = table(1:0,
    `value_range`count`sum_val,
    [INT, INT, DOUBLE])
db.createPartitionedTable(schema, `stats_data, `value_range)

// 插入数据
t = table(
    rand(0..500, 1000) as value_range,
    rand(1..10, 1000) as count,
    rand(100.0..1000.0, 1000) as sum_val
)
loadTable("dfs://range_db", "stats_data").append!(t)

2.4 按月分区

// 按月分区
months = [2024.01M, 2024.02M, 2024.03M, 2024.04M, 2024.05M, 2024.06M,
          2024.07M, 2024.08M, 2024.09M, 2024.10M, 2024.11M, 2024.12M, 2025.01M]
db = database("dfs://monthly_db", RANGE, months)

// 创建分区表
schema = table(1:0,
    `month`device_id`value,
    [MONTH, INT, DOUBLE])
db.createPartitionedTable(schema, `monthly_data, `month)


三、RANGE分区查询

3.1 范围查询优化

// RANGE分区支持高效的范围查询
t = loadTable("dfs://time_db", "sensor_data")

// 查询日期范围(分区裁剪)
select count(*) from t
where date(timestamp) between 2024.01.01 and 2024.01.31

// 查询单日数据
select count(*) from t
where date(timestamp) = 2024.01.15

// 查看执行计划
explain select * from t
where date(timestamp) between 2024.01.01 and 2024.01.07

3.2 时间窗口查询

// 时间窗口聚合
select bar(timestamp, 1h) as hour,
       avg(temperature) as avg_temp,
       max(temperature) as max_temp,
       min(temperature) as min_temp
from t
where date(timestamp) = 2024.01.15
group by bar(timestamp, 1h)


四、动态分区管理

4.1 添加分区

// RANGE分区支持动态添加新分区
db = database("dfs://time_db")

// 添加新日期分区
addPartitions(db, [2025.01.01, 2025.02.01])

// 查看分区方案
db.partitionSchema()

4.2 删除分区

// 删除旧分区数据
db = database("dfs://time_db")

// 删除指定日期分区
dropPartition(db, [2023.01.01])

// 注意:删除分区会删除该分区的所有数据

4.3 分区数据迁移

// 数据迁移函数
def migrateOldData(dbPath, tableName, cutoffDate) {
    db = database(dbPath)
    t = loadTable(db, tableName)

    // 查询需要迁移的数据
    oldData = select * from t
              where date(timestamp) < cutoffDate

    // 写入归档库
    archive_db = database("dfs://archive_db")
    loadTable(archive_db, tableName).append!(oldData)

    // 删除原数据
    dates = distinct(date(oldData.timestamp))
    for (d in dates) {
        dropPartition(db, [d])
    }

    return oldData.rows()
}

// 执行迁移
migrateOldData("dfs://time_db", "sensor_data", 2024.01.01)


五、RANGE分区最佳实践

5.1 分区粒度选择

数据特点建议粒度说明高频数据按日/小时减少单分区数据量中频数据按日/周平衡分区数量低频数据按月/季减少分区数量历史数据按月/年便于归档管理

5.2 分区大小建议

建议说明单分区大小1GB-10GB分区数量不超过10000数据均匀各分区数据量相近

5.3 时间分区设计

// 方案1:按日分区(适合高频数据)
dates = 2024.01.01..2024.12.31
db = database("dfs://daily_db", RANGE, dates)

// 方案2:按月分区(适合中频数据)
months = 2024.01M..2025.01M
db = database("dfs://monthly_db", RANGE, months)

// 方案3:按年分区(适合低频数据)
years = [2020, 2021, 2022, 2023, 2024, 2025]
db = database("dfs://yearly_db", RANGE, years)


六、组合RANGE分区

6.1 时间+设备分区

// 组合分区:时间RANGE + 设备VALUE
db = database("dfs://combo_db", COMPO,
    [RANGE, 2024.01.01..2024.12.31,  // 时间范围分区
     VALUE, 1..100])                  // 设备值分区

// 创建分区表
schema = table(1:0,
    `device_id`timestamp`temperature`humidity,
    [INT, TIMESTAMP, DOUBLE, DOUBLE])
db.createPartitionedTable(schema, `sensor_data, `timestamp`device_id)

6.2 时间+哈希分区

// 组合分区:时间RANGE + 设备HASH
db = database("dfs://combo_db2", COMPO,
    [RANGE, 2024.01.01..2024.12.31,  // 时间范围分区
     HASH, [INT, 10]])                // 设备哈希分区(10个桶)

// 创建分区表
schema = table(1:0,
    `device_id`timestamp`temperature`humidity,
    [INT, TIMESTAMP, DOUBLE, DOUBLE])
db.createPartitionedTable(schema, `sensor_data, `timestamp`device_id)


七、实战案例

7.1 物联网数据按日分区

// 创建按日分区的物联网数据库
dates = 2024.01.01..2024.12.31
db = database("dfs://iot_daily", RANGE, dates)

// 创建设备数据表
schema = table(1:0,
    `device_id`timestamp`temperature`humidity`pressure`vibration`power,
    [INT, TIMESTAMP, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE])
db.createPartitionedTable(schema, `device_data, `timestamp)

// 创建告警表
schema = table(1:0,
    `alert_id`device_id`timestamp`alert_type`alert_level`message,
    [LONG, INT, TIMESTAMP, SYMBOL, INT, STRING])
db.createPartitionedTable(schema, `alert_data, `timestamp)

// 模拟数据写入
def simulateData(startDate, days) {
    for (i in 0..(days-1)) {
        date = startDate + i
        data = table(
            take(1..100, 10000) as device_id,
            take(dateT00:00:00 + 0..9999 * 8640, 10000) as timestamp,
            rand(20.0..30.0, 10000) as temperature,
            rand(40.0..60.0, 10000) as humidity,
            rand(1000.0..1020.0, 10000) as pressure,
            rand(0.0..5.0, 10000) as vibration,
            rand(100.0..500.0, 10000) as power
        )
        loadTable("dfs://iot_daily", "device_data").append!(data)
    }
}

simulateData(2024.01.01, 30)  // 模拟30天数据

// 查询某日数据
select count(*) from loadTable("dfs://iot_daily", "device_data")
where date(timestamp) = 2024.01.15

7.2 数据生命周期管理

// 数据生命周期管理
def lifecycleManagement() {
    db = database("dfs://iot_daily")

    // 1. 归档90天前的数据
    cutoffDate = today() - 90
    oldData = select * from loadTable(db, "device_data")
              where date(timestamp) < cutoffDate

    archive_db = database("dfs://iot_archive")
    loadTable(archive_db, "device_data").append!(oldData)

    // 2. 删除已归档分区
    dates = distinct(date(oldData.timestamp))
    for (d in dates) {
        dropPartition(db, [d])
    }

    // 3. 添加新分区(未来30天)
    newDates = (today() + 1)..(today() + 30)
    addPartitions(db, newDates)

    print("生命周期管理完成")
}

// 每日执行
scheduleJob("lifecycle", "数据生命周期管理", lifecycleManagement,
            02:00, 2024.01.01, 2030.12.31, 'D')


八、总结

这里详细介绍了DolphinDB RANGE分区策略:

1. 分区原理:按值域范围分区
2. 创建方法:时间范围、数值范围、按月分区
3. 查询优化:范围查询、时间窗口查询
4. 动态管理:添加分区、删除分区、数据迁移
5. 最佳实践:分区粒度、分区大小、时间分区设计
6. 组合分区:时间+设备、时间+哈希

思考题

1. RANGE分区适合什么类型的数据?
2. 如何选择合适的分区粒度?
3. 如何实现数据生命周期管理?


参考资料


这篇笔记就先到这里,后面用到新的思路或者发现有问题再补充。

评论 (0)

暂无评论