V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX  ›  wxf666  ›  全部回复第 1 页 / 共 27 页
回复总数  536
1  2  3  4  5  6  7  8  9  10 ... 27  
1 天前
回复了 tramm 创建的主题 数据库 有没有推荐的时序数据库或者其他数据库?
需求是这样吗:每秒最多写入 (100W 在线设备 / 5s/条) = 20W 条数据,每条 (4 << 40) / 6e10 = 73 字节?

没用过太高大上的数据库,只有点朴素的想法。

1. 每小时的数据,存在内存里(需 20W * 3600 * 100 / 2 ^ 30 = 67 GB 内存)
2. 数据库 ID 设计为(时间戳_年~小时 + 设备 ID + 时间戳_分~秒)
3. 整点后,按 (设备 ID, 时间) 顺序,写入这一小时数据,至数据库。(顺序写入,很快的)
4. 如果怕丢失数据,接收到上报信息后,也同步写入到 csv 文件里。
 (也是顺序写入,很快的。如果只是《时间戳,设备 ID 》信息,每秒顺序写入 (10 + 1 + 6 + 1) * 20W / 2 ^ 20 = 3.43 MB 数据而已)

这样搞下来,数据库相当于《读多写少》了。正好可以考虑 SQLite 、DuckDB 等。。

试了下,对于 SQLite ,如果只是存上述主键的话,一小时 7.2 亿数据,需要 10GB 文件,写了 5 分钟。。(平均每秒 200W ,还行)

前几天回帖过,SQLite 可以 0.1 秒,在 1 亿数据里,找什么设备,什么时候离线超过 24 小时(每台设备每分钟上报一次)。

当然,这是缓存了 1 GB 内存的结果。在固态上,完全无缓存,需要几秒钟。

具体要啥查询,题主也没写。。只能联想到前几天,有点相关的场景了。。

如果换 DuckDB ,应该能更快。我没试过。
@wanghr64 #5 老板:帮我统计该员工认真工作时长?

https://i.imgur.com/krir4IG.png https://i.imgur.com/krir4IG.png
2 天前
回复了 irihiyahnj 创建的主题 信息安全 用了 23 年的 QQ 被盗了
@testonly #5 听起来有点像网络世界的房产税?

每年贡献点钱保号?不想交,就会被赶出去?价值越高的号,概率越高?

狠一点,就像毛伊岛一样,强抢了事?

https://i.imgur.com/F29pmQ6.png https://i.imgur.com/F29pmQ6.png
@Archeb #2 以前想让 OpenAI 出海龟汤题给我,它一直不理解是啥。。

我就举个例子(好像涉及杀人啥的),然后就被封号了。。

https://i.imgur.com/krir4IG.png https://i.imgur.com/krir4IG.png
3 天前
回复了 Zaden 创建的主题 MySQL mysql 如何高效获取两条相邻推送时间间隔
@ys1992 #10

我又改了改,速度提升到,仅 0.2 秒了。。

原来大部分时间,都在遍历这张一亿数据的表,查有哪些独立的 point_id 了。。

如果有 point 表,直接从里面抽出所有 point_id 即可。我是手动用 generate_series(1, 100) 模拟的。

https://i.imgur.com/krir4IG.png https://i.imgur.com/krir4IG.png


@Zaden #22:

可能你和我原因一样,想分组 point_id ,查最后时间,数据库居然扫全索引去了。。

其他快的原因,我觉得就是,不断跳来跳去,直接查最接近 24 小时前的时间,而不是每两条时间一一对比。

这需要高度依赖 4K 随机读取。比如,浏览器里运行数据库时,会将数据留存在内存里,自然快一些。

- 本地上测试,同样缓存在内存里时,只需 0.1 秒
- 7 年前垃圾固态上(顺序读 420 MB/s ,4K 随机读 25 MB/s ),且清除系统对文件缓存后,需 10 秒
- 10 年前硬盘上(顺序读 150 MB/s ,4K 随机读 0.65 MB/s ),也清除系统对文件缓存后,需 80 秒。。

我也不知道,为啥硬盘 4K 随机读,比固态差近 40 倍,但耗时才慢 8 倍。。

可能夹杂着一些顺序读取(比如有时跳到相邻页上了?),使得差距没这么大吧。。

总之,我浏览器里都能缓存一亿数据(约 1.3 GB ),对你来说应该也不是啥难事的。


## 0.2 秒截图

