告别关系型数据库:百万日活社区监控架构选型复盘

我们去年接手了一个日活 120 万左右的社区 APP 的后端重构任务。当时最让我头疼的不是业务代码,而是监控系统的存储层。原来的架构用的是 MySQL 5.7,把接口 QPS、用户在线数、帖子发布量这些指标按分钟粒度往一张大表里写。

一开始数据量小,大家也没在意。等到日活突破 80 万,这张表每天新增近 200 万行记录。某天早上 9 点,运营同学打开后台看昨天的活跃用户趋势,页面转了 15 秒还没出来。我登上服务器一看,慢查询日志里全是 SELECT AVG(value) FROM metrics WHERE type = 'qps' AND time > '2024-03-01',MySQL 在 2 亿行数据上做范围扫描,CPU 直接干到 90%。

那时候我才真正意识到,关系型数据库设计出来就不是干这个的。它擅长的是事务和关联查询,而监控数据有几个非常典型的特征:数据只追加、几乎不更新、按时间顺序写入、查询往往是对一段时间内的聚合。用 MySQL 存,就像把流水账记在账本里,每次想算个月度平均值,都得把整本账翻一遍。

我们当时对比了几个方案。Prometheus 确实火,但它是拉模型,而且存储层对长期历史数据支持不够,我们有些运营报表需要查过去 90 天的趋势,Prometheus 的本地 TSDB 撑不住。TimescaleDB 是基于 PostgreSQL 的扩展,功能很强,但运维复杂度偏高,我们团队当时人手不够,不想再维护一套 PostgreSQL 集群。

最终选了 InfluxDB 2.7。原因很实际:第一,它是专门为时序数据设计的,写入和存储压缩比我们预期的要好很多;第二,我们当时用的 Telegraf 采集器可以直接对接,改造成本低;第三,InfluxDB 2.x 的 UI 自带 Dashboard 和告警,能先顶一阵子,不用立刻搭 Grafana。

迁移过程里有个细节我印象很深。原来 MySQL 表里有 metric_typevaluecreate_time 三个核心字段。我一开始想当然地在 InfluxDB 里建了一个 measurement 叫 metrics,把 metric_type 当成 tag。结果写入一周后,tag cardinality 爆炸——因为 metric_type 里混入了类似 user_id_123456 这种高基数的数据。InfluxDB 的 tag 是高基数敏感的,一旦 tag 值太多,内存占用会急剧上升。那次线上 InfluxDB 进程被 OOM Killer 干掉了两次,我才明白:tag 必须选那些低基数、用于分组的字段,比如 hostregionenv;而像具体数值或高唯一性 ID,必须放在 field 里

后来我们重新设计 schema,把真正的指标类型(如 cpu_usageorder_qps)作为 measurement 名,环境信息作为 tag,数值作为 field,问题才彻底解决。同样的数据量,写入 QPS 稳定在 3 万左右,查询 7 天内的聚合数据基本在 200ms 内返回。

这里给一个我们当时验证 schema 的示例,用的是 InfluxDB 2.7 的 Line Protocol 写入方式,通过 Python 客户端实现:

from influxdb_client import InfluxDBClient, Point from influxdb_client.client.write_api import SYNCHRONOUS import time # 实际项目中这些配置放在环境变量里 client = InfluxDBClient( url="http://localhost:8086", token="your-influxdb-token", org="community-app" ) write_api = client.write_api(write_options=SYNCHRONOUS) # 模拟一次接口 QPS 上报 point = Point("api_qps") \ .tag("env", "prod") \ .tag("region", "cn-beijing") \ .tag("endpoint", "/api/post/list") \ .field("value", 1420.5) \ .time(time.time_ns()) write_api.write(bucket="monitor-30d", record=point)

这个结构在后来支撑了我们整个社区的实时监控。直到今年我们开始评估升级到 InfluxDB 3.0,因为社区提到它基于 Apache Arrow 和 DataFusion 的新引擎在分析查询上会有明显提升,这也是后面章节要展开的内容。

---

实战案例:电商大促下订单流量监控系统的落地实践

去年双 11,我们负责一个电商平台的订单核心链路监控。这个系统平时订单量在每秒 300 单左右,但大促开场前 10 分钟会瞬间冲到每秒 1.2 万单。我们必须在 5 秒内发现订单下跌、支付失败率飙升这些异常,否则损失就是实打实的金额。

