Why Listing Large Directories Is Slow: Inode and Block Consumption in ext4
The article explains how directories consume inodes and filesystem blocks, how this consumption grows with many files or long filenames, and why the ls command can become sluggish, offering practical tips to mitigate the performance impact.
Have you ever noticed that running ls in a directory with a huge number of files takes a long time to display? The delay is caused by the way the filesystem stores directory entries.
1. Inode consumption verification
Each file consumes an inode, and a directory itself also consumes an inode. Using df -i we can see the inode usage before and after creating an empty directory; the IUsed count increases by one, confirming that an empty directory consumes one inode (about 256 bytes on the author's machine).
# df -i
Filesystem Inodes IUsed IFree IUse% Mounted on
...
/dev/sdb1 2147361984 12785020 2134576964 1% /search # mkdir temp
# df -i
Filesystem Inodes IUsed IFree IUse% Mounted on
...
/dev/sdb1 2147361984 12785021 2134576963 1% /searchThus an empty directory consumes one inode, but this small amount is not the main cause of the ls slowdown.
2. Block consumption verification
Directory entries are stored in blocks as ext4_dir_entry_2 structures. Creating a directory and checking its size with du -h shows that a newly created empty directory occupies one block (typically 4 KB). Adding empty files does not increase the size until the block is filled.
# mkdir test
# cd test
# du -h
4.0K . # touch aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab
# touch aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
# du -h
4.0K .Creating many small files (e.g., 100 files with 32‑byte names) eventually forces the directory to allocate additional blocks, growing to 12 KB, and with 10 000 files the size reaches 548 KB, roughly 54 bytes per file.
# for i in {1..100}; do
file="tempDir/$(printf "%032d" $i)"
touch $file
done
# du -h
12K . # du -h
548K .The ext4_dir_entry_2 structure is defined as:
struct ext4_dir_entry_2 {
__le32 inode; /* Inode number */
__le16 rec_len; /* Directory entry length */
__u8 name_len; /* Name length */
__u8 file_type;
char name[EXT4_NAME_LEN]; /* File name */
};Key conclusions:
The directory itself consumes one inode (≈256 bytes on the author's system).
It also consumes a directory entry ( ext4_dir_entry_2 ) in its parent block.
Each additional file or subdirectory adds entries to the directory’s block; longer names increase the space per entry.
When many entries are present, the directory allocates more blocks, which can cause ls to stall if the required blocks are not cached.
To avoid the slowdown, distribute files across multiple sub‑directories (e.g., using a hash‑based directory tree) so that no single directory contains an excessive number of entries.
4. An ext4 bug
Even after deleting all files with rm -f * , the directory may still occupy space (e.g., 72 KB) because the rec_len field of each ext4_dir_entry_2 remains, and Linux simply marks the inode as zero without reclaiming the whole entry. This behavior is similar to a “soft delete”.
# rm -f *
# du -h
72K .Refining Core Development Skills
Fei has over 10 years of development experience at Tencent and Sogou. Through this account, he shares his deep insights on performance.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.