如何基于Django/Postgres实现内存高效的二进制数据存储?
结论
你确实需要调整二进制数据的存储实现方式,当前的用法从底层设计上就存在内存浪费的问题,低并发下内存占满是必然结果。
问题核心原因
你将最大50MB的二进制数据存在models.BinaryField中,每次查询该字段时,Django ORM默认会把整段二进制数据全部加载到应用进程内存中,后续你用io.BytesIO处理时相当于又在内存里拷贝了一份全量数据,哪怕你只需要读取其中一小段,也会先占用两倍于文件大小的内存,2个并发请求就会占用至少200MB内存,再算上其他进程开销自然很容易触达内存上限。
可行优化方案
方案1(推荐):将大二进制数据移出数据库
数据库的设计目标是处理结构化查询,不是存储大二进制Blob,你可以直接把二进制文件存储到本地磁盘或者私有对象存储中,数据库仅存储文件的路径/唯一标识。
需要读取指定偏移量的内容时,直接用文件流的形式打开文件,调用seek()定位到对应位置后仅读取需要的字节长度即可,全程不会加载全量文件到内存,内存占用可以控制在KB级别。
方案2(必须存数据库时采用):数据库层面分段读取
如果业务要求必须把数据存在数据库的BinaryField中,不要直接查询全量字段:
- 常规查询时用
defer('二进制字段名')排除大字段,避免不必要的内存占用 - 需要读取指定段落的二进制数据时,直接调用对应数据库的内置截取函数(PostgreSQL用
substr,MySQL用substring),在数据库层面就只返回你需要的字节段,不会把全量50MB数据拉到应用层。
拆块存储的优化方向
你之前用many-to-one关联块的方案开销高,是因为每次读数据都会查询所有关联块。如果要继续用块存储方案,可以给每个块加上起始偏移量字段,需要读取对应位置的内容时,直接根据偏移量查询命中的单个块即可,不需要拉取所有块,能大幅降低数据库开销。
内容的提问来源于stack exchange,提问作者Ozzie
相关产品推荐
相关产品推荐