最开始我们用的是 ELK 架构,把订单事件往 Elasticsearch 里写,再用 Kibana 做图。听起来合理,但实际跑起来问题很多。Elasticsearch 对时序数据的压缩不够友好,同样存 1 小时的订单指标,ES 占用的磁盘是 InfluxDB 的 3 倍多。更麻烦的是查询延迟,有一次大促预热,运营同学想看最近 30 分钟的订单趋势,ES 的聚合查询花了 4 秒多,Dashboard 几乎不可用。

后来我们决定把实时指标层切到 InfluxDB 2.7。设计目标很明确:写入不能丢数据、查询要在 200ms 内、系统能扛住 3 倍于平时的流量峰值。

我们当时采集了几个关键指标:

这里有一个非常具体的教训。最初我把 order_id 作为 tag 往 InfluxDB 里写,心想这样以后可以按订单查。结果大促压测时,InfluxDB 内存一路涨到 12GB,写入延迟从 10ms 变成 800ms。排查下来发现,order_id 是绝对的高基数,每秒几万个不同值,InfluxDB 的索引机制会为每个 tag 值维护内存索引,直接把服务拖垮。从那以后我定了个规矩:InfluxDB 的 tag 只放“用来分组的维度”,比如 channelregionenv;每个订单的唯一标识,要么不存,要么放 field 里

最终我们用的 schema 是这样的:

measurement: order_metrics tags: channel, env, region fields: create_qps, pay_qps, pay_latency timestamp: 秒级或毫秒级

写入端我们用 Telegraf 做了一层聚合。订单服务不直接写 InfluxDB,而是先往 Kafka 发原始事件,Telegraf 消费 Kafka 后做 10 秒窗口的聚合,再批量写入 InfluxDB。这样写入 QPS 从 1.2 万降到了 120 左右,InfluxDB 的压力小了很多。

下面是我们 Telegraf 配置里的一段真实片段,用来说明怎么做聚合后写入:

[[inputs.kafka_consumer]] brokers = ["kafka-01:9092", "kafka-02:9092"] topics = ["order_events"] data_format = "json" [[aggregators.basicstats]] period = "10s" drop_original = true fields = ["create_qps", "pay_qps"] [[outputs.influxdb_v2]] urls = ["http://influxdb-prod:8086"] token = "your-token" organization = "ecommerce" bucket = "order-monitor-7d"

这个架构在大促当天表现很稳。订单流量从 300 QPS 涨到 1.2 万 QPS 的过程中,InfluxDB 的写入延迟始终在 50ms 以下,查询最近 1 小时的聚合数据基本在 150ms 左右返回。

后来我们还用上了 InfluxDB 2.x 的 连续查询(CQ) 做自动降采样。原始数据保留 7 天,超过 7 天的自动聚合成 1 分钟粒度存到长期 bucket 里。这样既保住了实时精度,又控制了存储成本。这个需求如果用 MySQL 或 ES 做,要么写一堆定时任务,要么存储膨胀到无法接受。

今年我们也在关注 InfluxDB 3.0 的进展。它提到会增强实时流处理能力,如果未来能把这种降采样逻辑直接下沉到引擎层,而不是靠 CQ 任务去跑,那架构还能再简化一层。

---

性能硬核对比:InfluxDB 2.7 vs 3.0 写入与查询压测实录

今年初我们内部做了一次很实在的对比测试。背景是我们的监控平台准备升级,到底继续用 InfluxDB 2.7 还是迁移到 3.0 早期版本,需要拿数据说话。

测试环境是 3 台 8 核 16GB 的云服务器,数据盘是 SSD。InfluxDB 2.7 用的是官方最新稳定版 2.7.6,InfluxDB 3.0 用的是基于 Apache Arrow 和 DataFusion 引擎的预览构建版(当时是 2024 年 3 月的内部测试镜像)。

我们模拟的是一个典型的 IoT + 监控混合场景:

先说写入。InfluxDB 2.7 在批量写入(batch size 5000)下,单节点稳定写入 QPS 在 28 万左右,CPU 占用约 65%。InfluxDB 3.0 的写入表现略高,稳定在 32 万 QPS 左右,CPU 占用反而低了 10 个百分点。我分析下来,3.0 的 Arrow 内存格式在批量写入时的序列化开销更小,这是架构层面的优势。

但真正让我意外的是查询性能。我们用一个典型的 SQL 查询来对比:

SELECT date_bin(interval '10 minutes', time) as t, avg(cpu_usage), avg(memory_usage) FROM device_metrics WHERE time >= now() - interval '24 hours' AND region = 'cn-beijing' GROUP BY t ORDER BY t;