https://i.imgur.com/0iDVpvs.png
4 天前
回复了 Zaden 创建的主题 MySQL mysql 如何高效获取两条相邻推送时间间隔
@ys1992 #10 不一定要扫全表吧。。

不断根据索引,查最接近 24 小时前的推送时间,应该只需要检查很少数据量,就能算出来了?


@Zaden 我用最垃圾的 SQLite ,在七年前的 i5-8250U 轻薄本上,效率一般的浏览器 wasm 环境里,试了下,

100 设备、一亿数据(每分钟推送、持续两年),每设备断线十次,每次 1~2 天,

只需 7 秒,就能全找出来了?


## 截图

https://i.imgur.com/lZyiLTp.png


## SQL 测试代码

```sql
-- V 站吞空格,缩进改成全角空格了

-- 建表,当 (point_id, push_time) 索引用
DROP TABLE IF EXISTS data;
CREATE TABLE data (
   point_id INT,
   push_time INT,
   PRIMARY KEY (point_id, push_time)
) WITHOUT ROWID;

-- 添加一亿条数据( 100 设备、每分钟推送、持续两年)
INSERT INTO data
SELECT point.value, time.value
FROM generate_series(1, 100) point
JOIN generate_series(
   unixepoch('2024-05-24', '-2 year'),
   unixepoch('2024-05-24'), 60) time;

-- 删掉断线数据( 100 设备,每台断线 10 次,第 N 次是 id*N 天前,持续 1, 1.1, ..., 1.9 天)
DELETE FROM data
WHERE (point_id, push_time) IN (
   SELECT point_id, push_time
   FROM generate_series(1, 100) point
   JOIN generate_series(1, 10) nth
   CROSS JOIN data
   WHERE point_id = point.value
   AND push_time >= unixepoch('2024-05-24', format('-%f day', nth.value * (point.value + 0.1) - 0.1))
   AND push_time < unixepoch('2024-05-24', format('-%f day', nth.value * point.value - 1))
);

-- 循环每个设备,从今天开始,不断往前找,最接近 24 小时前的推送时间
-- 若俩时间 >= 24 小时,则属于断线过久
WITH RECURSIVE
   t(id, a, b) AS (
     SELECT point_id, unixepoch('2024-05-24'), NULL
     FROM data
     GROUP BY point_id
     UNION ALL
     SELECT id, ifnull((
         SELECT min(push_time)
         FROM data
         WHERE point_id = t.id
         AND push_time > t.a - 86400
         AND push_time < t.a
      ), (
         SELECT max(push_time)
         FROM data
         WHERE point_id = t.id
         AND push_time < t.a - 86400
      )), a
     FROM t
     WHERE a
  )
SELECT
   id "设备 ID",
   datetime(a, 'auto') "最后在线",
   format('%d 天 %d 小时 %d 分钟', (b-a)/86400, (b-a)%86400/3600, (b-a)%3600/60) "断线时长"
FROM t
WHERE b - a >= 86400
ORDER BY id IN (1, 2, 73) DESC, id, a DESC;
```
5 天前
回复了 freewind 创建的主题 数据库 请教多标签查询怎么做效率高?
@yjhatfdu2 #19 我用 SQLite 试了下,感觉用普通 SQL ,也勉强能满足这个需求。

## 结果

- 数据:4000 标签,1 亿数据,50 标签/数据(即,倒排索引中,约 50 亿数据 ID )
- 性能:0.8s 查询一个标签,拥有的 625W 数据 ID ,升序输出
- 体积:倒排索引表,约 11 GB

## 环境

- SYS:Win11
- CPU:i7-4790
- MEM:16 GB ,1600 MHz
- HDD:顺序读 150 MB/s ,4K 随机读 0.65 MB/s

## 多标签呢?

SQLite 不支持 Sort Merge Join ,多个标签一起查时,很慢。。
需要物化每个标签的结果,再由 SQLite 进行布隆匹配、B 树二分查找。。

(测前,都用 RamMap 清除了,系统对文件的缓存)
2 个 625W 数据 ID 的标签,结果 250W ,耗时 4.9 秒,
3 个 625W 数据 ID 的标签,结果 205W ,耗时 8.5 秒,
4 个 625W 数据 ID 的标签,结果 120W ,耗时 11.9 秒。。

不知道能不能用 Virtual Table ,写个表值函数(知道有,没写过),自己实现:

