The small files problem

HDFS is built for big files. Store the same data as thousands of tiny files and the NameNode's memory, every client call and every job pay for it. This lesson measures the cost on a real cluster with 10,000 files against one, explains where the time and memory go, and fixes it with a Hadoop Archive and better writing habits.

Module 7 · HDFS advanced⏱ 26 minIntermediate281 s vs 2 s, measuredHAR fix on a real clusterRuns in your browser (Python)8 practice problems
1 The story

Ten thousand files, three hundred kilobytes

The canteen billing app was changed to write one CSV file per order into HDFS. It looked tidy: o_1.csv, o_2.csv, … After one busy morning there are 10,000 files with only 339 KB of data between them. Uploading them took almost five minutes, reading them back took 15 seconds, and the cluster admin calls Riya: the NameNode just gained 20,000 new objects to keep in memory. The same data as one file uploads in 2 seconds and costs the NameNode 2 objects.

10,000 small files … 10,000 × ~33 bytes NameNode: +10,000 files, +10,000 blocks upload 281 s · read 15.2 s 1 file, same data big.csv 339 KB NameNode: +1 file, +1 block upload 2.1 s · read 1.9 s Real lab numbers. HDFS cost grows with the NUMBER of objects, not with bytes. myphatshala.in
Arjun Sir

This is the famous small files problem. HDFS was designed for a few million large files. Every file, folder and block is an object in the NameNode's memory, and every file needs its own calls to the NameNode and its own pipeline to DataNodes. A 33-byte file costs the NameNode about as much as a 128 MB one.

Riya

Then the data size doesn't matter at all?

Arjun Sir

For the NameNode, hardly. For jobs, small files also mean one map task per file, so 10,000 tiny tasks that spend more time starting than working. Today we measure all of it, and then fix it: pack the files into a Hadoop Archive, and see why the real fix is to never write them in the first place.

2 Definition

The small files problem

Formal definition (write this in exams)

The small files problem refers to the degradation of HDFS and processing performance when data is stored as a large number of files significantly smaller than the HDFS block size. Because the NameNode keeps every file, directory and block as an object in memory (commonly estimated at roughly 150 bytes each), the number of files rather than the volume of data determines NameNode heap use, RPC load and the time for listings, block reports and restarts. Each small file also requires separate metadata operations and a separate block write and read, and frameworks typically create at least one input split (and task) per file, so job overhead grows with file count. Mitigations include writing larger files at the source, periodic compaction, container formats (SequenceFile, Avro, Parquet), Hadoop Archives (HAR), and input formats that combine files (CombineFileInputFormat).

In simple words

HDFS keeps a catalogue card for every file and block. Millions of tiny files mean millions of cards, endless trips to the catalogue, and thousands of tiny jobs, even if all the files together are small. Store fewer, bigger files.

Words you'll meet in this lesson

Small fileA file much smaller than the block size (128 MB), e.g. KBs.
Namespace objectA file, folder or block held in NameNode memory.
RPCA call to the NameNode (create, addBlock, complete, getBlockLocations…).
CompactionRewriting many small files into a few large ones.
HARHadoop Archive: many files packed into a few, read via har://.
SequenceFileA Hadoop container file of key-value records, e.g. filename → content.
CombineFileInputFormatGroups many small files into one split for a single map task.
Target file sizeTypical goal for compaction: 128 MB to 1 GB per file.

From our lab (Hadoop 3.5.0, same 338,894 bytes)

NameNode objectsFilesTotal 41 → 10,043 and BlocksTotal 31 → 10,031 after the small files; fsck: 10,000 blocks, average 33 B
Upload10,000 files: 281,026 ms (one -put command) · 1 file: 2,133 ms
Read everything10,000 files: 15,168 ms · 1 file: 1,892 ms
Local diskdu -sh of the 10,000 local files: 40 M for 339 KB of data (4 KB blocks)
After HAR + delete4 files in orders.har; NameNode back to FilesTotal 49, BlocksTotal 35

Small files at three levels

Pick your level. New to this? Start at Beginner and climb.

Beginner A library that registers every page

  • Imagine a library that makes a catalogue card for every book. Fine. Now someone donates 10,000 single-page leaflets: 10,000 cards, 10,000 shelf slots.
  • The librarian's card box (NameNode memory) fills up, and finding anything takes longer, even though the leaflets together are thinner than one book.
  • Binding the leaflets into one volume (compaction or an archive) needs one card.