在 InfluxDB 2.7 里,这个查询平均耗时 1.8 秒。同样的查询在 3.0 里只用了 620 毫秒。我后来反复测了几次,确认不是缓存原因。DataFusion 的查询优化器确实比 2.x 的 TSM 引擎在复杂聚合上更有优势,尤其是涉及多字段、多时间区间的场景。

不过 3.0 也不是没有代价。我们当时发现,3.0 对内存的占用比 2.7 更“激进”。同样的数据集,2.7 占用约 4.2GB 内存,3.0 在查询高峰期能冲到 6.8GB。如果你的机器内存紧张,这一点必须提前考虑。

还有一个细节值得提。InfluxDB 3.0 开始原生支持更多 SQL 语义,我们原来在 2.7 里用 InfluxQL 写的查询,有一部分要改写成标准 SQL。比如原来 SELECT mean(value) FROM metrics WHERE time > now() - 1h 这种,在 3.0 里要写成 SELECT avg(value) FROM metrics WHERE time >= now() - interval '1 hour'。虽然改动不大,但如果你有大量历史查询脚本,迁移成本要算进去。

下面是我们压测时用的一个 Python 写入脚本,两种版本都兼容,只是 endpoint 和 token 不同:

from influxdb_client import InfluxDBClient, Point, WritePrecision from datetime import datetime import random import time client = InfluxDBClient( url="http://localhost:8086", token="your-token", org="benchmark" ) write_api = client.write_api() start_time = time.time() for i in range(100000): p = Point("device_metrics") \ .tag("device_id", f"dev-{random.randint(1,100)}") \ .tag("region", "cn-beijing") \ .field("cpu_usage", random.uniform(0, 100)) \ .field("memory_usage", random.uniform(0, 100)) \ .time(datetime.utcnow(), WritePrecision.NS) write_api.write(bucket="bench-1d", record=p) print(f"Write 100k points cost: {time.time() - start_time:.2f}s")

综合来看,如果你现在已经在用 2.7,而且系统稳定,不用急着迁移。但如果你是新项目,或者查询复杂度高、数据量增长快,3.0 的架构确实更有后劲。按照 InfluxDB 官方 2024-2026 年的路线图,3.0 会是未来的核心方向,SQL 兼容性、Arrow 生态集成、AI/ML 能力都会往这个版本集中。

我们现在的策略是:生产环境继续跑 2.7,但新功能验证和性能敏感的场景,先在 3.0 上做试点。等 3.0 的正式稳定版发布,再规划全面切换。

4. 踩坑实录:一次由Tag Cardinality引发的线上OOM排查与修复

去年双十一大促,我们负责的一个电商实时订单监控系统突然告警,InfluxDB 2.7.4 实例内存占用飙到 16G 上限,随后直接 OOM 重启。系统写入量其实不算夸张,峰值也就 8 万 QPS,按理说这个配置(8C16G)完全扛得住。

我第一反应是去查慢查询,但日志里全是正常的写入请求,没有复杂查询。接着看监控,发现 influxdb_tsdb_cache_memory_bytes 这个指标一直在涨,从不下降。这时候我才意识到问题可能出在 Tag Cardinality(标签基数) 上。

我们的订单数据 Schema 设计是这样的:

-- 原来的 measurement 设计(问题版本) measurement: order_metrics tags: order_id -- 错误示范:把订单ID作为tag user_id -- 错误示范:把用户ID作为tag region fields: amount item_count timestamp

我当时为了查询方便,把 order_iduser_id 都塞进了 Tag,心想这样就能快速按订单或用户筛选。但我忽略了 InfluxDB 的底层原理:Tag 是用来建立索引的,每个 Tag 的唯一值组合都会在内存中维护一个索引条目

一个订单 ID 是全局唯一的,这意味着每写入一条数据,就会在内存里新增一个索引条目。大促期间每分钟产生几十万新订单,内存不炸才怪。

我通过 InfluxDB 自带的 CLI 工具验证了我的猜测:

# 查看 tag cardinality influx query ' import "influxdata/influxdb/schema" schema.tagKeys(bucket: "order_monitor") ' --host http://localhost:8086 # 统计 order_id 的基数 influx query ' from(bucket: "order_monitor") |> range(start: -1h) |> filter(fn: (r) => r._measurement == "order_metrics") |> group(columns: ["order_id"]) |> count() ' --host http://localhost:8086

结果出来我傻了,order_id 的基数在 1 小时内达到了 200 万,这就是内存泄漏的根源。