- Sort Merge Join 的效果,流式匹配所有标签,免除物化表、上千万次 B 树匹配的开销
- 提速读取数据 ID ( SQL 中,若写为 `SELECT COUNT(*)`,不计算具体数据 ID ,会提速到 0.1s )

## 倒排索引结构

- 表结构:`(<标签 ID ,高位数据 ID >,低 11 位数据 ID 集合 BLOB )`
- 若 > 146 个低位数据 ID ,第 3 列视为 293 字节的 Bitmap ,但每字节最高位弃用( SQLite 中没有便捷转换 128~255 为单字节 BLOB 的方法)
- 否则,每个低位数据 ID ,转为二字节 UTF-8 字符串( 0~127 需后补 `x'00'`),并串联起来。

## 测试数据

- 标签 ID:`[0, 3999]`
- 数据 ID:`[1, 1e8]`
- 每个数据拥有的标签 ID:`[(数据 ID * 1) % 4000, (数据 ID * 2) % 4000, ..., (数据 ID * 50) % 4000]`
- 标签 ID 为 400 、1600 、2800 、3600 时,拥有的数据 ID 最多,都为 625W 个

## 查询 SQL

```sql
-- V 站吞空格,缩进改为全角空格了
WITH
   t1 AS MATERIALIZED (
     SELECT (data_hi_id << 11) |
       CASE WHEN i.stop = 7 THEN -- 低位数据 ID 量 <= 146 ,逐 2 字节翻译
         IFNULL(unicode(substr(data_lo_ids, j.value, 2)), 0)
       WHEN j.stop & (1 << (6 + j.value)) THEN -- Bitmap ,逐 bit 翻译
         i.value + j.value - 1
       END AS data_id
     FROM tag_data
     JOIN generate_series(7, IIF(length(data_lo_ids) < 293, 1, 293) * 7, 7) i
     JOIN generate_series(
       IIF(i.stop > 7, -6, 1),
       IIF(i.stop > 7, unicode(substr(data_lo_ids, i.value / 7, 1)), length(data_lo_ids)),
       IIF(i.stop > 7, 1, 2)) j
     WHERE tag_id = '《第一个标签 ID 》'
    -- 这句加了好慢。。怀疑是反复计算 data_id 导致
    -- AND data_id IS NOT NULL
  ),
  -- 以下结构类似,省略了
   t2 AS (...),
   t3 AS (...),
   t4 AS (...)
-- 写成 COUNT(*) 且仅 t1 时,飞快
-- 估计是不用算具体 data_id 了
SELECT COUNT(data_id)
FROM t1
JOIN t2 USING(data_id)
JOIN t3 USING(data_id)
JOIN t4 USING(data_id)
-- 匹配到后面,预计数据量很少时,可采用这种方式:
/*
WHERE EXISTS (
   SELECT 1
   FROM tag_data
   WHERE tag_id = '《第四个标签 ID 》'
   AND data_hi_id = data_id >> 11
   AND CASE WHEN octet_length(data_lo_ids) <= 292 THEN
       instr(data_lo_ids, IIF(data_id & 0x780, char(data_id & 0x7FF), char(data_id & 0x7FF, 0)))
     ELSE
       unicode(substr(data_lo_ids, (data_id & 0x7FF) / 7 + 1, 1)) & (1 << (data_id & 0x7FF) % 7)
     END)
*/
```

## 增删改

用触发器实现。但很慢。。

C++ 新人,正好练练手,生成倒排索引库(这都要花十几分钟。。):

