很久之前接触过一个灰盒的漏洞检测工具,当时跟工具的开发者讨论了一些工具未解决的一些痛点,op 印象比较深的是其中一个点,这个工具的漏洞检测算法会参考调用栈信息,因此存储了海量的stack trace
数据,因为一些业务上的因素,这些数据需要能够存储一段时间才能删除,比如存储够一个月,那这累积下来存储空间的占用就是一个比较头疼的问题,比如 Java 的调用栈可能的一个数据样例:
java.lang.RuntimeException: level 2 exception
at com.msh.demo.exceptionStack.Test.fun2(Test.java:17)
at com.msh.demo.exceptionStack.Test.main(Test.java:24)
at sun.reflect.NativeMethodAccessorIm 猴子 pl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)
Caused by: java.io.IOException: level 1 exception
at com.msh.demo.exceptionStack.Test.fun1(Test.java:10)
at com.msh.demo.exceptionStack.Test.fun2(Test.java:15)
... 6 more
笔者之前看过美剧《硅谷》之后就对压缩算法领域很是感兴趣,也研究过一些压缩算法的实现,之前自己也针对性的设计过几款压缩算法整体压缩率还是很高的,于是就想着看看能不能解决这个问题,开始思考如何针对此类数据的特性针对性设计压缩算法:
sun.reflect.DelegatingMethodAccessorImpl.invoke
,让我们来以代码静态分析的视角来看这个数据,我们把这个称之为符号symbol
,则让我们把这个符号按照包分隔符.
进行分割,并且从上往下对应到一颗树上,则我们可以把程序代码中的所有可能的符号都看做是一颗根节点为空字符的树上的不同的路径(因为程序代码量是有限的,所以这颗树的大小其实也是可控的,一般不会特别离谱,暂不考虑代码混淆的情况的话...),则我们的问题转换为如何存储这棵树以及每个路径对应的节点sun.reflect.DelegatingMethodAccessorImpl.invoke
压缩到一个 4 字节甚至更短的整数,比如把sun.reflect.DelegatingMethodAccessorImpl.invoke
对应到整数10086
以上仅为个人的头脑风暴,还未编码验证,发到 v2 里与老哥们讨论一下,欢迎老哥们发表看法互相讨论。
最后,贴上一个美剧《硅谷》中我比较喜欢的一帧截图:
1
kneo 2023-09-28 02:38:40 +08:00 via Android
你有发帖子的闲工夫,不如先实现出来测试一下效果。
|
2
1423 2023-09-28 02:40:34 +08:00
先用常规通用算法跑一遍做个列表对比下吧, 约摸心里有个数
gzip xz zstd shoco smaz zstd 还能预训练字典, 用自己的数据训练后可以尝试压缩单独的数据(而不是只能用于大量数据) |
3
CC11001100 OP @kneo 代码在写了老哥,方案设计写了大概一个小时,只是感觉算法应该还有挺大优化空间,论坛里牛逼大佬比较多先发出来让大家喷一下,这样我在写的时候也能及时调整,代码估计要写个两三天国庆后应该能发出来具体效果 https://github.com/stack-database 😀
|
4
CC11001100 OP @1423 通用的压缩算法大部分都是基于字典和偏移尽量消除重复数据,比较依赖的是压缩的时候的字典窗口大小和一次性被压缩的数据量的大小,调用栈是热数据需要能够随时查询不适合批量压缩,如果 gzip 只能针对单条调用栈进行压缩,那个工具的开发者现在已经是在这么做了,消耗的资源( gzip 的内存、CPU 消耗等)与效果做性价比的话不是很理想(但也能凑活用。。。),所以才想着看看探索更极致的优化,当然这也只是一次尝试,也很可能还不如通用的压缩算法也说不好 😅
|
5
CC11001100 OP @1423 预训练字典的方式也跟那个工具的作者讨论过他否定了,他的业务场景不太满足,数据拿不到那是个类似私有部署的 CLI 工具,要训练只能在用户自己的电脑上训练。。。🤣
|
6
1423 2023-09-28 03:10:06 +08:00
那你想的挺清楚了,还要讨论啥呢?
|
7
1423 2023-09-28 03:10:58 +08:00
😅
|
8
nuk 2023-09-28 03:21:26 +08:00
重要的是重复数据,只做栈压缩没啥意义,exception 产生的地方是有限的
|
9
CC11001100 OP @1423 #6 哈哈哈我有点慌呗,我在压缩这块还没太多经验属于刚入门还在各种探索寻找案例练手,我怕自己有想不到的点导致后面全都是错的心态就真崩了那,发到论坛里让大家喷一喷聊一聊能更完善一些有问题我也能及时调整,我之前出现过埋头造轮子造完才发现因为最开始一个很基础的东西没想对到后面全都是错的整个都白费劲哈哈哈。。。😂
|
10
CC11001100 OP @nuk 是的老哥,所以压缩分为了两部分:
1. 一部分是栈内部的压缩,这一步相当于是所有的栈共享了一个全局字典来压缩自己内部的重复部分 2. 一部分是栈之间的压缩,完全相同的栈就直接引用同一个 id ,有点类似于 jvm 里的 fast throw 机制,完全相同的栈从第二次开始就是引用的之前的 id 而不重复存储,相当于把整个栈压缩为一个整数 对了怪我的背景故事介绍不完整,他那个工具调用栈不只是异常抛出,他那个工具是通过 java agent 技术给代码添加 hook 点来检出漏洞(原理跟 arthas 类似),每个 hook 点都会有对应的调用栈,hook 点是用户可配置的不可控,甚至可能会出现用户把每行代码都 hook 的情况。。。 |
11
Gota 2023-09-28 08:26:39 +08:00 via iPhone
pyroscope 做过类似的存储优化,可以参考看看: https://pyroscope.io/docs/storage-design/
|
12
nuk 2023-09-28 11:00:20 +08:00
@CC11001100 前缀压缩树,行号感觉需要单独处理。。
|
13
learningman 2023-09-28 12:40:49 +08:00 via Android
你直接用 brotli ,然后把他那个内置的字典换成适用于你这个场景的不就好了
|