修复方案很简单,把高基数的字段从 Tag 移到 Field:

-- 修复后的 Schema 设计 measurement: order_metrics tags: region -- 低基数:只有几个地区 channel -- 低基数:app, web, mini_program fields: order_id -- 移到 field,不再参与索引 user_id -- 移到 field amount item_count timestamp

修改后,我写了一个数据迁移脚本,把历史数据重新写入新的 bucket:

import influxdb_client from influxdb_client.client.write_api import SYNCHRONOUS import time # 初始化客户端,我们用的 2.7.4 版本 client = influxdb_client.InfluxDBClient( url="http://localhost:8086", token="your-admin-token", org="production" ) write_api = client.write_api(write_options=SYNCHRONOUS) query_api = client.query_api() # 读取旧数据并转换 query = ''' from(bucket: "order_monitor_old") |> range(start: -30d) |> filter(fn: (r) => r._measurement == "order_metrics") ''' tables = query_api.query(query) points = [] count = 0 for table in tables: for record in table.records: # 重构数据点,把 order_id 和 user_id 从 tag 变成 field new_point = influxdb_client.Point("order_metrics") \ .tag("region", record.values.get("region", "unknown")) \ .tag("channel", record.values.get("channel", "unknown")) \ .field("order_id", record.values.get("order_id", "")) \ .field("user_id", record.values.get("user_id", "")) \ .field("amount", float(record.values.get("amount", 0))) \ .field("item_count", int(record.values.get("item_count", 0))) \ .time(record.time) points.append(new_point) count += 1 # 批量写入,每 5000 条提交一次 if count % 5000 == 0: write_api.write(bucket="order_monitor_new", record=points) points = [] print(f"Migrated {count} records...") # 写入剩余数据 if points: write_api.write(bucket="order_monitor_new", record=points) print(f"Total migrated: {count} records")

改完之后,内存占用直接从 14G 降到了 1.2G,写入延迟也从 800ms 优化到了 120ms。

教训总结:Tag 只放用于分组的低基数维度(如 region、host、env),绝对不要把 UUID、手机号、订单号这种高基数标识放进去。如果你不确定某个字段适不适合做 Tag,先算一下它的唯一值数量,超过 10 万就要警惕了。

---

5. 生态集成:基于Telegraf+Flink构建实时流处理分析流水线

我们现在的架构是:设备端产生数据 -> Telegraf 采集 -> Kafka 缓冲 -> Flink 实时处理 -> InfluxDB 存储 -> Grafana 展示。这套流水线在 3.0 架构下跑得很稳,特别是 InfluxDB 3.0 基于 Apache Arrow 的引擎,查询性能比 2.x 提升了不少。

先说 Telegraf 配置。我们采集的是工厂传感器的温度、振动数据,每个车间 200 个传感器,每秒上报一次。Telegraf 的配置要针对高吞吐做优化:

# telegraf.conf [agent] interval = "1s" round_interval = true metric_batch_size = 5000 # 增大批次,减少网络开销 metric_buffer_limit = 100000 collection_jitter = "0s" flush_interval = "5s" # 输入插件:MQTT 订阅传感器数据 [[inputs.mqtt_consumer]] servers = ["tcp://mqtt-broker:1883"] topics = ["factory/sensors/+/data"] qos = 1 connection_timeout = "30s" data_format = "json" # 关键:把传感器ID映射为 tag [inputs.mqtt_consumer.tags] source = "mqtt" # 处理器:转换数据格式 [[processors.converter]] [processors.converter.fields] float = ["temperature", "vibration", "humidity"] # 输出到 Kafka,不直接写 InfluxDB,避免背压 [[outputs.kafka]] brokers = ["kafka-1:9092", "kafka-2:9092"] topic = "sensor_metrics" data_format = "influx" influx_max_line_bytes = 0 timeout = "30s" # 关键配置:开启压缩 compression_codec = "snappy"

数据进 Kafka 后,我用 Flink 1.17 做实时降采样和异常检测。这里有个坑:Flink 默认的序列化方式跟 InfluxDB 的 Line Protocol 不兼容,所以我自定义了一个 Sink。