```c++
// 同上,缩进是全角空格
#include <string>
#include <vector>
#include <iostream>
#include <string_view>
#include "sqlite_modern_cpp.h"

constexpr size_t MAX_DATA_ID = 1e8;
constexpr size_t MAX_TAG_COUNT = 50;
constexpr size_t MAX_TAG_ID = 4000 - 1;
constexpr size_t MAX_MEMORY = 8ull << 30;
constexpr auto DB_PATH = "D:/out.db";

int main() {

   struct TagData {
     struct {
       uint8_t data_lo_ids[(2048 + 6) / 7]{};
    } data_hi_ids[(MAX_DATA_ID >> 11) + 1]{};
  };

   constexpr int tag_steps = MAX_MEMORY / sizeof(TagData);

   sqlite::database db(DB_PATH);
   db << "CREATE TABLE IF NOT EXISTS tag_data ("
     "   tag_id INT,"
     "   data_hi_id INT,"
     "   count INT,"
     "   data_lo_ids BLOB,"
     "   PRIMARY KEY (tag_id, data_hi_id)"
     ") WITHOUT ROWID";

   std::string blob_buf;
   std::vector<uint16_t> data_lo_ids;
   auto insert_stmt = db << "INSERT INTO tag_data VALUES (?, ?, ?, CAST(? AS BLOB))";

   for (size_t tag_id_beg = 0; tag_id_beg <= MAX_TAG_ID; tag_id_beg += tag_steps) {

     auto tag_id_end = (std::min)(tag_id_beg + tag_steps - 1, MAX_TAG_ID);
     auto tags = std::make_unique<TagData[]>(tag_id_end - tag_id_beg + 1);

     db << "BEGIN";
     std::cout
      << "正在计算 1~" << MAX_DATA_ID << ",每个数的 1~" << MAX_TAG_COUNT << " 倍,÷" << MAX_TAG_ID + 1
      << " 后的余数,是否在 [" << tag_id_beg << ", " << tag_id_end << "] 范围内……" << std::endl;

     for (size_t data_id = 1; data_id <= MAX_DATA_ID; data_id++) {
       for (size_t times = 1; times <= MAX_TAG_COUNT; times++) {
         size_t tag_id = (data_id * times) % (MAX_TAG_ID + 1);
         if (tag_id >= tag_id_beg && tag_id <= tag_id_end)
           tags[tag_id - tag_id_beg]
            .data_hi_ids[data_id >> 11]
            .data_lo_ids[(data_id & 0x7FF) / 7] |= 1 << ((data_id & 0x7FF) % 7);
      }
    }

     for (size_t tag_id = tag_id_beg; tag_id <= tag_id_end; tag_id++) {

       auto& tag = tags[tag_id - tag_id_beg];
       std::cout << "正在写入 tag_id = " << tag_id << " 至数据库……\r";

       for (auto& data_hi: tag.data_hi_ids) {
         data_lo_ids.clear();
         for (size_t i = 0; i < sizeof data_hi.data_lo_ids; i++)
           if (data_hi.data_lo_ids[i])
             for (size_t j = 0; j < 7; j++)
               if (data_hi.data_lo_ids[i] & (1 << j))
                 data_lo_ids.push_back(i * 7 + j);

         if (data_lo_ids.size() > 292 / 2)
           insert_stmt
            << tag_id << (&data_hi - tag.data_hi_ids) << data_lo_ids.size()
            << std::string_view((char *) data_hi.data_lo_ids, sizeof data_hi.data_lo_ids)
            >> []{};

         else if (!data_lo_ids.empty()) {
           blob_buf.clear();
           for (auto lo_id: data_lo_ids)
             if (lo_id > 127)
               blob_buf.append({char(0xC0 | (lo_id >> 6)), char(0x80 | (lo_id & 0x3F))});
             else
               blob_buf.append({char(lo_id), 0});
           insert_stmt
            << tag_id << (&data_hi - tag.data_hi_ids) << data_lo_ids.size() << blob_buf
            >> []{};
        }
      }
    }

     db << "COMMIT";
  }
}
```
11 天前
回复了 yunv2 创建的主题 程序员 关于 cpu 性能和 Java 编译速度的问题
@liprais #32 和楼主问的并不 100% 相关,只是有些联系。

想问的就是,Java 编译应该已经足够快了,还这么在意这个干啥?

就好比,现在 CPU 内存 硬盘,都很强了,结果还有人天天在意,PC 软件那几 KB 体积啥的。。

见到这种情况,你不会产生疑问吗? https://i.imgur.com/F29pmQ6.png https://i.imgur.com/F29pmQ6.png
11 天前
回复了 yunv2 创建的主题 程序员 关于 cpu 性能和 Java 编译速度的问题
不懂就问,Java 编译很复杂吗?不是简单翻译到字节码而已吗?

复杂的应该都在 JIT 时吧?搜集信息,实时优化,编译成 CPU 指令啥的?


那应该能参考 TinyCC 的编译速度?毕竟这货也不怎么优化代码,效率和 gcc -O0 相当。。

即,二十年前的 奔腾 4 CPU ,能(单线程)每秒编译 100W 行 C99 代码成 x86 指令?


那 Java 项目即使有过亿行代码,现代 8 核电脑,最多不也就等待 10 秒钟,就能编译完成了吗?

楼主为啥那么在意呢。。

