Кажется, общеизвестно, что вы должны использовать глубокую (также называемую древовидной) структуру каталогов (например, files/00/01/123.data) вместо плоского каталога (например, files/123.data), когда вы хотите хранить миллионы файлов. Возможно, это было верно для старых файловых систем, таких как ext3, но верно ли это для более современных файловых систем, таких как ext4?

Давай проверим это.

Мы будем использовать Ruby для создания и тестирования обеих стратегий хранения. Сначала нам нужно найти способ создавать поддельные файлы. Мы хотим создать 10 миллионов из них. Для этого мы просто воспользуемся хешем со случайными ключами md5 и случайными значениями md5. Таким образом, мы уверены, что читаем то, что не может быть кэшировано системой:

hash = {}
10_000_000.times do
  key = Digest::MD5.hexdigest(rand.to_s)
  value = Digest::MD5.hexdigest(rand.to_s)
  hash[key] = value
end

Затем нам понадобится код для записи, а не для чтения этих файлов с использованием стратегии хранения плоских каталогов:

puts Benchmark.measure {
  hash.each do |key,value|
    File.write "./dir_flat/#{key}", value
  end
}
puts Benchmark.measure {
  hash.each do |key,value|
    File.read "./dir_flat/#{key}"
  end
}

И некоторый код для записи, чем для чтения этих файлов, с использованием стратегии хранения в глубоких каталогах. Мы выбрали два уровня каталогов с двумя шестнадцатеричными буквами. В среднем он должен содержать 152-153 файла на листовой каталог. (10 000 000 / (256 * 256)):

puts Benchmark.measure {
  hash.each do |key,value|
    dir_path = "./dir_deep/#{key[0..1]}/#{key[2..3]}/"
    FileUtils.mkdir_p dir_path
    File.write dir_path + key, value
  end
}
puts Benchmark.measure {
  hash.each do |key,value|
    dir_path = "./dir_deep/#{key[0..1]}/#{key[2..3]}/"
    File.read dir_path + key 
  end
}

Отметим, что на производительность записи, вероятно, влияет динамическое создание каталогов. Давайте предварительно отрендерим структуру каталогов:

hash.keys.each do |key|
  dir_path = "./dir_deep/#{key[0..1]}/#{key[2..3]}/"
  FileUtils.mkdir_p dir_path
end
puts Benchmark.measure {
  hash.each do |key,value|
    dir_path = "./dir_deep/#{key[0..1]}/#{key[2..3]}/"
    File.write dir_path + key, value
  end
}
puts Benchmark.measure {
  hash.each do |key,value|
    dir_path = "./dir_deep/#{key[0..1]}/#{key[2..3]}/"
    File.read dir_path + key 
  end
}

Вот окончательные результаты тестов:

Запись выполняется на 44% быстрее при использовании плоской структуры каталогов вместо глубокой / древовидной структуры каталогов. Чтение происходит даже в 7,8 раза быстрее.

В заключение, просто используйте плоскую структуру каталогов. Так проще в использовании. Быстрее писать. Гораздо быстрее читается. Экономьте на ионодах. И не нужно предварительно создавать или динамически создавать папки веток.

Ссылки: источник - сырые результаты

[Edit] Итак, после публикации этой статьи я обнаружил, что ограничения ext4 составляют около 10 118 651 (или ~ 10 233 706) файлов на каталог для длинного имени файла md5.

Я пытался запустить вышеуказанный тест с 20 миллионами файлов. Но я получал Errno::ENOSPC: No space left on device @ rb_sysopen ошибку в Ruby. Это было странно, потому что и дисковое пространство, и inode были в порядке.

В журнале thedmesg у меня действительно были ошибки полного индекса каталога inode:

ext4_dx_add_entry:2235: inode #258713: comm pry: Directory index full
[1718.956797] EXT4-fs warning (device vda1): ext4_dx_add_entry:2184: Directory (ino: 384830) index full, reach max htree level :2
[1718.956798] EXT4-fs warning (device vda1): ext4_dx_add_entry:2188: Large directory feature is not enabled on this filesystem
[10788.316073] EXT4-fs warning (device vda1): ext4_dx_add_entry:2184: Directory (ino: 384830) index full, reach max htree level :2
[10788.316075] EXT4-fs warning (device vda1): ext4_dx_add_entry:2188: Large directory feature is not enabled on this filesystem

Индексы каталогов в ext4 связаны с размером имени файла и количеством файлов. Таким образом, это ограничение может отличаться в вашей системе.

Следуя советам некоторых комментаторов, повторное выполнение теста 10M с реальными файлами JSON дает аналогичные результаты:

Чтение по-прежнему в 2 раза быстрее, а запись по-прежнему на 20%.

Ссылки: source v2 - raw results v2 - tune2fs -l output

DMke также сделал отличную вилку оригинального кода. Он добавил тесты того, как глубины каталогов работают друг с другом:

Вывод 2: придерживайтесь индивидуальной мудрости и используйте файловую систему с глубокими каталогами. Однако будьте осторожны с потерями производительности из-за слишком большого количества уровней каталогов.