Intermediate Where the cost comes from

  • NameNode memory: each file + block ≈ 2 objects. Our 10,000 files added 20,000 objects; the big file added 2.
  • RPCs: each file needs create, addBlock, complete, plus a DataNode pipeline. That is why the upload took 281 s instead of 2 s, even in one JVM.
  • Reading: one getBlockLocations and one DataNode connection per file: 15.2 s vs 1.9 s.
  • Jobs: MapReduce and Spark create about one split per file. 10,000 files → about 10,000 tasks, each lasting milliseconds after seconds of start-up.

Advanced How teams fix it

  • At the source: buffer and write larger files (e.g. a file per hour per partition, not per event); streaming sinks with sensible flush intervals.
  • Compaction jobs: Spark coalesce/repartition, Hive merge settings, or table-format commands (Delta OPTIMIZE, Iceberg rewrite_data_files) on a schedule.
  • Containers: SequenceFile or Avro with filename as key; Parquet tables instead of per-record files.
  • HAR: for archives that must keep file names but are rarely read. HAR files are immutable and add an index lookup on read.
  • Guardrails: name quotas (Lesson 7.4) on landing folders, monitoring of average file size per folder, and federation for unavoidable churn (Lesson 7.2).

Exam tip: For “What is the small files problem in Hadoop and how do you solve it?”, give the two costs (NameNode memory, roughly 150 bytes per object; one map task per file), one number (e.g. 10,000 files vs 1 file), and three solutions: HAR, SequenceFile/combining, CombineFileInputFormat, plus “write bigger files at source”.

3 Types and ways to use it

Solutions compared

