序列化成本
在上一篇文章中,我们讨论了检查任何给定查询的性能,但这并不是导致“慢查询”的唯一原因。将数据从数据库移动到请求者的成本也可能很高,有时甚至比查询本身的成本还要高。在本文中,我将尝试向您展示为什么这很重要,并介绍一些相关信息。
对于本文,我们将重点介绍其中的某些部分,主要是从数据库向后端发送数据,以及其他一些方面:
概括
通常会发生什么
当你有一个来自后端的解耦客户端时,你通常会有类似这样的情况:
- 客户提出请求
- 后端接收请求
- 验证用户身份
- 也许可以通过数据库调用来获取用户
- 进行一个或多个数据库调用
- 尽可能使用单个查询是避免向数据库请求数据负担的最佳方法
- 将响应发送回客户端
- 也许你会在发回数据之前对其进行转换
您在列表中看不到的是与操作相关的多个序列化和反序列化成本。从数据库或任何外部信息源获取数据都会产生反序列化成本,而当您序列化回客户端时,您又需要再次支付这笔费用。
“我为什么要为此付费?”你问,每个数据交互点:数据库、服务器、客户端;它们每个都有自己的数据处理域,即使是同一个域(TypeScript 服务器/客户端),你仍然需要在它们之间发送数据,为此你需要一种通用语言来发送,并让接收者理解和解析它。
正在传输的数据
传输的数据是什么样的?Postgres 有两种数据格式:文本和二进制。
# Packet: t=1695825669.456395, session=213070643341250
PGSQL: type=Query, F -> B
QUERY query=select * from users limit 200;
# Packet: t=1695825669.458287, session=213070643341250
PGSQL: type=RowDescription, B -> F
ROW DESCRIPTION: num_fields=3
---[Field 01]---
name='id'
type=1043
type_len=65535
type_mod=4294967295
relid=57371
attnum=1
format=0
---[Field 02]---
name='username'
type=25
type_len=65535
type_mod=4294967295
relid=57371
attnum=2
format=0
---[Field 03]---
name='created_at'
type=1114
type_len=8
type_mod=4294967295
relid=57371
attnum=3
format=0
# Packet: t=1695825669.458287, session=213070643341250
PGSQL: type=DataRow, B -> F
DATA ROW num_values=3
---[Value 0001]---
length=26
value='01H7GXCFN7K4ZQEDP59G31SY4D'
---[Value 0002]---
length=10
value='username-1'
---[Value 0003]---
length=26
value='2023-08-10 23:44:21.689678'
# Packet: t=1695825669.458287, session=213070643341250
PGSQL: type=DataRow, B -> F
DATA ROW num_values=3
---[Value 0001]---
length=26
value='01H7GXCFN7865YD7Q16BXEAB8N'
---[Value 0002]---
length=10
value='username-2'
---[Value 0003]---
length=26
value='2023-08-10 23:44:21.689678'
...
# Packet: t=1695825677.943246, session=213070643341250
PGSQL: type=BackendKeyData, B -> F
BACKEND KEY DATA pid=1463956811, key=1412773965
# Packet: t=1695825677.943246, session=213070643341250
PGSQL: type=NotificationResponse, B -> F
NOTIFICATION RESPONSE pid=1970496882, channel='name-8785', payload=''
...
这是从后端(postgres)发送到客户端(psql)的部分数据;
- PGSQL: type=RowDescription, B -> F:此部分消息表明这是一条“RowDescription”消息。此消息由后端(B)发送到前端(F),通常作为查询执行时结果集描述的一部分。
- 行描述:num_fields=3:此部分告诉您 RowDescription 消息正在描述一行包含三个字段(列)。
- [字段 01]:此部分提供有关该行中第一个字段(列)的信息。
- name='id':该字段名为“id”,表示它对应结果集中名为“id”的列。
- type=1043:type 字段指定该列的数据类型。在本例中,type 表示为整数,“1043”对应于 PostgreSQL 中的特定数据类型。
- type_len=65535:“type_len”字段指示数据类型的长度。在本例中,它被设置为一个非常大的值 65535,该值可能用于表示最大长度未定义的可变长度字符串(例如 varchar)。
- type_mod=4294967295:“type_mod”字段通常表示数据类型的修饰符。在本例中,较大的值“4294967295”可能表示该数据类型没有修饰符。
- relid=57371:relid 字段通常指的是该列所属表的 OID(对象 ID)。在本例中,57371 就是关联表的 OID。
- attnum=1:“attnum”字段表示表中列的属性编号。它设置为“1”,表示这是表中的第一列。
- format=0:“format”字段指定数据的格式。值为“0”表示数据为文本格式。在二进制模式下,此值将被设置为“1”。
如果你有更深入的了解,你可能会想:“为什么不使用像 protobuf 这样的东西?你可以让这个过程更快!”
是的,没错。但是使用protobuf,你需要事先知道服务器和客户端发送的数据的结构,这就违背了获取更少数据或使用sql聚合的初衷。
双重序列化
文章中的序列化是指获取数据,转换为共享的通用语言(json、yaml、xml 等),然后将其发送给请求将其反序列化为已知数据的人员。
但究竟是什么呢double serialization
?双重序列化是指针对同一请求对数据进行两次序列化/反序列化。想知道具体是怎么回事吗?数据库 → API → 客户端。每个箭头都表示数据经过序列化、传输,然后反序列化。如果不谨慎处理数据传递,最终可能会产生大量垃圾数据,并导致性能问题,因为这可能是一项繁重的操作。需要传输的数据越多,产生的垃圾就越多。
async function someFunction(_: Request) {
// Data is first serialized in the database, shipped,
// then deserialized here to have a valid data object
const data = await fetchFromDb()
// Maybe you need to transform the data to aggregate or remove fields
// const transformedData = someMapFunction(data);
// You serialize data again here,
// shipped for the client deserialize it,
// then transform into something useful for them
return new Response(JSON.stringify(data), {
status: 200,
headers: {
'Content-Type': 'application/json'
}
})
}
总结
或许你从未想过自己搜索和发送了多少信息。毫不留情地收集所有信息要简单得多,但正是这些小细节,像滚雪球一样,开始制造问题。
悲观地思考:
- 您获取的信息超出了您的需要。
- 服务器必须做更多的工作来处理和发送信息。
- 需要通过网络发送的数据包更多,这增加了出现问题和数据包重新发送的可能性,从而增加了延迟。
- 应用程序需要处理比必要更多的信息,因此需要做更多的工作。
- 如果它是一个对内存采取更“宽松”方法的应用程序,那么应用程序本身就会变得更重(垃圾收集)。
- 随着需要发送的信息越来越多,响应也变得越来越繁重。既需要将信息从一个域转换为通用格式,又需要将所有信息再次通过网络发送。
- 传输了更多信息,现在您也开始为通过网络传输的数据传输付费。
- 接收信息的客户将需要付出更多努力来处理信息。
仅仅决定获取超出必要范围的信息就产生了巨大的滚雪球效应。或许,当一切都需要快速传递时,事后重新考虑传输的信息量是有意义的。但认为这个问题不存在却存在相当大的风险,可能会滚雪球效应越滚越大。
参考
- https://www.wireshark.org/用于检查软件包