Ext4 Partition Size Discrepancy – Understanding Block Size and Available Space

ext4

I have a very high density virtualized environment with containers, so I'm trying to make each container really small. "Really small" means 87 MB on base Ubuntu 14.04 (Trusty Tahr) without breaking up the package manager compatibility.

So I use LVM as a backing storage for my containers and recently I found very strange numbers. Here they are.

Let's create a 100 MiB (yeah, power of 2) logical volume.

sudo lvcreate -L100M -n test1 /dev/purgatory

I'd like to check the size, so I issue sudo lvs --units k

test1             purgatory  -wi-a----  102400.00k

Sweet, this is really 100 MiB.

Now let's make an ext4 filesystem. And of course, we remember -m 0 parameter, which prevents space waste.

sudo mkfs.ext4 -m 0 /dev/purgatory/test1

mke2fs 1.42.9 (4-Feb-2014)
Filesystem label=
OS type: Linux
Block size=1024 (log=0)
Fragment size=1024 (log=0)
Stride=0 blocks, Stripe width=0 blocks
25688 inodes, 102400 blocks
0 blocks (0.00%) reserved for the super user
First data block=1
Maximum filesystem blocks=67371008
13 block groups
8192 blocks per group, 8192 fragments per group
1976 inodes per group
Superblock backups stored on blocks:
        8193, 24577, 40961, 57345, 73729

Allocating group tables: done
Writing inode tables: done
Creating journal (4096 blocks): done
Writing superblocks and filesystem accounting information: done

Sweet and clean. Mind the block size – our logical volume is small, so mkfs.ext4 decided to make a 1 KiB sized block, not the usual 4 KiB.

Now we will mount it.

sudo mount /dev/purgatory/test1 /mnt/test1

And let's call df without parameters (we would like to see 1 KiB-blocks)

/dev/mapper/purgatory-test1     95054    1550     91456   2% /mnt/test1

Wait, oh shi~

We have 95054 blocks total. But the device itself has 102400 blocks of 1 KiB. We have only 92.8% of our storage. Where are my blocks, man?

Let's look at it on a real block device. A have a 16 GiB virtual disk, 16777216 blocks of 1K, but only 15396784 blocks are in df output. 91.7%, what is it?

Now follows the investigation (spoiler: no results)

  1. Filesystem could begin not at the beginning of the device. This is strange, but possible. Luckily, ext4 has magic bytes, let's check their presence.

    sudo hexdump -C /dev/purgatory/test1 | grep "53 ef"

This shows superblock:

00000430  a9 10 e7 54 01 00 ff ff  53 ef 01 00 01 00 00 00  |...T....S.......|

Hex 430 = Dec 1072, so somewhere after first kilobyte. Looks reasonable, ext4 skips first 1024 bytes for oddities like VBR, etc.

  1. This is journal!

No, it is not. Journal take space from Available if df output.

  1. Oh, we have dump2fs and could check the sizes there!

… a lot of greps …

sudo dumpe2fs /dev/purgatory/test1 | grep "Free blocks"

Ouch.

Free blocks:              93504
  Free blocks: 3510-8192
  Free blocks: 8451-16384
  Free blocks: 16385-24576
  Free blocks: 24835-32768
  Free blocks: 32769-40960
  Free blocks: 41219-49152
  Free blocks: 53249-57344
  Free blocks: 57603-65536
  Free blocks: 65537-73728
  Free blocks: 73987-81920
  Free blocks: 81921-90112
  Free blocks: 90113-98304
  Free blocks: 98305-102399

And we have another number. 93504 free blocks.

The question is: what is going on?

  • Block device: 102400k (lvs says)
  • Filesystem size: 95054k (df says)
  • Free blocks: 93504k (dumpe2fs says)
  • Available size: 91456k (df says)

Best Answer

Try this: mkfs.ext4 -N 104 -m0 -O ^has_journal,^resize_inode /dev/purgatory/test1