SolutionReduces NameNode objectsKeeps file namesReadable by jobsNotes
Write bigger files at sourceYes, bestn/aYesThe real fix; needs app change
Compaction (Spark, Hive, table formats)YesNo (records merged)YesSchedule regularly
HAR (hadoop archive)YesYes (har://)Yes, via har://Immutable; extra index lookups
SequenceFile / Avro containerYesAs keysYesNeeds a pack job and readers that understand it
CombineFileInputFormatNoYesFewer tasksFixes job overhead only

Where small files come from

Per-event writers

Apps writing one file per order, click or sensor reading.

design

Streaming micro-batches

A file every few seconds per partition (Lesson 2.2 of Big Data Fundamentals).

streaming

Over-partitioning

Partitioning by date AND hour AND city AND item gives tiny folders with tiny files.

tables

Too many reducers

A job with 2,000 reducers writing 2,000 files of 50 KB each.

jobs

A quick health metric: average file size = CONTENT_SIZE / FILE_COUNT from hdfs dfs -count. Our /small folder averaged 34 bytes. Anything far below the block size in a big folder deserves a compaction plan.

4 Step by step

Measuring and fixing small files

Run on the Module 5 lab cluster with small_files_lab.sh (in “Run it on your PC”).

  1. Create the same data two ways

    seq 1 10000 | while read i; do printf 'order_id,item,amount\n%d,chai,20\n' $i > o_$i.csv; done
    cat /tmp/small/*.csv > /tmp/big.csv
    10000 40M /tmp/small 338894 bytes in big.csv
    Even the local disk wastes space: 339 KB of data occupy 40 MB because each tiny file takes a 4 KB block (Big Data Fundamentals, Lesson 2.3).
  2. Upload both and watch the NameNode

    hdfs dfs -put /tmp/small/*.csv /small/
    hdfs dfs -put /tmp/big.csv /big/
    curl -s 'http://localhost:9870/jmx?qry=Hadoop:service=NameNode,name=FSNamesystem'   # FilesTotal, BlocksTotal
    NameNode before : FilesTotal 41 BlocksTotal 31 put 10000 files: 281026 ms NameNode after small files: FilesTotal 10043 BlocksTotal 10031 put 1 file: 2133 ms
    4 minutes 41 seconds vs 2 seconds for the same bytes. The NameNode gained 10,002 files (10,000 + two folders) and 10,000 blocks. (Its JMX counters refresh with a delay, so they didn't show the single big file's +1 within our 6 s wait; fsck below confirms it.)
  3. Same bytes, very different object counts

    hdfs dfs -count -v /small /big
    hdfs dfs -du -s /small /big
    hdfs fsck /small ; hdfs fsck /big
    DIR_COUNT FILE_COUNT CONTENT_SIZE PATHNAME 1 10000 338894 /small 1 1 338894 /big 338894 1016682 /small 338894 1016682 /big Total blocks (validated): 10000 (avg. block size 33 B) Total blocks (validated): 1 (avg. block size 338894 B)
    Identical bytes on DataNode disks (HDFS blocks are only as big as their data, so no disk is wasted there). The difference is entirely in the NameNode: 10,000 blocks averaging 33 bytes.
  4. Read everything back

    hdfs dfs -cat '/small/*.csv' | wc -l
    hdfs dfs -cat /big/big.csv | wc -l
    20000 read 10000 files: 15168 ms 20000 read 1 file: 1892 ms
    Same 20,000 lines. The small files needed 10,000 location lookups and 10,000 DataNode connections: 8 times slower, and that's with one client and no job framework.
  5. Fix: pack them into a Hadoop Archive

    hadoop archive -archiveName orders.har -p /small /archives
    hdfs dfs -ls /archives/orders.har
    hdfs dfs -count -v har:///archives/orders.har ; hdfs dfs -cat har:///archives/orders.har/o_42.csv
    archive job took 24 s -rw-r--r-- 3 hadoop supergroup 0 2026-10-21 16:39 /archives/orders.har/_SUCCESS -rw-r--r-- 3 hadoop supergroup 834555 2026-10-21 16:39 /archives/orders.har/_index -rw-r--r-- 3 hadoop supergroup 388 2026-10-21 16:39 /archives/orders.har/_masterindex -rw-r--r-- 3 hadoop supergroup 338894 2026-10-21 16:39 /archives/orders.har/part-0 1 10000 338894 har:///archives/orders.har order_id,item,amount 42,chai,20
    The archive job (a MapReduce job, here in local mode) packed all data into one part-0 file plus an index. Through har:// the 10,000 names still exist and can be read individually.
  6. Delete the originals and check the NameNode

    hdfs dfs -rm -r -skipTrash /small
    removed /small NameNode after HAR, without /small: FilesTotal 49 BlocksTotal 35
    From over 10,000 files and blocks back to 49 and 35: the archive costs the NameNode 4 files. The trade-off: archives are immutable, and each read first consults the index.

A compaction or archive job must finish and be verified before deleting the originals, and readers must be switched to the new location. Delete first and a failed job means lost data.

5 See it happen

What the NameNode does for each tiny file

Six steps comparing one small-file upload with one big-file upload, then the archive fix.

Objects and RPCs 6 steps
What you see hadoop@namenode : ~ $ hdfs dfs -put o_1.csv … o_10000.csvo_1.csv … o_2.csv … o_3.csv … put 10000 files: 281026 msFilesTotal 41 → 10043BlocksTotal 31 → 10031 $ hdfs dfs -put big.csv /big/put 1 file: 2133 ms1 file, 1 block $ hdfs dfs -cat '/small/*.csv'15168 ms$ hdfs dfs -cat /big/big.csv1892 ms $ hadoop archive -archiveName orders.har …_index _masterindex part-0 _SUCCESShar://…/o_42.csv → 42,chai,20 $ hdfs dfs -rm -r /smallFilesTotal 49 BlocksTotal 35 myphatshala.in
Behind the screen client NameNode DNs heap: objects create o_1.csvaddBlockwrite 33 B → pipelinecompleteper file: 3 NameNode calls + a 3-DataNode pipeline × 10,000: 30,000 NameNode calls,20,000 objects (orange files, blue blocks) one file: 3 calls, 2 objects, one stream of bytes reading: a location lookup + a DataNode connection per file _index_masterindexpart-0 (all data)HAR: the names live inside _index, not in the NameNode after deleting /small: only the archive's few objects remain myphatshala.in
6 Real-world use

Small files at work

nightly compaction→ 256 MB files

1. Nightly compaction of streaming data

Streaming jobs write small files all day for freshness; a nightly Spark or table-format job rewrites yesterday's partitions into files of a few hundred MB.

Stream: many small files→Compaction job→Few large files
avg file size34 B ⚠

2. Monitoring average file size

Platform teams scan the namespace (from fsimage with the Offline Image Viewer, Lesson 5.4) to rank folders by file count and average size, and ask the owners of the worst offenders to fix their writers.

fsimage → oiv→Files per folder→Owners fix writers
.har

3. Archiving old logs and documents

Years of small log or document files that must stay individually retrievable are packed into HAR archives (or containers) so they stop costing NameNode memory.

Old small files→hadoop archive→har:// access

4. IoT and per-event uploads redesigned

Systems that once wrote one file per sensor reading switch to a message queue (Kafka) and write hourly files per device group, cutting file counts by orders of magnitude.

Per-event files→Kafka + batching→Hourly files

Simplified; organisations differ in tools and policies.

7 Run the code

Count the NameNode's work for small and big files

This model counts NameNode objects (files + blocks), NameNode calls and map tasks for the same data stored as many small files or a few big ones. The cost per object comes from the common rule of thumb of about 150 bytes of heap.

import math
BLOCK = 128 * 1024 * 1024
HEAP_PER_OBJECT = 150                       # bytes, rule of thumb

def cost(total_bytes, n_files):
    size = total_bytes / n_files
    blocks_per_file = max(1, math.ceil(size / BLOCK))
    objects = n_files * (1 + blocks_per_file)
    calls = n_files * (2 + blocks_per_file)   # create + complete + addBlock per block
    tasks = n_files * blocks_per_file         # about one split per block (one per small file)
    return objects, calls, tasks, objects * HEAP_PER_OBJECT

data = 338_894
for n in [10_000, 1]:
    o, c, t, heap = cost(data, n)
    print(f"{n:>6} files: {o:>6} objects, {c:>6} NameNode calls, {t:>6} map tasks, ~{heap / 1024:.0f} KB heap")
10000 files: 20000 objects, 30000 NameNode calls, 10000 map tasks, ~2930 KB heap 1 files: 2 objects, 3 NameNode calls, 1 map tasks, ~0 KB heap
  • objects = n_files * (1 + blocks_per_file)Each file is at least two objects: the file and its block. Our lab: +10,000 files and +10,000 blocks.
  • calls = n_files * (2 + blocks_per_file) # create + complete + addBlock per blockEach file needs its own create, addBlock and complete calls, the reason 10,000 uploads took 281 s.
  • tasks = n_files * blocks_per_file # about one split per block (one per small file)Frameworks create about one split per file: 10,000 tiny tasks, mostly start-up overhead.

Run it on your PC: 10,000 files vs 1, then archive them

Run this against the Module 5 lab cluster. It creates 10,000 tiny CSV files and one file with the same bytes inside the namenode container, uploads both, reads NameNode counters from /jmx, compares reads, then packs the small files into a Hadoop Archive. It takes about 6 minutes, most of it the small-file upload.

Small files lab

#!/usr/bin/env bash
# small_files_lab.sh: the same 10 MB of orders as 10,000 tiny files vs 1 file, and what the NameNode pays.
NN="docker exec lab5-namenode-1"
jmx() { sleep 6; $NN bash -c "curl -s 'http://localhost:9870/jmx?qry=Hadoop:service=NameNode,name=FSNamesystem'" | python3 -c "import sys,json; b=json.load(sys.stdin)['beans'][0]; print('FilesTotal', b['FilesTotal'], ' BlocksTotal', b['BlocksTotal'])"; }
run() { echo "\$ $1"; $NN bash -c "$1" 2>&1 | grep -v -E "^\s*$|WARN|INFO"; echo; }
run "hdfs dfs -rm -r -f -skipTrash /small /big /archives > /dev/null; rm -rf /tmp/small && mkdir -p /tmp/small && cd /tmp/small && seq 1 10000 | while read i; do printf 'order_id,item,amount\n%d,chai,20\n' \$i > o_\$i.csv; done; ls | wc -l; du -sh /tmp/small; cat /tmp/small/*.csv > /tmp/big.csv; ls -l /tmp/big.csv | awk '{print \$5, \"bytes in big.csv\"}'"
echo "NameNode before : $(jmx)"; echo
run "hdfs dfs -mkdir -p /small /big; s=\$(date +%s%N); hdfs dfs -put /tmp/small/*.csv /small/; echo put 10000 files: \$(( (\$(date +%s%N)-s)/1000000 )) ms"
echo "NameNode after small files: $(jmx)"; echo
run "s=\$(date +%s%N); hdfs dfs -put /tmp/big.csv /big/; echo put 1 file: \$(( (\$(date +%s%N)-s)/1000000 )) ms"
echo "NameNode after big file   : $(jmx)"; echo
run "hdfs dfs -count -v /small /big"
run "hdfs dfs -du -s /small /big"
run "s=\$(date +%s%N); hdfs dfs -cat '/small/*.csv' | wc -l; echo read 10000 files: \$(( (\$(date +%s%N)-s)/1000000 )) ms"
run "s=\$(date +%s%N); hdfs dfs -cat /big/big.csv | wc -l; echo read 1 file: \$(( (\$(date +%s%N)-s)/1000000 )) ms"
run "hdfs fsck /small | grep -E 'Total files|Total blocks|Average block size'; hdfs fsck /big | grep -E 'Total files|Total blocks|Average block size'"
echo "=== FIX 1: pack the small files into a Hadoop Archive (HAR)"
run "s=\$(date +%s); hadoop archive -archiveName orders.har -p /small /archives > /dev/null 2>&1; echo archive job took \$(( \$(date +%s)-s )) s; hdfs dfs -ls /archives/orders.har"
run "hdfs dfs -count -v har:///archives/orders.har; hdfs dfs -cat har:///archives/orders.har/o_42.csv"
run "hdfs dfs -rm -r -skipTrash /small > /dev/null && echo removed /small"
echo "NameNode after HAR, without /small: $(jmx)"

# Run:  bash small_files_lab.sh
$ hdfs dfs -rm -r -f -skipTrash /small /big /archives > /dev/null; rm -rf /tmp/small && mkdir -p /tmp/small && cd /tmp/small && seq 1 10000 | while read i; do printf 'order_id,item,amount\n%d,chai,20\n' $i > o_$i.csv; done; ls | wc -l; du -sh /tmp/small; cat /tmp/small/*.csv > /tmp/big.csv; ls -l /tmp/big.csv | awk '{print $5, "bytes in big.csv"}' 10000 40M /tmp/small 338894 bytes in big.csv NameNode before : FilesTotal 41 BlocksTotal 31 $ hdfs dfs -mkdir -p /small /big; s=$(date +%s%N); hdfs dfs -put /tmp/small/*.csv /small/; echo put 10000 files: $(( ($(date +%s%N)-s)/1000000 )) ms put 10000 files: 281026 ms NameNode after small files: FilesTotal 10043 BlocksTotal 10031 $ s=$(date +%s%N); hdfs dfs -put /tmp/big.csv /big/; echo put 1 file: $(( ($(date +%s%N)-s)/1000000 )) ms put 1 file: 2133 ms NameNode after big file : FilesTotal 10043 BlocksTotal 10031 $ hdfs dfs -count -v /small /big DIR_COUNT FILE_COUNT CONTENT_SIZE PATHNAME 1 10000 338894 /small 1 1 338894 /big $ hdfs dfs -du -s /small /big 338894 1016682 /small 338894 1016682 /big $ s=$(date +%s%N); hdfs dfs -cat '/small/*.csv' | wc -l; echo read 10000 files: $(( ($(date +%s%N)-s)/1000000 )) ms 20000 read 10000 files: 15168 ms $ s=$(date +%s%N); hdfs dfs -cat /big/big.csv | wc -l; echo read 1 file: $(( ($(date +%s%N)-s)/1000000 )) ms 20000 read 1 file: 1892 ms $ hdfs fsck /small | grep -E 'Total files|Total blocks|Average block size'; hdfs fsck /big | grep -E 'Total files|Total blocks|Average block size' Connecting to namenode via http://namenode:9870/fsck?ugi=hadoop&path=%2Fsmall Total files: 10000 Total blocks (validated): 10000 (avg. block size 33 B) Total files: 0 Connecting to namenode via http://namenode:9870/fsck?ugi=hadoop&path=%2Fbig Total files: 1 Total blocks (validated): 1 (avg. block size 338894 B) Total files: 0 === FIX 1: pack the small files into a Hadoop Archive (HAR) $ s=$(date +%s); hadoop archive -archiveName orders.har -p /small /archives > /dev/null 2>&1; echo archive job took $(( $(date +%s)-s )) s; hdfs dfs -ls /archives/orders.har archive job took 24 s Found 4 items -rw-r--r-- 3 hadoop supergroup 0 2026-10-21 16:39 /archives/orders.har/_SUCCESS -rw-r--r-- 3 hadoop supergroup 834555 2026-10-21 16:39 /archives/orders.har/_index -rw-r--r-- 3 hadoop supergroup 388 2026-10-21 16:39 /archives/orders.har/_masterindex -rw-r--r-- 3 hadoop supergroup 338894 2026-10-21 16:39 /archives/orders.har/part-0 $ hdfs dfs -count -v har:///archives/orders.har; hdfs dfs -cat har:///archives/orders.har/o_42.csv DIR_COUNT FILE_COUNT CONTENT_SIZE PATHNAME 1 10000 338894 har:///archives/orders.har order_id,item,amount 42,chai,20 $ hdfs dfs -rm -r -skipTrash /small > /dev/null && echo removed /small removed /small NameNode after HAR, without /small: FilesTotal 49 BlocksTotal 35

Real output. Timings vary; the ratio stays dramatic. The JMX counters refresh with a delay, which is why the line after the big file still shows the earlier values; fsck shows its 1 file and 1 block.

8 Line by line

Packing small files into one container

A container (like a SequenceFile or a HAR) stores many small files inside one big file and keeps an index of where each one starts. Follow three files being packed and one being read back.

Worked examples: real problems, solved and traced in memory

Three small-files calculations: NameNode heap, job overhead, and the payoff of compaction.

Example 1BeginnerSizing · heap

How much NameNode memory do small files cost?

Problem statement

A team writes 20 lakh files a day, each 30 KB (one block each), and keeps them for 180 days. Using about 150 bytes per object, how much NameNode heap do they occupy?

Input
20,00,000/day · 180 days · 2 objects/file
Expected output
360,000,000 files -> 101 GB of NameNode heap

Approach

  1. Files kept = per day × days.
  2. Objects = files × 2 (file + block).
  3. Heap = objects × 150 B.

Solution: run it live

What happens in memory, step by step

Recorded from a real Python run: press Next to execute one line at a time. Watch variables change in each frame, and lists and dictionaries (numbered #1, #2 …) fill up as objects.

Example 2IntermediateJobs · overhead

How long do 10,000 tiny tasks take?

Problem statement

A job reads 10,000 small files, one map task each. Each task spends 1.5 s starting and 0.05 s working. With 200 task slots, how long does the map phase take, compared with 2 tasks reading 2 big files (1.5 s + 40 s work each)?

Input
10,000 × (1.5 + 0.05) s / 200 slots vs 2 × 41.5 s
Expected output
small files: 77.5 s (4.2 task-hours of pure start-up)
big files  : 41.5 s

Approach

  1. Waves = tasks / slots.
  2. Time = waves × task time.
  3. Compare.

Solution: run it live

What happens in memory, step by step

Recorded from a real Python run: press Next to execute one line at a time. Watch variables change in each frame, and lists and dictionaries (numbered #1, #2 …) fill up as objects.

Example 3AdvancedOperations · payoff

Is a nightly compaction job worth it?

Problem statement

A streaming job creates 2,880 files per partition per day across 50 partitions. Compaction rewrites each day into 2 files per partition, costing 20 minutes of a 40-node cluster (₹600 per node-hour). Each NameNode object costs ₹0.0002 per day in extra heap and GC (illustrative). Compare the yearly cost with and without compaction.

Input
2,880 × 50 files/day · compaction 20 min × 40 nodes
Expected output
heap/GC cost avoided: Rs 3,844,720  compaction cost: Rs 2,920,000

Approach

  1. Objects avoided per day.
  2. Objects accumulate: day d keeps d days of files.
  3. Compare with compaction cost.

Solution: run it live

What happens in memory, step by step

Recorded from a real Python run: press Next to execute one line at a time. Watch variables change in each frame, and lists and dictionaries (numbered #1, #2 …) fill up as objects.

9 Quiz and revision

Check yourself

Quick revision

Lesson 7.5 in one screen

  • Small files cost the NameNode (≈150 B per file/folder/block, rule of thumb), RPCs and tasks, not DataNode disk.
  • Lab (338,894 bytes): 10,000 files upload 281 s vs 2.1 s; read 15.2 s vs 1.9 s; +10,000 files and blocks; fsck avg block 33 B.
  • DataNode raw bytes were identical (1,016,682): blocks are only as big as their data.
  • Fixes: write bigger files at source; scheduled compaction (Spark, Hive, Delta OPTIMIZE, Iceberg rewrite); containers (SequenceFile, Avro, Parquet).
  • HAR: hadoop archive -archiveName x.har -p /src /dest; read via har://; immutable.
  • CombineFileInputFormat reduces tasks, not NameNode objects.
  • Watch average file size (-count), use name quotas on landing zones.

Interview and viva questions

What is the small files problem in Hadoop?

When data is stored as many files much smaller than the block size, the NameNode must keep an object in memory for each file and block, handle separate RPCs for each, and jobs create a task per file. Memory, NameNode load and job overhead grow with the number of files, not the amount of data, which limits scalability and performance.

How do you solve the small files problem?

Prevent it by writing larger files (batching, sensible streaming intervals, fewer reducers). Clean up existing data with compaction jobs that rewrite small files into large ones, Hadoop Archives (HAR) for rarely accessed data, or container formats like SequenceFile/Avro/Parquet. Use CombineFileInputFormat to reduce task counts when reading.

What is a Hadoop Archive (HAR)?

An archive created by the hadoop archive command (a MapReduce job) that packs many files into a few large part files plus index files. The original paths remain readable through the har:// file system, while the NameNode stores only the archive's few files. HAR files are immutable.

Do small files waste DataNode disk space in HDFS?

Not in the way local file systems do: an HDFS block replica only occupies the bytes it contains. In our lab, 10,000 small files and one big file used the same raw bytes. The waste is in NameNode memory, RPC load and job overhead.

What file size should data in HDFS have?

As a guideline, hundreds of MB to a few GB per file: at least around the block size (128 MB) and big enough that jobs have reasonably sized splits, while not so big that a single file limits parallelism (for non-splittable formats).

Practice problems

Small-files exercises using the real counts and timings from the lab.

P1Easy

Objects added

FilesTotal went 41 → 10,043 and BlocksTotal 31 → 10,031. How many objects did the NameNode gain?

Input
two counters
Expected output
20002 objects
Show solution and explanation
print((10043 - 41) + (10031 - 31), "objects")
20002 objects

How it works: 20,002: 10,000 files, 2 folders and 10,000 blocks.

P2Easy

Slowdown factors

Compute how many times slower upload and read were for the small files.

Input
281,026 vs 2,133 ms · 15,168 vs 1,892 ms
Expected output
upload 132x slower, read 8.0x slower
Show solution and explanation
print(f"upload {281026 / 2133:.0f}x slower, read {15168 / 1892:.1f}x slower")
upload 132x slower, read 8.0x slower

How it works: About 132× slower to upload and 8× slower to read, for identical bytes.

P3Easy

Average file size

What is the average size of the 10,000 files?

Input
338,894 bytes / 10,000
Expected output
33.8894 bytes
Show solution and explanation
print(338894 / 10000, "bytes")
33.8894 bytes

How it works: About 34 bytes; fsck reports 33 B (rounded down).

P4Medium

Heap after a year

An app creates 5,000 small files per hour, each one block, kept forever. How many objects after one year, and roughly how much heap at 150 B each?

Input
5,000/hour × 8,760 hours
Expected output
87,600,000 objects -> 12.2 GB
Show solution and explanation
files = 5000 * 8760
print(f"{files * 2:,} objects -> {files * 2 * 150 / 1024**3:.1f} GB")
87,600,000 objects -> 12.2 GB

How it works: 8.76 crore objects, about 12 GB of heap from a single application.

P5Medium

Parse the HAR index idea

Given an index of name → (offset, length) and the big file, read o_3.csv.

Input
index + bytes
Expected output
b'3,dosa,40\n'
Show solution and explanation
part0 = b"1,chai,20\n2,poha,30\n3,dosa,40\n"
index = {"o_1.csv": (0, 10), "o_2.csv": (10, 10), "o_3.csv": (20, 10)}
o, l = index["o_3.csv"]
print(part0[o:o + l])
b'3,dosa,40\n'

How it works: b"3,dosa,40\n": one index lookup and one read from part-0, like har:// does.

P6Medium

Tasks with combining

10,000 small files of 34 bytes; CombineFileInputFormat packs files into splits of up to 128 MB. How many tasks?

Input
10,000 × 34 B
Expected output
1 task(s)
Show solution and explanation
import math
print(max(1, math.ceil(10000 * 34 / (128 * 1024**2))), "task(s)")
1 task(s)

How it works: 1 task instead of 10,000, but the NameNode still holds 20,000 objects.

P7Business problem

Fix the writer or buy RAM?

Option A: a bigger NameNode server with 512 GB RAM for ₹35 lakh. Option B: 3 developer-weeks (₹1.5 lakh each) to batch writes, which removes 90% of new small files. Which is cheaper, and which keeps working next year?

Input
₹35 lakh vs 3 × ₹1.5 lakh
Expected output
A: Rs 35 lakh, B: Rs 4.5 lakh
Show solution and explanation
a, b = 35, 3 * 1.5
print(f"A: Rs {a} lakh, B: Rs {b} lakh")
A: Rs 35 lakh, B: Rs 4.5 lakh

How it works: B costs ₹4.5 lakh vs ₹35 lakh (illustrative) and stops the growth; A only postpones the problem.

P8Advanced

Compaction planner

Given partition sizes (MB) and file counts, plan how many output files each partition needs at a 256 MB target, and how many objects are saved.

Input
3 partitions
Expected output
dt=2026-10-19: 2880 -> 22 files
dt=2026-10-20: 2880 -> 24 files
dt=2026-10-21: 1500 -> 2 files
objects saved: 14424
Show solution and explanation
import math
parts = {"dt=2026-10-19": (5400, 2880), "dt=2026-10-20": (6100, 2880), "dt=2026-10-21": (300, 1500)}
saved = 0
for p, (mb, files) in parts.items():
    out = max(1, math.ceil(mb / 256))
    saved += (files - out) * 2
    print(f"{p}: {files} -> {out} files")
print("objects saved:", saved)
dt=2026-10-19: 2880 -> 22 files dt=2026-10-20: 2880 -> 24 files dt=2026-10-21: 1500 -> 2 files objects saved: 14424

How it works: 22 + 24 + 2 output files; about 14,424 objects saved. Real compaction jobs also respect sort order and table formats.

Real-world scenario questions (asked in company interviews)

The small files problem is one of the most asked Hadoop interview topics, from freshers to architects.

HDFS · basicsFresher

“Why is HDFS bad at small files?”

Almost guaranteed in a Hadoop interview.

The interviewer is checking: NameNode memory and task overhead.

Show a strong answer
  • Every file and block is an object in NameNode RAM (~150 bytes each); millions of tiny files exhaust the heap.
  • Each file means separate NameNode calls and DataNode connections, and one map task per file.
  • Fixes: bigger files at source, compaction, HAR or SequenceFiles, CombineFileInputFormat.
Streaming · designExperienced

A Spark Streaming job writes 50,000 files an hour

Analysts' queries on the output table get slower each week; the NameNode heap keeps growing.

The interviewer is checking: write-side and cleanup-side fixes.

Show a strong answer
  • Reduce output partitions per micro-batch (coalesce) and increase the trigger interval if freshness allows.
  • Use a table format (Delta/Iceberg/Hudi) and schedule compaction (OPTIMIZE / rewrite) for closed partitions.
  • Monitor average file size per partition and alert when it drops.
Operations · cleanupExperienced

Find the folders causing the most NameNode load

The cluster has 40 crore objects; you must find who owns them without hammering the NameNode.

The interviewer is checking: offline analysis.

Show a strong answer
  • Fetch the latest fsimage (hdfs dfsadmin -fetchImage) and convert it with the Offline Image Viewer to a delimited file.
  • Aggregate by folder: file count, total size, average size; rank by file count with small average size.
  • Work with owners: compaction or HAR for old data, writer fixes for new data, name quotas on landing folders.
Concepts · trade-offsFresher

“HAR or SequenceFile?”

A follow-up after you list solutions.

The interviewer is checking: when each fits.

Show a strong answer
  • HAR keeps the original paths readable through har:// with no code change for readers; good for archives. It is immutable and reading has index overhead.
  • SequenceFile packs files as key-value records (name → bytes); efficient and splittable for MapReduce, but readers must use the container.
  • For tabular data, compaction into Parquet is usually better than both.