Repository navigation
Best practices for scanning large numbers of small binary+text files — Nginx cache use case #3411
|
Background / Use Case I'm the author of a free WordPress plugin Nginx Cache Purge Preload that uses ripgrep as a RG Purge feature — as of v2.1.6 — to locate and purge specific Nginx cache files. I'd love input from maintainers or the community on whether my current ripgrep usage is well-optimized for this workload. Workload characteristics:
KEY: httpGETexample.com/my-post/
What I'm actually scanning inside each cache file ? Nginx cache files have this structure: So every cache file is a mixed binary+text file. I only ever care about one line: KEY: Current core ripgrep commands used in plugin1. Single-page purge — find cache file(s) for one or more specific URLs in one pass: This uses a combined alternation pattern so the directory is walked exactly once regardless of how many URLs need purging. 2. Full cache inventory (two-pass approach): Pass 1 — Collect redirect files to exclude them: Pass 2 — extract KEY: line from every non-redirect cache file: 3. Post-preload cache confirmation — verify a single URL was cached: QuestionsQ1: --no-mmap vs --mmap for small files Q2: Is there a way to limit how many bytes ripgrep reads per file? Q3: Thread count tuning for I/O-bound small-file workloads Q4: Two-pass vs single-pass for the full cache inventory scan
...would return both match types per file in one walk, but I'd need to post-process output to separate them. Is a single-pass approach actually faster at this scale, or does the Linux dentry-cache warm-up from pass 1 make the two-pass approach net-faster? Q5: Any other flags I'm missing or redundant for this workload? The performance difference between ripgrep and the PHP iterator (read head 4KB) is much smaller than expected. This is precisely why I'm looking for further optimization opportunities. Some test results here Best~Hasan |
Replies: 2 comments
|
Your flags are already in good shape, so a few of these are "you're fine, stop worrying." Going in order: Q1 (mmap): Q2 (cap bytes per file): there's no "read at most N bytes then give up" flag. Q3 (threads): ripgrep auto-sets threads to your logical CPU count and parallelizes the tree walk, not the inside of one file. For many-small-files on tmpfs/FUSE your bottleneck is open()/stat()/read() latency, not CPU, so piling on threads past the default mostly buys you contention, not speed. Leave it on default. Only expose Q4 (one pass vs two): single pass is almost certainly faster at 50k-500k files. The warm dentry cache on pass 2 saves you some open/stat latency, but you still re-read every file's bytes a second time, and at this file count the per-file syscalls dominate. One walk with Q5: nothing redundant except the On the "smaller-than-expected gap vs the PHP head-4KB reader": that's the tell that you're I/O-bound, not scan-bound. When the whole job is open a file, read a few KB, close, times 200k, ripgrep's fast matching engine barely gets to flex because it's waiting on the filesystem the same as your PHP loop is. The realistic win left is fewer syscalls (the single pass), not regex tuning. If the storage is the persistent-disk case, that's where a faster mount or more read parallelism would actually move the needle. |
|
This is golden. I will drop all
Merged. psaux-it/nginx-fastcgi-cache-purge-and-preload@8347934
Overall, I see the picture more clearly now. I previously thought that we were paying a cost with Architecturally, I also use a single-pass rg walk to build an authoritative Transposition Table (with object caching). Every subsequent single-page cache purges hits the TT first as a fast path— a hit means an instant cache purge with no directory walk. On a TT miss I delegate to ripgrep for a full purge walk and update/rebuild TT. Helped me squeeze every last drop of juice out of ripgrep by optimizing its flags. |
Your flags are already in good shape, so a few of these are "you're fine, stop worrying." Going in order:
Q1 (mmap):
--no-mmapis a no-op in your commands. ripgrep only memory-maps when it's searching a single file you name directly. The moment you point it at a directory it switches to parallel traversal and doesn't mmap at all, so there's nothing to turn off. You can drop--no-mmapfrom every one of these. mmap only enters the picture for single large files, which isn't your workload.Q2 (cap bytes per file): there's no "read at most N bytes then give up" flag.
-m 1is the right tool and it's already doing the work: it stops the file as soon as it hits the first match. SinceKEY:sits ~…