I thinks this does let you understand "what is going on".

-N 104 (set the number of iNodes you filesystem should have)

  • every iNode "costs" usable space (128 Byte)

-m 0 (no reserved blocks)
-O ^has_journal,^resize_inode (deactivate the features has_journal and resize_inode

  • resize_inode "costs" free space (most of the 1550 1K-Blocks/2% you see in your df - 12K are used for the "lost+found" folder)
  • has_journal "costs" usable space (4096 1K-Blocks in your case)

We get 102348 out of 102400, another 52 blocks unusable (if we have deleted the "lost+found" folder). Therefore we dive into dumpe2fs:

Group 0: (Blocks 1-8192) [ITABLE_ZEROED]
  Checksum 0x5ee2, unused inodes 65533
  Primary superblock at 1, Group descriptors at 2-2
  Block bitmap at 3 (+2), Inode bitmap at 19 (+18)
  Inode table at 35-35 (+34)
  8150 free blocks, 0 free inodes, 1 directories, 65533 unused inodes
  Free blocks: 17-18, 32-34, 48-8192
  Free inodes: 
Group 1: (Blocks 8193-16384) [BLOCK_UNINIT, ITABLE_ZEROED]
  Checksum 0x56cf, unused inodes 5
  Backup superblock at 8193, Group descriptors at 8194-8194
  Block bitmap at 4 (+4294959107), Inode bitmap at 20 (+4294959123)
  Inode table at 36-36 (+4294959139)
  8190 free blocks, 6 free inodes, 0 directories, 5 unused inodes
  Free blocks: 8193-16384
  Free inodes: 11-16
Group 2: (Blocks 16385-24576) [INODE_UNINIT, BLOCK_UNINIT, ITABLE_ZEROED]
  Checksum 0x51eb, unused inodes 8
  Block bitmap at 5 (+4294950916), Inode bitmap at 21 (+4294950932)
  Inode table at 37-37 (+4294950948)
  8192 free blocks, 8 free inodes, 0 directories, 8 unused inodes
  Free blocks: 16385-24576
  Free inodes: 17-24
Group 3: (Blocks 24577-32768) [INODE_UNINIT, BLOCK_UNINIT, ITABLE_ZEROED]
  Checksum 0x3de1, unused inodes 8
  Backup superblock at 24577, Group descriptors at 24578-24578
  Block bitmap at 6 (+4294942725), Inode bitmap at 22 (+4294942741)
  Inode table at 38-38 (+4294942757)
  8190 free blocks, 8 free inodes, 0 directories, 8 unused inodes
  Free blocks: 24577-32768
  Free inodes: 25-32
Group 4: (Blocks 32769-40960) [INODE_UNINIT, BLOCK_UNINIT, ITABLE_ZEROED]
  Checksum 0x79b9, unused inodes 8
  Block bitmap at 7 (+4294934534), Inode bitmap at 23 (+4294934550)
  Inode table at 39-39 (+4294934566)
  8192 free blocks, 8 free inodes, 0 directories, 8 unused inodes
  Free blocks: 32769-40960
  Free inodes: 33-40
Group 5: (Blocks 40961-49152) [INODE_UNINIT, BLOCK_UNINIT, ITABLE_ZEROED]
  Checksum 0x0059, unused inodes 8
  Backup superblock at 40961, Group descriptors at 40962-40962
  Block bitmap at 8 (+4294926343), Inode bitmap at 24 (+4294926359)
  Inode table at 40-40 (+4294926375)
  8190 free blocks, 8 free inodes, 0 directories, 8 unused inodes
  Free blocks: 40961-49152
  Free inodes: 41-48
Group 6: (Blocks 49153-57344) [INODE_UNINIT, BLOCK_UNINIT, ITABLE_ZEROED]
  Checksum 0x3000, unused inodes 8
  Block bitmap at 9 (+4294918152), Inode bitmap at 25 (+4294918168)
  Inode table at 41-41 (+4294918184)
  8192 free blocks, 8 free inodes, 0 directories, 8 unused inodes
  Free blocks: 49153-57344
  Free inodes: 49-56
Group 7: (Blocks 57345-65536) [INODE_UNINIT, BLOCK_UNINIT, ITABLE_ZEROED]
  Checksum 0x5c0a, unused inodes 8
  Backup superblock at 57345, Group descriptors at 57346-57346
  Block bitmap at 10 (+4294909961), Inode bitmap at 26 (+4294909977)
  Inode table at 42-42 (+4294909993)
  8190 free blocks, 8 free inodes, 0 directories, 8 unused inodes
  Free blocks: 57345-65536
  Free inodes: 57-64
Group 8: (Blocks 65537-73728) [INODE_UNINIT, BLOCK_UNINIT, ITABLE_ZEROED]
  Checksum 0xf050, unused inodes 8
  Block bitmap at 11 (+4294901770), Inode bitmap at 27 (+4294901786)
  Inode table at 43-43 (+4294901802)
  8192 free blocks, 8 free inodes, 0 directories, 8 unused inodes
  Free blocks: 65537-73728
  Free inodes: 65-72
Group 9: (Blocks 73729-81920) [INODE_UNINIT, BLOCK_UNINIT, ITABLE_ZEROED]
  Checksum 0x50fd, unused inodes 8
  Backup superblock at 73729, Group descriptors at 73730-73730
  Block bitmap at 12 (+4294893579), Inode bitmap at 28 (+4294893595)
  Inode table at 44-44 (+4294893611)
  8190 free blocks, 8 free inodes, 0 directories, 8 unused inodes
  Free blocks: 73729-81920
  Free inodes: 73-80
Group 10: (Blocks 81921-90112) [INODE_UNINIT, BLOCK_UNINIT, ITABLE_ZEROED]
  Checksum 0x60a4, unused inodes 8
  Block bitmap at 13 (+4294885388), Inode bitmap at 29 (+4294885404)
  Inode table at 45-45 (+4294885420)
  8192 free blocks, 8 free inodes, 0 directories, 8 unused inodes
  Free blocks: 81921-90112
  Free inodes: 81-88
Group 11: (Blocks 90113-98304) [INODE_UNINIT, BLOCK_UNINIT, ITABLE_ZEROED]
  Checksum 0x28de, unused inodes 8
  Block bitmap at 14 (+4294877197), Inode bitmap at 30 (+4294877213)
  Inode table at 46-46 (+4294877229)
  8192 free blocks, 8 free inodes, 0 directories, 8 unused inodes
  Free blocks: 90113-98304
  Free inodes: 89-96
Group 12: (Blocks 98305-102399) [INODE_UNINIT, ITABLE_ZEROED]
  Checksum 0x9223, unused inodes 8
  Block bitmap at 15 (+4294869006), Inode bitmap at 31 (+4294869022)
  Inode table at 47-47 (+4294869038)
  4095 free blocks, 8 free inodes, 0 directories, 8 unused inodes
  Free blocks: 98305-102399
  Free inodes: 97-104

and count the used blocks (for Backup superblock, Group descriptors, Block bitmap, Inode bitmap and Inode table) or we grep and count:

LANG=C dumpe2fs /dev/mapper/vg_vms-test1 | grep ' at ' | grep -v ',' | wc -l

which gives us the count of lines which have a single block (in our example) and

LANG=C dumpe2fs /dev/mapper/vg_vms-test1 | grep ' at ' | grep ',' | wc -l

which gives us the count of lines which have two blocks (in our example).

So we have (in our example) 13 lines with one block each and 19 lines with two blocks each.

13+19*2

which gives us 51 blocks which are in use by ext4 itself. Finally there is only one block left. The block 0, which are the skipped 1024 Bytes at the beginning for things like the boot sector.

Related Question