在磁盘上存储大量文件的最佳方式

8

我找不到一个好的问题标题,这就是我想做的:

  • 这是一个.NET应用程序。
  • 我需要存储多达200000个对象(大小在3KB-500KB之间)
  • 我需要从多个线程每秒存储大约10个对象
  • 我在存储之前使用二进制序列化
  • 我需要通过整数唯一ID稍后访问它们

最好的方法是什么?

  • 我不能将它们保存在内存中,因为我会得到outofmemory异常
  • 当我将它们作为单独的文件存储在磁盘上时,可能会出现哪些性能问题?这会显著降低总体性能吗?
  • 是否应该实现某种缓存,例如将100个对象组合起来,一次写入一个文件。然后稍后解析它们。或类似的东西?
  • 是否应该使用数据库?(访问时间不重要,不会进行搜索,我只会通过已知唯一ID进行几次访问)。理论上我不需要数据库,我不想把它复杂化。

更新:

  • 我认为数据库比文件系统慢,如果你有这方面的问题,请证明我错了。所以这就是我也倾向于文件系统的原因。但我真正担心的是将200KB * 10每秒写入HDD(它可以是任何HDD,我无法控制硬件,它是一个将部署在不同系统中的桌面工具)。
  • 如果我使用文件系统,我将将文件存储在单独的文件夹中,以避免与文件系统相关的问题(因此您可以忽略该限制

唯一的ID是由我们提供的,还是需要我们生成它?如果需要我们生成它,它是否需要在系统重启后保留? - Damien_The_Unbeliever
唯一标识已经被处理并提供,不需要在重启后保留。它只在应用程序运行期间需要。 - dr. evil
请注意这不是重复问题,但可以参考以下链接:https://dev59.com/JnE95IYBdhLWcg3wn_Vr - Martin Beckett
你会编辑或删除记录吗? - Albin Sunnanbo
@Albin在用户完成流程后,从不编辑记录,而是删除所有记录。 - dr. evil
有关此问题的任何解决方案,示例源代码吗? - Kiquenet
8个回答

5
如果您想避免使用数据库,可以将它们存储为磁盘文件(以保持简单)。但是,在维护单个目录中的大量文件时,需要注意文件系统考虑因素。
许多常见的文件系统按某种顺序列表(例如,仅存储文件指针或inode,或者使用链接列表)来维护其文件。这使得打开位于列表底部的文件变得非常缓慢。
一个好的解决方案是将目录限制在小数量的节点下(比如n=1000),并在该目录下创建文件树。
因此,不要将文件存储为:
/dir/file1 /dir/file2 /dir/file3 ... /dir/fileN
而应将其存储为:
/dir/r1/s2/file1 /dir/r1/s2/file2 ... /dir/rM/sN/fileP
通过这种方式拆分文件,您可以显着提高大多数文件系统的访问时间。
(请注意,有一些新的文件系统会以树形结构或其他形式的索引表示节点。这种技术在这些文件系统上也同样有效)
其他考虑因素是调整文件系统(块大小、分区等)和缓冲区缓存,以使数据局部性良好。根据您的操作系统和文件系统,有许多方法可以实现这一点 - 您可能需要查找它们。
或者,如果这行不通,可以使用类似SQLlite或Firebird的某种嵌入式数据库。
希望这有所帮助。

我无法控制硬件,因此它可能是任何东西,从糟糕的带有FAT32的HDD(不太可能但有可能)到RAID。操作系统始终为Windows,尽管这是在Windows中的.NET,没有mono相关内容。 - dr. evil
@Dr. Evil:我认为,如果使用“垃圾硬盘”无法存储2MB /秒的情况下,任何包括DMBS在内的解决方案都会失败,因为任何DBMS在存储数据时都会增加自己的开销。 - Igor Korkhov
这个想法太糟糕了;我尝试过类似的方法,即使在像ext4这样的现代文件系统中也是不可行的。几乎任何文件系统都会因为在8k目录中放置超过8k个文件而崩溃。我的设置是将每个文件进行哈希处理,并放在一个目录结构下,比如img/a412/8cdf/e9k4/rest_of_hash.png,结果基本上把我整死了,因为 (a) 我用完了所有的inode,(b) 由于成千上万的目录导致所有文件遍历都失败,以及 (c) 消耗了大量的额外磁盘空间,因为每个目录都是4096k,即使我的图像只有2k大小。对于大规模解决方案来说,像这样使用文件系统存储是一个糟糕的想法。 - Nthalk

2

我建议制作一个类,其中包含一个单线程队列,用于将图像(经过gzip压缩)倒入文件末尾,然后将文件偏移量/元信息保存到小型数据库(如sqlite)中。这样可以让您快速、紧密地存储所有文件,从多个线程中读取它们,并且无需处理任何文件系统的怪异之处(除了最大文件大小——可以通过具有一些额外元数据来处理它)。

File:
file.1.gzipack

Table:
compressed_files {
  id,
  storage_file_id,
  storage_offset,
  storage_compressed_length,
  mime_type,
  original_file_name
}

2

我会倾向于使用数据库,在C++中可以选择sqlite或couchDB。
这两个都可以在.NET中使用,但我不知道是否有更好的.NET专用替代方案。

即使在能够处理200,000个文件的目录的文件系统上,打开目录也需要很长时间。

编辑 - 数据库可能会更快!
文件系统不适用于大量小对象,而数据库则是如此。
它将实现各种聪明的缓存/事务策略,这些策略您从未考虑过。

有一些照片网站选择了文件系统而不是数据库。但它们大多在较大的块上进行读取,并且他们有很多专家来调整他们的服务器以针对这个特定应用程序。


有没有任何数据库的性能优势?如果有的话,我认为它会更慢,那么相对于文件系统(假设我将每个文件夹分组为1000个文件在文件系统中 - 这样可以轻松解决打开目录问题),有什么优势呢? - dr. evil

1
你可以查看mongoDb,它支持存储文件。

MongoDB有性能优势吗?我认为它会更慢,如果是这样的话,与文件系统相比有什么优势(假设我在文件系统中每个文件夹中分组1000个文件)? - dr. evil

0

唯一确定的方法是了解您的使用场景。

例如,文件的后续使用是否需要每次以100个文件为一组?如果是这样,将它们合并可能是有意义的。

无论如何,我建议先尝试制定一个简单的解决方案,只有在以后发现存在性能问题时才进行更改。

以下是我的建议:

  1. 创建一个处理存储和检索的类(这样您以后可以更改此类,而不必更改应用程序中使用它的每个点)
  2. 将文件按原样存储在磁盘上,不要将它们合并
  3. 将它们分散到子目录中,每个目录中保留1000个或更少的文件(如果单个目录中有许多文件,则目录访问会增加开销)

使用场景如问题所述明确,后续使用并不重要。我只需要通过ID访问它0-10次,并且访问时间在15-30秒以内即可,时间长短并不重要。 - dr. evil

0

我其实不使用.NET,所以不确定那里是否容易,但总的来说,我会提供两个建议。

如果你需要大量写入而很少读取(例如日志文件),你应该创建一个.zip文件或类似的文件(选择不会使性能过慢的压缩级别;在1-9评级中,5左右通常适用于我)。这给你带来了几个优势:你不会对文件系统造成太大的负担,你的存储空间被减少了,你可以自然地将文件分组为100或1000个块。

如果你需要大量写入和读取,可以定义自己的平面文件格式(除非你有读写.tar文件或类似文件的工具,或者欺骗并将二进制数据放入8位灰度TIFF中)。为每个头定义记录--可能是每个1024字节,其中包含文件偏移量、文件名和任何其他需要存储的内容--然后按块写入数据。当你需要读取一个块时,首先读取头(可能是100k),然后跳转到你需要的偏移量并读取所需的数量。固定大小头的优点是你可以在开头写入空数据,然后只需将新内容附加到文件末尾,然后返回并覆盖相应的记录。
最后,你可能可以研究一些像HDF5这样的东西;我不知道.NET对它的支持情况,但这是一种存储通用数据的好方法。

0
你可以考虑使用微软的缓存应用程序块。你可以配置它使用隔离存储作为后备存储,这样缓存中的项目将被序列化到磁盘上。性能可能会成为一个问题 - 我认为默认情况下它会在写入时阻塞,所以你可能需要调整它以执行异步写入。

0

在你的情况下,Memcached 可能会解决一些性能问题。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接