// Flink 主处理逻辑 import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.api.datastream.DataStream; import org.apache.flink.api.common.eventtime.WatermarkStrategy; import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumer; import org.apache.flink.api.common.serialization.SimpleStringSchema; import java.util.Properties; public class SensorAnalyticsJob { public static void main(String[] args) throws Exception { final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.enableCheckpointing(5000); // 每5秒做一次 checkpoint Properties props = new Properties(); props.setProperty("bootstrap.servers", "kafka-1:9092"); props.setProperty("group.id", "flink-sensor-processor"); // 从 Kafka 消费原始传感器数据 FlinkKafkaConsumer<String> source = new FlinkKafkaConsumer<>( "sensor_metrics", new SimpleStringSchema(), props ); source.assignTimestampsAndWatermarks(WatermarkStrategy.noWatermarks()); DataStream<String> stream = env.addSource(source); // 实时处理:解析 -> 异常检测 -> 降采样 DataStream<String> processed = stream .map(new SensorDataParser()) // 解析 Line Protocol .keyBy(r -> r.getSensorId()) // 按传感器分组 .process(new AnomalyDetector()) // 异常检测逻辑 .map(new DownsampleMapper()); // 降采样到 10 秒粒度 // 写入 InfluxDB 3.0 processed.addSink(new InfluxDBSink()); env.execute("Sensor-RealTime-Analytics"); } } // 自定义 InfluxDB Sink import org.apache.flink.configuration.Configuration; import org.apache.flink.streaming.api.functions.sink.RichSinkFunction; import okhttp3.*; public class InfluxDBSink extends RichSinkFunction<String> { private OkHttpClient client; private String influxUrl = "http://influxdb:8086/api/v2/write"; private String token = "your-token"; private String org = "factory"; private String bucket = "sensor_data"; @Override public void open(Configuration parameters) { client = new OkHttpClient(); } @Override public void invoke(String lineProtocol, Context context) { // 批量写入,这里简化成单条,生产环境建议攒批 RequestBody body = RequestBody.create(lineProtocol, MediaType.parse("text/plain")); Request request = new Request.Builder() .url(influxUrl + "?org=" + org + "&bucket=" + bucket) .addHeader("Authorization", "Token " + token) .post(body) .build(); try { client.newCall(request).execute().close(); } catch (Exception e) { System.err.println("Write failed: " + e.getMessage()); } } }

在 InfluxDB 端,我配置了 保留策略(RP)任务(Task) 来做自动降采样。3.0 的任务引擎比 2.x 好用,直接用 SQL 就能写:

-- 创建降采样任务:每 5 分钟执行一次,把 10 秒精度的数据聚合成 1 分钟精度 CREATE TASK downsample_1m EVERY 5m AS INSERT INTO sensor_data_retention.sensor_metrics_1m SELECT region, device_type, DATE_BIN(INTERVAL '1 minute', time, '2024-01-01') AS time, AVG(temperature) AS avg_temp, MAX(vibration) AS max_vibration, COUNT(*) AS sample_count FROM sensor_data.sensor_metrics WHERE time >= NOW() - INTERVAL '10 minutes' GROUP BY region, device_type, DATE_BIN(INTERVAL '1 minute', time, '2024-01-01')

这个流水线跑了一年多,每天处理 20 亿条数据,InfluxDB 的压缩率大概在 1:8 左右,存储成本比我们之前用 TimescaleDB 低了 40%。

---

6. 面试高频:InfluxDB核心概念与RP策略深度解析

面试时经常有人问我:"InfluxDB 和 MySQL 到底有啥本质区别?" 我一般不直接背定义,而是拿我们订单系统的例子说事。

Measurement 相当于表,但没那么简单

在 MySQL 里,一张 orders 表存所有订单。但在 InfluxDB 里,我把 order_metrics 这个 measurement 设计成只存指标,不存实体属性。

-- MySQL 的表结构(存实体) CREATE TABLE orders ( order_id VARCHAR(64) PRIMARY KEY, user_id BIGINT, amount DECIMAL(10,2), region VARCHAR(20), created_at TIMESTAMP ); -- InfluxDB 的 measurement(存指标变化) -- 数据长这样: -- order_metrics,region=sh,channel=app amount=299.0,item_count=3 1704067200000000000

Tag 和 Field 的区别是面试必问,也是最容易用错的

我之前踩过的坑就是没分清这两者的用途。Tag 是索引,Field 是值。

-- 正确的设计 measurement: server_metrics tags: host -- 索引:用于 WHERE 和 GROUP BY,基数低(几百台服务器) env -- 索引:prod, staging, dev service -- 索引:order-service, user-service fields: cpu_usage -- 非索引:用于计算 AVG/MAX,不需要精确匹配 mem_usage -- 非索引 qps -- 非索引