https://i.imgur.com/F29pmQ6.png https://i.imgur.com/F29pmQ6.png
12 天前
回复了 freewind 创建的主题 数据库 请教多标签查询怎么做效率高?
@yjhatfdu2 #15 是 (tag_id, main_ids_array) 这样存的吗?

即,有 4000 条记录,总计 50 亿个主表 ID (平均每条记录有 125W 个主表 ID )?

每个 ID 有 4 字节,算下来也要 (5e9 * 4) / (1 << 20) = 18.6 GB 呀。。

https://i.imgur.com/F29pmQ6.png https://i.imgur.com/F29pmQ6.png
12 天前
回复了 freewind 创建的主题 数据库 请教多标签查询怎么做效率高?
@yjhatfdu2 #8 这个索引,实际有多大呢?

我怀疑它是按照 (array_value, id) 来存的,即存了 50 亿条。

每条 10 字节的话,也要快 50 GB 了。。
12 天前
回复了 yodhcn 创建的主题 数据库 数据库中间表要不要用联合主键?
@waytodelay #4

如果中间表字段少(比如就存 user_id 、group_id ),存索引不是还多一个自增字段吗?

如果中间表字段多,存索引的话,会不会大概率经常回表,查其他字段呢?

另外,索引相当于乱序插入了,页分裂肯定很频繁呀。。

(除非你严格按照 user_id 、group_id 自增顺序插入。那你的业务,是没有回头客啥的吗?)
13 天前
回复了 yodhcn 创建的主题 数据库 数据库中间表要不要用联合主键?
你用自增主键,表本身是不会页分裂。

但联合索引呢?你也严格按 (userId, groupId) 递增顺序,插入到表里吗?

你的业务请求,挺有规律啊。。https://i.imgur.com/krir4IG.png
@Int100 #10 要不,你模拟几个场景出来看看?比如:

10GB 的 A.json (格式是 [{"a": xxx, "b": yyy, ...}, ...],约一亿条),
100GB 的 B.json (格式是 [{"c": xxx, "d": yyy, ...}, ...],约十亿条),

需求一:A 里的 a, b 字段去 B 里的 c, d 查,保留结果是 xxx 的,再分组取前一万,再分季度汇总,。。。
需求二:。。。

https://i.imgur.com/F29pmQ6.png https://i.imgur.com/F29pmQ6.png
22 天前
回复了 Braisdom 创建的主题 推广 产品文档上线啦!
@Braisdom #16 不是概括总结数据为人话,是用人话输入想查询的结果,程序翻译成 SQL 执行?
22 天前
回复了 Braisdom 创建的主题 推广 产品文档上线啦!
都用上 AI 了,为嘛不直接翻译人话,出查询结果呢?
@NessajCN #23 这么久以来,有通过自己查,避免了什么东西吗?

想看看有没有必要,尽量少用别人编译好的二进制。。

https://i.imgur.com/F29pmQ6.png https://i.imgur.com/F29pmQ6.png
@debuggerx #13 看了看 AvaloniaUI 的展示案例,确实手机端很少,只有两个。界面看起来还行。。

QT 我一时也想不到,手机上的应用有啥。。

所以你觉得 AI 的能力边界是什么呢?

其他人也能写出赚这么多钱的,那就直接让他《写一个机器人,能日种几亩田》之类的。。?

https://i.imgur.com/krir4IG.png https://i.imgur.com/krir4IG.png
@debuggerx #11 QT 、AvaloniaUI ,这些不行吗?

AI 出来后,让它《写一个日赚过万的 App 》,会咋样呢?

换句话说,AI 的能力边界是什么呢?

https://i.imgur.com/F29pmQ6.png https://i.imgur.com/F29pmQ6.png
@kuanat #26 浏览器会利用系统的 DWM ,由其来单独合成普通页面与视频画面,而不是浏览器自己合成再提交?

放到 Windows 上说,就是利用了 DirectComposition ?

可这是 Win8 才引入的呀。。Win7- 是咋做到的呢?

https://i.imgur.com/F29pmQ6.png https://i.imgur.com/F29pmQ6.png
1  2  3  4  5  6  7  8  9  10 ... 27  
关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1093 人在线   最高记录 6679   ·     Select Language
创意工作者们的社区
World is powered by solitude
VERSION: 3.9.8.5 · 32ms · UTC 19:05 · PVG 03:05 · LAX 12:05 · JFK 15:05
Developed with CodeLauncher
♥ Do have faith in what you're doing.