What Nobody Tells You About the Performance Trap of Using Git-LFS on Large Projects
Your repository is ballooning, clones take ten minutes, and someone suggests Git-LFS as the cure-all. But moving those 4K textures and machine learning models out of the core history often just replaces disk bloat with a soul-crushing "Waiting for LFS..." progress bar every time you switch branches.
We’re told Git-LFS is the silver bullet for heavy repos. Technically, it is: it replaces large files with tiny text pointers, keeping your .git folder slim. But in practice, the way LFS handles the "smudge" and "clean" filters can turn a high-performance development workflow into a series of agonizing bottlenecks.
The "Smudge" Tax
Every time you run git checkout or git pull, Git looks at the pointers in your working tree and realizes it needs to turn those text snippets into real files. This is the smudge filter.
The problem? Git-LFS is an external process. For every single file tracked by LFS, Git has to fork a new process to ask LFS, "Hey, do you have the real version of this?" Even if you have the file cached locally, the overhead of firing up that process thousands of times is massive. I once worked on a project with 5,000 smallish binary assets (icons and audio clips) tracked via LFS. Switching branches took nearly three minutes, even though the total data size was less than 200MB.
If you find yourself in this boat, you can actually speed things up by telling Git-LFS to handle transfers in bulk rather than one-by-one.
# Increase the number of concurrent downloads
git config --global lfs.concurrenttransfers 64But the real fix for the "many small files" trap isn't just more threads—it's reconsidering if those files belong in LFS at all. If a file is under 500KB, the LFS overhead often outweighs the benefit of keeping it out of the packfiles.
The Network Latency Loop
Git is designed to be distributed. Once you git clone, you have everything. Git-LFS breaks this. It makes Git depend on a central server for every single checkout of a new revision.
If your team is distributed across the globe and your LFS storage is sitting in a single AWS bucket in US-East-1, your developers in Berlin or Tokyo are going to have a bad time. Unlike standard Git objects, which are highly compressed and bundled, LFS files are pulled down as individual HTTP requests.
You can see what's happening under the hood by running:
GIT_TRACE=1 git checkout masterYou'll see a stream of individual POST requests. If you have high latency, those round-trips add up. This is where a Global Cache or a local proxy (like a Nexus or Artifactory instance) becomes mandatory, not optional.
The Stealth Disk Hog
LFS is supposed to save disk space, right? Well, only for the .git directory history. Your working directory still needs the actual files.
What people don't tell you is that LFS keeps a local cache of every version of every file you’ve ever downloaded in .git/lfs/objects. If you're working on a long-lived project, you’ll eventually find that your project folder is 50GB, even though the current "live" files are only 2GB.
I've seen CI runners run out of disk space because they were bloated with months of old LFS objects. You have to aggressively prune:
# Clean up old LFS files that aren't in the current checkout
git lfs pruneIf you want to be even more aggressive and only keep what's strictly necessary for the last few days of work:
git config lfs.prunerecentdays 3
git lfs pruneThe "Include/Exclude" Strategy
When a repo gets truly massive, you shouldn't be downloading all the LFS assets anyway. Most developers don't need every raw .psd or 4K video file just to fix a CSS bug.
Instead of a blanket git lfs install, use fetch patterns. You can tell LFS to ignore certain paths by default and only pull them when you actually need them.
# Don't download heavy assets by default
git config lfs.fetchexclude "assets/videos/*,assets/raw_models/*"
# Later, when you actually need the videos for a specific task:
git lfs fetch --include "assets/videos/*"
git lfs checkoutThis is the only way to keep a 500GB repository usable on a standard laptop. It’s essentially "lazy loading" for your file system.
Don't use LFS for everything
The biggest performance trap is using LFS for files that change constantly but shouldn't be binary—like huge JSON metadata files or generated lockfiles.
Because LFS files can't be diffed effectively by the Git engine, you lose all the deduplication benefits that Git's internal delta compression provides. If you have a 10MB JSON file that changes every commit, Git handles that via deltas quite well. LFS, however, will save a brand new 10MB file for every single change.
Before you git lfs track "*.something", ask yourself: Is this file truly an opaque blob, or is it text in disguise? If it's the latter, stay away from LFS. The convenience of a small .git folder isn't worth the architectural debt of a sluggish, network-dependent workflow.