Fundamentals 8 min read

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.

Refining Core Development Skills
Refining Core Development Skills
Refining Core Development Skills
Why Listing Large Directories Is Slow: Inode and Block Consumption in ext4

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% /search

Thus 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    .
PerformanceLinuxInodeFilesystemdisk spaceEXT4
Refining Core Development Skills
Written by

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.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.