为什么这么设计?因为 Tag 会进内存索引,Field 不会。如果你把 cpu_usage 放进 Tag,那每次 CPU 值变化(比如从 45.2 变到 45.3),都会产生一个新的索引条目。

数据保留策略(RP)是我们生产环境里最实用的功能

我们系统里,监控数据分三层存储:

-- 1. 原始数据:保留 7 天,用于实时排障 CREATE RETENTION POLICY "raw_7d" ON "monitoring" DURATION 7d REPLICATION 1 SHARD DURATION 1d -- 分片周期,影响查询性能 -- 2. 降采样数据:保留 30 天,用于周报分析 CREATE RETENTION POLICY "downsample_30d" ON "monitoring" DURATION 30d REPLICATION 1 SHARD DURATION 7d -- 3. 归档数据:保留 1 年,用于月度/年度趋势 CREATE RETENTION POLICY "archive_1y" ON "monitoring" DURATION 52w REPLICATION 1 SHARD DURATION 30d

这里有个细节:SHARD DURATION 设置很关键。如果设置太短(比如 1h),会产生大量小文件,查询变慢;如果太长(比如 1 年),删除过期数据时 InfluxDB 要扫描整个大文件,也会卡顿。我们测试下来,原始数据用 1d,降采样用 7d 是比较均衡的。

实际写入时的 RP 指定

from influxdb_client import InfluxDBClient, Point from datetime import datetime client = InfluxDBClient(url="http://localhost:8086", token="xxx", org="prod") # 写入到指定 RP write_api = client.write_api() # 方式一:在 bucket 名后加 RP(2.x 兼容写法) write_api.write( bucket="monitoring/raw_7d", # 指定 RP record=Point("server_metrics") .tag("host", "server-01") .tag("env", "prod") .field("cpu", 85.2) .time(datetime.utcnow()) ) # 方式二:用 Task 自动路由(推荐) # 在 InfluxDB UI 里配置 Task,自动把数据从 raw_7d 复制到 downsample_30d

面试时我还会补充一个细节:InfluxDB 3.0 的 RP 机制和 2.x 略有不同。3.0 基于 Apache Arrow,底层存储是 Parquet 格式,数据过期是通过删除整个 Parquet 文件实现的,比 2.x 的 TSM 引擎更高效。这也是为什么我们今年开始往 3.0 迁移的原因之一。

如果你在面试中被问到 "如何优化 InfluxDB 查询",我的经验是:第一,确保 WHERE 条件里用的是 Tag 而不是 Field;第二,时间范围尽量精确,不要 WHERE time > '2024-01-01'(全量扫描);第三,GROUP BY 的字段必须是 Tag,否则会触发全表扫描。

我们之前有个慢查询,就是因为用了 GROUP BY field_name,导致查询耗时从 200ms 飙升到 8 秒。改回 GROUP BY tag_name 后立刻恢复正常。

站长实战手记

去年帮一个做智能充电桩的朋友救火,印象太深了。他们当时要监控全国两万多根桩的实时状态,每秒上报数据量大概在 5 万点左右。最初技术负责人坚持用 MySQL,结果跑了不到一周,库就撑不住了,磁盘 IO 常年 100%,历史数据稍微一多,查询直接超时。

我接手后第一件事就是换存储,上了 InfluxDB 2.7。当时为了图省事,我把充电桩的序列号直接塞进了 Tag,心想这样查单桩数据方便。结果上线第二天,整个数据库直接 OOM 挂掉。我折腾了大半夜,盯着日志看,才反应过来:两万多个序列号作为 Tag,产生了极高的基数(Cardinality),InfluxDB 的内存直接被元数据撑爆了。

后来我重构了 Schema,把序列号这种高基数数据挪到了 Field 里,Tag 只保留城市、运营商这种低基数的维度。改完重启,内存瞬间降了下来,查询也丝滑了。

关于选型,我现在的看法很直接:

* 如果你只是做后台管理系统的报表,数据量一天连一万条都不到,千万别上时序库。MySQL 或 PostgreSQL 完全够用,引入新组件只会徒增运维负担。

* 真正的场景是那种设备多、上报频率高、且你需要看趋势图的业务。比如监控服务器 CPU、IoT 设备上报,这时候 InfluxDB 的压缩率和查询速度才是真的香。

最后给个建议:学 InfluxDB 别光看概念,一定要亲手去建个库,试着往里灌点数据。特别是 Tag 和 Field 的区别,这是新手最容易翻车的地方,别等上线了才拍大腿。