<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: y4u</title>
    <description>The latest articles on DEV Community by y4u (@amru195704).</description>
    <link>https://dev.to/amru195704</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1466730%2F7b49a169-2811-48c7-98a1-214aab4983cf.jpeg</url>
      <title>DEV Community: y4u</title>
      <link>https://dev.to/amru195704</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/amru195704"/>
    <language>en</language>
    <item>
      <title>We Lose to klogg on Opening. uvf Wins the Whole Run — Re-measured at 50 GB</title>
      <dc:creator>y4u</dc:creator>
      <pubDate>Fri, 18 Sep 2026 14:23:44 +0000</pubDate>
      <link>https://dev.to/amru195704/we-lose-to-klogg-on-opening-uvf-wins-the-whole-run-re-measured-at-50-gb-318d</link>
      <guid>https://dev.to/amru195704/we-lose-to-klogg-on-opening-uvf-wins-the-whole-run-re-measured-at-50-gb-318d</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbbqq7c2z7dugwsfmfe3p.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbbqq7c2z7dugwsfmfe3p.jpeg" alt="Time to find it and read it: at 50 GB the UwView free GUI takes 189.50 s, klogg 108.14 s and uvf -open 53.69 s" width="799" height="422"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If all you do is open a 51 GB file, klogg is faster. 52.55 s against 100.6 s. That one we lose.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Run it through to the search, though, and it turns over: klogg takes 108.14 s, &lt;code&gt;uvf … -open&lt;/code&gt; takes 53.69 s.&lt;br&gt;
About 2×. The reason is not speed. It is &lt;strong&gt;how many times the file gets read.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  First — what &lt;code&gt;uvf … -open&lt;/code&gt; is
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;uvf … -open&lt;/code&gt; runs through this whole article, so here is what it means.&lt;/p&gt;

&lt;p&gt;UwView has two faces: a &lt;strong&gt;window (GUI)&lt;/strong&gt; and a &lt;strong&gt;command line (&lt;code&gt;uvf&lt;/code&gt;)&lt;/strong&gt;. Same tool — the window is for&lt;br&gt;
&lt;strong&gt;opening and reading&lt;/strong&gt; a huge file, the command is for &lt;strong&gt;finding things in it&lt;/strong&gt;.&lt;br&gt;
&lt;code&gt;-open&lt;/code&gt; is the option that joins the two.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;uvf &amp;lt;file&amp;gt; &amp;lt;pattern&amp;gt; -open
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Type that and &lt;code&gt;uvf&lt;/code&gt; reads the file &lt;strong&gt;once&lt;/strong&gt;, searches it, &lt;strong&gt;hands the byte offsets of the matching lines&lt;br&gt;
straight to the window&lt;/strong&gt;, and the UwView window comes up. &lt;strong&gt;The window does not search again.&lt;/strong&gt;&lt;br&gt;
A line-numbered list of hits is there from the first frame, and clicking any of them puts you at that&lt;br&gt;
spot in the file.&lt;/p&gt;

&lt;p&gt;In other words, it removes the round trip where you &lt;code&gt;grep&lt;/code&gt; to find roughly where something is, then&lt;br&gt;
&lt;strong&gt;open the same file again in a separate viewer and search all over again.&lt;/strong&gt;&lt;br&gt;
Every &lt;code&gt;uvf … -open&lt;/code&gt; figure in this article is &lt;strong&gt;that one command&lt;/strong&gt;, timed from pressing enter to the&lt;br&gt;
list of hits being on screen.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;uvf&lt;/code&gt; is &lt;strong&gt;free&lt;/strong&gt; (it ships with the free edition of UwView) and runs standalone on Windows, macOS and Linux&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-open&lt;/code&gt; has existed since v1.6.0, but &lt;strong&gt;v1.6.4&lt;/strong&gt; is where it became a single pass and &lt;strong&gt;v1.6.5&lt;/strong&gt; is where
the wait for the index disappeared. Everything measured here is v1.6.5&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  The loss, first
&lt;/h2&gt;

&lt;p&gt;The same data at 3 GB, 10 GB and 51.25 GB (OpenStreetMap Japan expanded to XML, plus splits of it),&lt;br&gt;
&lt;strong&gt;just opening it&lt;/strong&gt;, and &lt;strong&gt;just searching for one term&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Opening&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;3 GB&lt;/th&gt;
&lt;th&gt;10 GB&lt;/th&gt;
&lt;th&gt;50 GB&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;klogg&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;3.65 s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;10.98 s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;52.55 s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UwView (free)&lt;/td&gt;
&lt;td&gt;5.27 s&lt;/td&gt;
&lt;td&gt;19.62 s&lt;/td&gt;
&lt;td&gt;100.6 s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Searching (東京)&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;3 GB&lt;/th&gt;
&lt;th&gt;10 GB&lt;/th&gt;
&lt;th&gt;50 GB&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;klogg&lt;/td&gt;
&lt;td&gt;0.56 s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;11.75 s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;55.59 s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UwView (free)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.20 s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;16.72 s&lt;/td&gt;
&lt;td&gt;88.9 s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Hit counts were 11,274 / 11,393 / 94,979 and &lt;strong&gt;matched between the two at all three sizes&lt;/strong&gt;.&lt;br&gt;
&lt;strong&gt;From 10 GB up, klogg wins both opening and searching.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Convert the seconds to read throughput and it is obvious what is happening.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;3 GB&lt;/th&gt;
&lt;th&gt;10 GB&lt;/th&gt;
&lt;th&gt;50 GB&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;klogg, opening&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;792 MB/s&lt;/td&gt;
&lt;td&gt;891 MB/s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;930 MB/s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;klogg, searching&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;(cached)&lt;/td&gt;
&lt;td&gt;832 MB/s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;879 MB/s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UwView free, opening&lt;/td&gt;
&lt;td&gt;549 MB/s&lt;/td&gt;
&lt;td&gt;498 MB/s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;486 MB/s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UwView free, searching&lt;/td&gt;
&lt;td&gt;(cached)&lt;/td&gt;
&lt;td&gt;585 MB/s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;550 MB/s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;This drive reads at about 950 MB/s raw. klogg saturates it. Our free GUI runs at half.&lt;/strong&gt;&lt;br&gt;
That is not a bug — it matches the figures we have published all along (473–491 MB/s to open,&lt;br&gt;
575–581 MB/s to search). &lt;strong&gt;It is how the thing is built, and it is a debt we owe.&lt;/strong&gt; More on that below.&lt;/p&gt;
&lt;h2&gt;
  
  
  And yet the whole run turns over
&lt;/h2&gt;

&lt;p&gt;What you actually want is not "open it". It is &lt;strong&gt;"find it and read it."&lt;/strong&gt; So we timed that.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Time to find it and read it&lt;/th&gt;
&lt;th&gt;3 GB&lt;/th&gt;
&lt;th&gt;10 GB&lt;/th&gt;
&lt;th&gt;50 GB&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;UwView free GUI (open + search)&lt;/td&gt;
&lt;td&gt;5.47 s&lt;/td&gt;
&lt;td&gt;36.34 s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;189.50 s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;klogg (open + search)&lt;/td&gt;
&lt;td&gt;4.21 s&lt;/td&gt;
&lt;td&gt;22.73 s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;108.14 s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;uvf … -open&lt;/code&gt; (v1.6.5)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;3.28 s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;10.41 s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;53.69 s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ratio (vs klogg / vs free GUI)&lt;/td&gt;
&lt;td&gt;1.28 / 1.67&lt;/td&gt;
&lt;td&gt;2.18 / 3.49&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2.01 / 3.53&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;klogg reads the file once to open it and again to search it.&lt;/strong&gt; The UwView free GUI also reads it twice,&lt;br&gt;
and at half the speed — which is how you get to 3.5×.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;uvf file 'pattern' -open&lt;/code&gt; &lt;strong&gt;reads it once.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ time uvf osm/japan-latest.osm 東京 -open
11.32s user  7.31s system  34% cpu  53.690 total
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;51.25 GB ÷ 53.69 s = 910 MB/s — essentially this drive's raw read speed (≈ 950 MB/s).&lt;/strong&gt;&lt;br&gt;
CPU sits at 34%; the rest is waiting on the disk, so &lt;strong&gt;the medium is the entire bottleneck.&lt;/strong&gt;&lt;br&gt;
Inside those 53.69 seconds it searches, builds the index, hands everything to the window and puts a&lt;br&gt;
line-numbered list of hits on screen. The window itself then appears in under a second.&lt;/p&gt;

&lt;p&gt;At 10 GB it is 10.405 s (940 MB/s); at 3 GB, 3.279 s (882 MB/s). &lt;strong&gt;At every size, it costs one pass over the file.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For reference, &lt;strong&gt;the &lt;code&gt;uvf&lt;/code&gt; command measured on the search alone&lt;/strong&gt; runs at 871 / 933 / 962 MB/s.&lt;br&gt;
&lt;code&gt;-open&lt;/code&gt; at 882 / 940 / 910 sits in the same band, &lt;strong&gt;dropping about 5% only at 50 GB&lt;/strong&gt; — that is the window starting up.&lt;/p&gt;
&lt;h2&gt;
  
  
  258 GB behaves the same way
&lt;/h2&gt;

&lt;p&gt;We checked the largest file too — the whole United States from OpenStreetMap expanded to XML,&lt;br&gt;
&lt;strong&gt;258.68 GB and 4.5 billion lines&lt;/strong&gt;, in one unsplit file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ sudo purge
$ time uvf osmUs/us-260726.osm 'New York' -open
56.24s user  40.60s system  36% cpu  4:25.21 total
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;265.21 s (4 min 25.2 s), 930 MB/s&lt;/strong&gt; — the medium's speed again.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The first run gave 250.68 s (984 MB/s). &lt;strong&gt;That was above the drive's ~950 MB/s raw read, so we put it down to&lt;br&gt;
cache left by a run we had stopped partway, and re-took it after &lt;code&gt;sudo purge&lt;/code&gt;.&lt;/strong&gt; The difference is 5.5%.&lt;br&gt;
&lt;strong&gt;We are publishing the slower number — the one without cache.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now line that up against how long each of them takes &lt;strong&gt;just to finish opening the file&lt;/strong&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;258.68 GB&lt;/th&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;th&gt;Throughput&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;klogg, &lt;strong&gt;to finish opening&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;258 s&lt;/td&gt;
&lt;td&gt;956 MB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;uvf … -open&lt;/code&gt; (find it, all the way to the screen)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;265.21 s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;930 MB/s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UwView Pro, &lt;strong&gt;to finish opening&lt;/strong&gt; (building the &lt;code&gt;.uwvz&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;328 s&lt;/td&gt;
&lt;td&gt;752 MB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UwView free GUI, &lt;strong&gt;to finish opening&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;530.1 s&lt;/td&gt;
&lt;td&gt;465 MB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;In about the time klogg needs merely to finish opening the file, we have already searched it and put the hits&lt;br&gt;
on screen&lt;/strong&gt; (258 s against 265.21 s — 7 seconds, 2.8%, apart). klogg starts searching from there.&lt;/p&gt;

&lt;p&gt;Doing the same thing in the free GUI costs 8 min 50.1 s to open plus 7 min 20.0 s to search — &lt;strong&gt;16 min 10 s&lt;/strong&gt;, or &lt;strong&gt;3.66×&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;(Both free-GUI figures were re-taken the same day and came out the same — the earlier run gave 532.6 s / 449.4 s, 0.5% and 2.1% apart. &lt;strong&gt;That is 465 / 561 MB/s&lt;/strong&gt;, the same band as 486 / 550 at 50 GB.)&lt;/p&gt;

&lt;p&gt;And of course, &lt;strong&gt;if you keep coming back to the same file, that is Pro.&lt;/strong&gt; Once the &lt;code&gt;.uwvz&lt;/code&gt; exists, a search takes&lt;br&gt;
&lt;strong&gt;34.8 s&lt;/strong&gt; and opening it again is instant. One pass, or many — that is the dividing line.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why one pass is enough
&lt;/h2&gt;

&lt;p&gt;When &lt;code&gt;uvf&lt;/code&gt; first shipped in v1.6.0, &lt;code&gt;-open&lt;/code&gt; passed &lt;strong&gt;only the pattern&lt;/strong&gt; to the window, and the window&lt;br&gt;
&lt;strong&gt;searched again on its own.&lt;/strong&gt; The CLI read everything, then the window read everything.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;v1.6.4&lt;/strong&gt; — the CLI hands over the byte offsets of the lines it found. The window stops re-searching&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;v1.6.5&lt;/strong&gt; — it also &lt;strong&gt;collects the index markers while searching&lt;/strong&gt; and hands those over too. The window
assembles the index instead of re-reading the file, and since the line numbers travel with it,
&lt;strong&gt;the results list has line numbers from the first frame&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is reading the whole file anyway, so &lt;strong&gt;counting one more thing costs almost nothing.&lt;/strong&gt; And indeed the&lt;br&gt;
timings above are near-identical to the search-only figures for &lt;code&gt;uvf&lt;/code&gt; (3.32 s / 10.48 s / 50.82 s).&lt;br&gt;
&lt;strong&gt;The handoff adds essentially zero.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The 3 GB case narrows
&lt;/h2&gt;

&lt;p&gt;Look at the ratios again: 1.28× against klogg at 3 GB, against 2.01× at 50 GB.&lt;/p&gt;

&lt;p&gt;The reason is simple — &lt;strong&gt;3 GB fits in memory (32 GB here).&lt;/strong&gt; klogg and the free GUI still read it twice,&lt;br&gt;
but &lt;strong&gt;the second read comes from cache&lt;/strong&gt;, so reading twice costs almost nothing (klogg's 0.56 s search at&lt;br&gt;
3 GB, the free GUI's 0.20 s).&lt;/p&gt;

&lt;p&gt;From 10 GB up it does not fit, so &lt;strong&gt;the second read really does hit the disk&lt;/strong&gt;, and the gap opens.&lt;br&gt;
This is the same thing we have been saying for months: 3 GB is a draw; the difference starts at 10 GB.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The free GUI's 0.20 s at 3 GB is near the limit of what a person can time by hand. &lt;strong&gt;Even if it were&lt;br&gt;
really 1.0 s&lt;/strong&gt;, the total is 6.27 s — 1.9× — and the conclusion does not move.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The debt we owe
&lt;/h2&gt;

&lt;p&gt;This measurement made our own weak spot plain.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The free GUI opens at 486 MB/s. klogg opens at 930 MB/s. Very nearly a factor of two.&lt;/strong&gt;&lt;br&gt;
The &lt;code&gt;uvf&lt;/code&gt; command was rebuilt as a single pass in v1.6.3 and reaches 962 MB/s, so &lt;strong&gt;we already have proof&lt;br&gt;
that this machine and this disk can do it.&lt;/strong&gt; If the GUI's index build is brought to the same way of reading,&lt;br&gt;
opening 50 GB should go from 100.6 s to somewhere near 52 s.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;klogg saturating the medium is, for us, a measured target.&lt;/strong&gt; We will close it in a coming release.&lt;/p&gt;

&lt;h2&gt;
  
  
  The side that keeps an index
&lt;/h2&gt;

&lt;p&gt;Everything above is the free edition. The paid &lt;code&gt;uvp&lt;/code&gt; converts the file once into a &lt;code&gt;.uwvz&lt;/code&gt;&lt;br&gt;
(about one ninth of the original, with a line index) and never touches the original again.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Search&lt;/th&gt;
&lt;th&gt;klogg&lt;/th&gt;
&lt;th&gt;
&lt;code&gt;uvp&lt;/code&gt; (with &lt;code&gt;.uwvz&lt;/code&gt;)&lt;/th&gt;
&lt;th&gt;Difference&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;10 GB&lt;/td&gt;
&lt;td&gt;11.75 s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1.80 s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;6.5×&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;50 GB&lt;/td&gt;
&lt;td&gt;55.59 s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;6.34 s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;8.8×&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Opening it again&lt;/th&gt;
&lt;th&gt;klogg&lt;/th&gt;
&lt;th&gt;&lt;code&gt;uvp&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;Difference&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;10 GB&lt;/td&gt;
&lt;td&gt;10.98 s (every time)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.01–0.07 s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;150–1,100×&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;50 GB&lt;/td&gt;
&lt;td&gt;52.55 s (every time)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.01–0.07 s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;750–5,000×&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;klogg rebuilds its index every time you open the file.&lt;/strong&gt; That is the dividing line between&lt;br&gt;
"look at it once" and "come back to it again and again".&lt;/p&gt;

&lt;h2&gt;
  
  
  Which to use
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Looking once&lt;/strong&gt; → klogg is enough. It opens fast, it is free, and it is open source&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Find it, then read it&lt;/strong&gt; → &lt;strong&gt;&lt;code&gt;uvf … -open&lt;/code&gt;&lt;/strong&gt;. One pass over the file gets you all the way to the window. Free&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Coming back to the same file&lt;/strong&gt; → &lt;strong&gt;UwView Pro&lt;/strong&gt;. Opening again takes 0.01–0.07 s, searching takes 6.34 s&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How it was measured (you can repeat it)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;File: OpenStreetMap Japan XML, one file of 51,254,526,392 bytes (the 10 GB and 3 GB files are splits of the same data)&lt;/li&gt;
&lt;li&gt;Term: &lt;code&gt;東京&lt;/code&gt;, fixed string. &lt;strong&gt;Hit counts confirmed at 11,274 / 11,393 / 94,979 — all three matched&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sudo purge&lt;/code&gt; before every run, then a 10 s settle; everything measured cold&lt;/li&gt;
&lt;li&gt;Mac M4 / 32 GB / external USB SSD; klogg 24.11.0.1685; UwView free GUI and &lt;code&gt;uvf&lt;/code&gt; v1.6.5&lt;/li&gt;
&lt;li&gt;The GUI runs were timed by hand (open = until the index finishes and you can move anywhere in the file;
search = until the hit count settles)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;uvf&lt;/code&gt; is free&lt;/strong&gt; and runs on Windows, macOS and Linux as a single binary from&lt;br&gt;
&lt;a href="https://github.com/amru195704/UwView/releases/latest" rel="noopener noreferrer"&gt;GitHub Releases&lt;/a&gt;.&lt;br&gt;
If you want an index so you can come back to the same file, that is &lt;a href="https://uvp.y42u.net/pro/" rel="noopener noreferrer"&gt;UwView Pro&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>logging</category>
      <category>cli</category>
      <category>performance</category>
      <category>showdev</category>
    </item>
    <item>
      <title>ripgrep Is 2.9x Faster With no-mmap — Measured Against the Default mmap Read on One 51 GB File, on Three OSes</title>
      <dc:creator>y4u</dc:creator>
      <pubDate>Fri, 18 Sep 2026 14:20:27 +0000</pubDate>
      <link>https://dev.to/amru195704/ripgrep-is-29x-faster-with-no-mmap-measured-against-the-default-mmap-read-on-one-51-gb-file-on-1epb</link>
      <guid>https://dev.to/amru195704/ripgrep-is-29x-faster-with-no-mmap-measured-against-the-default-mmap-read-on-one-51-gb-file-on-1epb</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5gdmdqfo8v4dqdnz3t32.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5gdmdqfo8v4dqdnz3t32.jpeg" alt="The effect of --no-mmap when searching a 51 GB file with ripgrep: Windows 1.22x and 2.89x faster, macOS unchanged, Linux 1.2x slower on a fixed string and 1.64x faster on regex plus invert" width="799" height="422"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Searching a 51 GB file with ripgrep, adding &lt;code&gt;--no-mmap&lt;/code&gt; can make it faster. On Windows, by up to 2.89×. On macOS, nothing changes. On Linux it goes either way, depending on what you are searching for.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is not a story about a flaw in ripgrep. &lt;strong&gt;Memory-mapped reading is the right default most of the time.&lt;/strong&gt; It is just that "most of the time" turned out not to mean "always".&lt;/p&gt;

&lt;h2&gt;
  
  
  The numbers first
&lt;/h2&gt;

&lt;p&gt;The same single 51.25 GB file (OpenStreetMap Japan expanded to XML — 51,254,526,392 bytes), the same two searches, every run measured immediately after dropping the cache.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Machine&lt;/th&gt;
&lt;th&gt;Search&lt;/th&gt;
&lt;th&gt;default (&lt;code&gt;--mmap&lt;/code&gt;)&lt;/th&gt;
&lt;th&gt;&lt;code&gt;--no-mmap&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;Effect&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Windows 11&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;fixed string&lt;/td&gt;
&lt;td&gt;83.74 s&lt;/td&gt;
&lt;td&gt;68.50 s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1.22× faster&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Windows 11&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;regex + invert&lt;/td&gt;
&lt;td&gt;282.84 s&lt;/td&gt;
&lt;td&gt;97.83 s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2.89× faster&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;macOS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;fixed string&lt;/td&gt;
&lt;td&gt;55.38 s&lt;/td&gt;
&lt;td&gt;55.70 s&lt;/td&gt;
&lt;td&gt;no change&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;macOS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;regex + invert&lt;/td&gt;
&lt;td&gt;59.40 s&lt;/td&gt;
&lt;td&gt;58.47 s&lt;/td&gt;
&lt;td&gt;no change&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Linux (VM, 8 GB RAM)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;fixed string&lt;/td&gt;
&lt;td&gt;~83 s&lt;/td&gt;
&lt;td&gt;~100 s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~1.2× slower&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Linux (VM, 8 GB RAM)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;regex + invert&lt;/td&gt;
&lt;td&gt;~172 s&lt;/td&gt;
&lt;td&gt;~105 s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1.64× faster&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Six cases: &lt;strong&gt;three got faster, two stayed the same, one got slower.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Three things follow.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;On macOS you can ignore all of this.&lt;/strong&gt; Either way is the same. The default is already doing the best it can.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;On Windows the default costs you&lt;/strong&gt; on a 51 GB file — nearly 3× on the heavier search.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;On Linux it flips with the workload.&lt;/strong&gt; Light search: the default wins. Heavy search: &lt;code&gt;--no-mmap&lt;/code&gt; wins.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why we ended up measuring this
&lt;/h2&gt;

&lt;p&gt;We were comparing our own CLI — &lt;code&gt;uvf&lt;/code&gt;, which ships with the free edition of UwView — against ripgrep across 3 GB, 10 GB and 50 GB on macOS, Windows and Linux. &lt;strong&gt;The 50 GB case on Windows looked wrong.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Summed over the same seven searches, Windows against macOS came out at 1.09× for 3 GB and 1.45× for 10 GB. That is a believable machine difference. But &lt;strong&gt;50 GB jumped to 2.25×&lt;/strong&gt;, and inside it one item — &lt;code&gt;-E -v&lt;/code&gt;, a regex printing the lines that do &lt;em&gt;not&lt;/em&gt; match — took 283 s against macOS's 58 s. &lt;strong&gt;4.9×.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;"The machine is slower" does not explain that, because there is no reason for the ratio to get worse as the file grows. So we wrote a script to take it apart.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it was measured
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;File: OpenStreetMap Japan XML, one file of 51,254,526,392 bytes&lt;/li&gt;
&lt;li&gt;Search 1 (fixed string): &lt;code&gt;rg -n -F '東京' &amp;lt;file&amp;gt;&lt;/code&gt; — 94,979 hits&lt;/li&gt;
&lt;li&gt;Search 2 (regex + invert): &lt;code&gt;rg -n -v '^ +&amp;lt;' &amp;lt;file&amp;gt;&lt;/code&gt; — 3 hits&lt;/li&gt;
&lt;li&gt;ripgrep 15.2.0 (15.1.0 on Linux)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cache dropped before every single run, then a 10 s settle&lt;/strong&gt; (&lt;code&gt;sudo purge&lt;/code&gt; on macOS, &lt;code&gt;drop_caches&lt;/code&gt; on Linux, RAMMap &lt;code&gt;-Et&lt;/code&gt; from an elevated shell on Windows)&lt;/li&gt;
&lt;li&gt;Same script everywhere; hit counts recorded each time and checked to match between &lt;code&gt;--mmap&lt;/code&gt; and &lt;code&gt;--no-mmap&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The machines:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;macOS&lt;/td&gt;
&lt;td&gt;Apple M4 / 32 GB / external USB SSD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Windows 11&lt;/td&gt;
&lt;td&gt;HP Spectre x360 (Core i7-1165G7, 4C/8T, 15 W class) / 15.6 GB / Intel Optane H20 with SSD 512 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Linux&lt;/td&gt;
&lt;td&gt;VMware on the same Mac, 2 vCPU / 8 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Do not compare seconds across machines.&lt;/strong&gt; The comparison is the two bars inside one machine.&lt;/p&gt;

&lt;h2&gt;
  
  
  It was neither the disk nor the CPU
&lt;/h2&gt;

&lt;p&gt;On Windows the first suspect was file placement and fragmentation. &lt;strong&gt;It was not that.&lt;/strong&gt; Reading the same 3 GiB with no tool in the way gave &lt;strong&gt;1159 MB/s at the start of the 51 GB file, 1134 MB/s in the middle and 1168 MB/s at the end.&lt;/strong&gt; Flat. This drive does 1.1 GB/s raw, so reading 51.25 GB has a floor of about 45 s.&lt;/p&gt;

&lt;p&gt;The second suspect was thermal throttling — a 15 W ultrabook under five minutes of load is a fair thing to suspect. &lt;strong&gt;Also not it.&lt;/strong&gt; The clock read 2803 MHz before and after, and running the same 3 GB search five times in a row gave 7.1 s from the second run onward, steady.&lt;/p&gt;

&lt;p&gt;What was left was the reading. And adding &lt;code&gt;--no-mmap&lt;/code&gt; turned 282.84 s into 97.83 s.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why (this part is a guess)
&lt;/h2&gt;

&lt;p&gt;A memory map makes a file look like memory. In practice, every page you touch is fetched from disk by the OS. &lt;strong&gt;When the file fits in RAM this is excellent&lt;/strong&gt; — it removes a copy.&lt;/p&gt;

&lt;p&gt;It stops being excellent when the file does not fit. 51.25 GB fits neither in the Windows machine's 15.6 GB nor in the Mac's 32 GB, so the whole read becomes a long sequence of mapping pages in and throwing them out again. &lt;strong&gt;How much that costs apparently differs by operating system&lt;/strong&gt; — that is our working explanation.&lt;/p&gt;

&lt;p&gt;To be honest about it: &lt;strong&gt;this is reasoning backwards from the measurements, not something we verified.&lt;/strong&gt; In particular we cannot cleanly explain why Linux flips — default faster for the light search, &lt;code&gt;--no-mmap&lt;/code&gt; faster for the heavy one. If you know, we would like to hear it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to actually do
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;On Windows, if you are searching one file larger than about 10 GB with ripgrep, try &lt;code&gt;--no-mmap&lt;/code&gt; once.&lt;/strong&gt; It is one word, and it does not cost you anything.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rg &lt;span class="nt"&gt;--no-mmap&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s1"&gt;'pattern'&lt;/span&gt; huge.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;On macOS, do nothing.&lt;/strong&gt; There is no difference.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;On Linux, it is worth trying for heavy searches&lt;/strong&gt; — regex, &lt;code&gt;-v&lt;/code&gt;, &lt;code&gt;-i&lt;/code&gt; and combinations of them. For a plain fixed string the default was faster in our runs.&lt;/p&gt;

&lt;p&gt;Again: &lt;strong&gt;this is not a knock on ripgrep.&lt;/strong&gt; Defaulting to mmap is the right call for the normal case — walking a source tree, reading logs of a few megabytes to a few gigabytes. What we measured sits at the far edge of the range: one file that does not fit in memory.&lt;/p&gt;

&lt;h2&gt;
  
  
  About our own tool, briefly
&lt;/h2&gt;

&lt;p&gt;Since we were the other side of this comparison, here is where &lt;code&gt;uvf&lt;/code&gt; sits.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;uvf&lt;/code&gt; does not memory-map. It reads sequentially into a fixed buffer. As a result &lt;strong&gt;it behaves the same on all three systems — there is no good case and no bad case.&lt;/strong&gt; On Windows at 10 GB with a fixed string, cold, ripgrep took 15.91 s and &lt;code&gt;uvf&lt;/code&gt; took 7.22 s: &lt;strong&gt;2.2×.&lt;/strong&gt; On macOS the two are level (3.26 s against 3.32 s cold at 3 GB), so we read that gap as exactly the amount Windows loses to mmap.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The flip side is a real one.&lt;/strong&gt; On Linux the default mmap is faster for light searches, and &lt;code&gt;uvf&lt;/code&gt; is not using it. There is speed left on the table there. Right now we prefer having the same behaviour everywhere, but it is on the list.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;uvf&lt;/code&gt; is free, runs on Windows, macOS and Linux, and is a single binary from &lt;a href="https://github.com/amru195704/UwView/releases/latest" rel="noopener noreferrer"&gt;GitHub Releases&lt;/a&gt;. If you want to search the same file repeatedly there is a paid edition (&lt;code&gt;uvp&lt;/code&gt;) that builds an index beside it — but &lt;strong&gt;for searching, &lt;code&gt;uvf&lt;/code&gt; is enough.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Trying it yourself
&lt;/h2&gt;

&lt;p&gt;Two runs against the same file, dropping the cache in between.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# first (the default)&lt;/span&gt;
rg &lt;span class="nt"&gt;--mmap&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s1"&gt;'pattern'&lt;/span&gt; huge.log &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /dev/null

&lt;span class="c"&gt;# drop the cache&lt;/span&gt;
&lt;span class="c"&gt;#   macOS  : sudo purge&lt;/span&gt;
&lt;span class="c"&gt;#   Linux  : sync &amp;amp;&amp;amp; sudo sh -c 'echo 3 &amp;gt; /proc/sys/vm/drop_caches'&lt;/span&gt;
&lt;span class="c"&gt;#   Windows: RAMMap64.exe -accepteula -Et   (elevated)&lt;/span&gt;

&lt;span class="c"&gt;# second&lt;/span&gt;
rg &lt;span class="nt"&gt;--no-mmap&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s1"&gt;'pattern'&lt;/span&gt; huge.log &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /dev/null
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Measuring without dropping the cache tells you nothing&lt;/strong&gt; except that the second run is warmer, so do not skip that step. And check that both runs print the same number of lines — if they do not, something about the measurement is wrong rather than something about ripgrep.&lt;/p&gt;

</description>
      <category>ripgrep</category>
      <category>cli</category>
      <category>performance</category>
      <category>showdev</category>
    </item>
    <item>
      <title>I Thought We Lost at 3 GB — Turns Out We'd Won (As Long as the Cache Is Cold)</title>
      <dc:creator>y4u</dc:creator>
      <pubDate>Wed, 16 Sep 2026 13:41:57 +0000</pubDate>
      <link>https://dev.to/amru195704/beating-ripgrep-even-at-3-gb-concede-the-single-grep-win-the-rest-of-the-investigation-2pon</link>
      <guid>https://dev.to/amru195704/beating-ripgrep-even-at-3-gb-concede-the-single-grep-win-the-rest-of-the-investigation-2pon</guid>
      <description>&lt;p&gt;&lt;strong&gt;A grep on 3 GB belongs to ripgrep if the file is in the cache. If it is not, it belongs to uvp.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://uvp.y42u.net/en/blog/uvp-rg-one-place-to-win-en/" rel="noopener noreferrer"&gt;the previous article&lt;/a&gt; I wrote that 3 GB is ripgrep's ground and I concede it. When the file fits in memory, an index has little to offer — believing that, I had been measuring warm every single time. &lt;strong&gt;Drop the cache and measure again, and it is the other way round.&lt;/strong&gt; So here is the 3 GB picture again, that included.&lt;/p&gt;

&lt;h2&gt;
  
  
  Up front
&lt;/h2&gt;

&lt;p&gt;I compared ripgrep / sed / gzip against &lt;code&gt;uvp&lt;/code&gt; on a 3 GB file (3,032,812,644 bytes cut from OpenStreetMap Japan XML) under the same rules (Apple M4 / 32 GB / external USB SSD, &lt;code&gt;/usr/bin/time -p&lt;/code&gt; real. Each search was measured &lt;strong&gt;cold — straight after &lt;code&gt;sudo purge&lt;/code&gt; — and warm, the run immediately after&lt;/strong&gt;, with the &lt;code&gt;.uwvz&lt;/code&gt; already built).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pure search swaps with the state of the cache.&lt;/strong&gt; Warm, ripgrep (0.33 s vs 1.01 s); cold, uvp (3.26 s vs 1.25 s = &lt;strong&gt;2.6×&lt;/strong&gt;). It comes down to whether uvp's fixed cost — startup plus index load, just under a second — is larger or smaller than rg's whole scan, and &lt;strong&gt;the absolute gap is under 0.7 s either way&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Even so, &lt;strong&gt;there are four situations where you win at 3 GB&lt;/strong&gt;: &lt;strong&gt;① fixing and saving&lt;/strong&gt; (replace 3.5× and extract 12.6× over sed), &lt;strong&gt;② operations rg does not have&lt;/strong&gt; (sequence detection, ±N-line drill-down, click-to-drill), &lt;strong&gt;③ logs kept as gz/zip&lt;/strong&gt; (expand + rg 6.47 s vs &lt;code&gt;.uwvz&lt;/code&gt; direct 0.68 s = 9.5×), and &lt;strong&gt;④ not yet knowing what you are looking for&lt;/strong&gt; (narrow in the CLI, look in the GUI, narrow again — 0.24 s per stage)&lt;/li&gt;
&lt;li&gt;In short: &lt;strong&gt;on a day that ends with grep, use rg. The moment the investigation involves reading, narrowing, counting, fixing or saving, 3 GB becomes uvp's ground too&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  First, drop the cache and measure again
&lt;/h2&gt;

&lt;p&gt;First, pure search, measured in two states. &lt;strong&gt;Cold&lt;/strong&gt; is straight after &lt;code&gt;sudo purge&lt;/code&gt; (the file is not in the page cache); &lt;strong&gt;warm&lt;/strong&gt; is the run immediately after (it is in memory). uvp 1.6.3, ripgrep 15.2.0, with the &lt;code&gt;.uwvz&lt;/code&gt; already built.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cold (opening a file after a while)&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Search&lt;/th&gt;
&lt;th&gt;&lt;code&gt;rg&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;uvp&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;rg ÷ uvp&lt;/th&gt;
&lt;th&gt;Hits&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Fixed string&lt;/td&gt;
&lt;td&gt;3.26 s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1.25 s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2.6×&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;11,274&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ignore case &lt;code&gt;-i&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;3.30 s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1.58 s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2.1×&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;940,482&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Regex &lt;code&gt;-E&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;3.32 s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1.70 s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2.0×&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;928,301&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Regex, anchored&lt;/td&gt;
&lt;td&gt;3.24 s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1.47 s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2.2×&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;222,072&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Regex + ignore case&lt;/td&gt;
&lt;td&gt;3.29 s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1.69 s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1.9×&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;491,643&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Exclude &lt;code&gt;-E -v&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;3.57 s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1.85 s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1.9×&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Exclude &lt;code&gt;-v&lt;/code&gt;, plain term&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;3.51 s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;3.96 s&lt;/td&gt;
&lt;td&gt;1/1.1&lt;/td&gt;
&lt;td&gt;7,698,790&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Six of seven go to uvp, by roughly 2×.&lt;/strong&gt; The reason is exactly the reason at 10 and 50 GB: &lt;strong&gt;it reads 1/12 of the bytes&lt;/strong&gt; (3,032,812,644 against 251,829,224).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Warm (asking again, right after reading it)&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Search&lt;/th&gt;
&lt;th&gt;&lt;code&gt;rg&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;uvp&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;rg ÷ uvp&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Fixed string&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.33 s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1.01 s&lt;/td&gt;
&lt;td&gt;1/3.1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ignore case &lt;code&gt;-i&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.65 s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1.22 s&lt;/td&gt;
&lt;td&gt;1/1.9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Regex &lt;code&gt;-E&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.52 s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1.28 s&lt;/td&gt;
&lt;td&gt;1/2.5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Regex, anchored&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.48 s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1.16 s&lt;/td&gt;
&lt;td&gt;1/2.4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Regex + ignore case&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.61 s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1.40 s&lt;/td&gt;
&lt;td&gt;1/2.3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Exclude &lt;code&gt;-v&lt;/code&gt;, plain term&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2.10 s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;3.74 s&lt;/td&gt;
&lt;td&gt;1/1.8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Exclude &lt;code&gt;-E -v&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;3.13 s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1.74 s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1.8×&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Warm, six rows go to rg. 3 GB fits in 32 GB of RAM, so one read puts the whole file in the cache and &lt;strong&gt;rg runs straight over it in 0.33 s&lt;/strong&gt;, while uvp pays its startup and index load first. &lt;strong&gt;I concede this.&lt;/strong&gt; The gap is 0.7 s, and conceding it costs nothing in practice.&lt;/p&gt;

&lt;p&gt;One exception: &lt;strong&gt;&lt;code&gt;-E -v&lt;/code&gt; is faster on uvp even warm&lt;/strong&gt; (1.74 s against 3.13 s). Excluding works over every line, so ripgrep's cache advantage stops helping and the difference in bytes read shows through.&lt;/p&gt;

&lt;h3&gt;
  
  
  At 10 GB there is no warm state at all
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;10 GB&lt;/th&gt;
&lt;th&gt;Cold&lt;/th&gt;
&lt;th&gt;Warm&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;rg&lt;/code&gt; (fixed string)&lt;/td&gt;
&lt;td&gt;10.96 s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;10.89 s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;uvp&lt;/code&gt; (&lt;code&gt;.uwvz&lt;/code&gt; 1.16 GB)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2.37 s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1.55 s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The second run is the same 10.96 s.&lt;/strong&gt; The file does not fit in 32 GB of RAM, so every run reads it off the drive again. "At 3 GB, use rg" holds only when the file fits in memory &lt;strong&gt;and&lt;/strong&gt; someone read it a moment ago. Right after a reboot, on a log you last opened days ago, on a USB HDD or a network share, the difference in bytes read shows up even at 3 GB.&lt;/p&gt;

&lt;p&gt;The problem is that an investigation almost never ends after one grep.&lt;/p&gt;

&lt;h2&gt;
  
  
  ① A day you fix and save — when the job writes output, bytes read matter directly
&lt;/h2&gt;

&lt;p&gt;Search is "read and count". Extract and replace are "read and &lt;strong&gt;write out&lt;/strong&gt;". That changes things.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Test&lt;/th&gt;
&lt;th&gt;CLI&lt;/th&gt;
&lt;th&gt;&lt;code&gt;uvp&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;Ratio&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Extract hit lines (&lt;code&gt;sed -n '/term/p'&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;9.96 s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.79 s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;12.6×&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Replace all (&lt;code&gt;sed 's/A/B/g'&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;15.04 s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;4.28 s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;3.5×&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Same (&lt;code&gt;rg --passthru -r&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;4.28 s&lt;/td&gt;
&lt;td&gt;1.7×&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;sed processes one line at a time, so even 3 GB takes 10–15 s. uvp pulls only the lines it needs out of a 0.25 GB index and writes them, so &lt;strong&gt;it wins even at 3 GB.&lt;/strong&gt; The replace output is byte-identical to sed and to &lt;code&gt;rg --passthru -r&lt;/code&gt; in every combination tested.&lt;/p&gt;

&lt;p&gt;Add up a day's flow — "investigate, fix, save" — and it looks like this.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Open → 2 queries → replace and save (3 GB)&lt;/th&gt;
&lt;th&gt;Total&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;rg + sed&lt;/td&gt;
&lt;td&gt;17.7 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;uvp (including index build)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;7.6 s&lt;/strong&gt; (&lt;strong&gt;2.3×&lt;/strong&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;uvp's 7.6 s includes 1.2 s to build the index on the first run. &lt;strong&gt;It still wins.&lt;/strong&gt; One replace turns sed's 15 s into uvp's 4.3 s, and next to that 10-second gap the 1.2 s for the index is noise.&lt;/p&gt;

&lt;h2&gt;
  
  
  ② A day you use something rg does not have
&lt;/h2&gt;

&lt;p&gt;Some rows could only go into the comparison as "reference", because rg has no equivalent command.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;th&gt;
&lt;code&gt;uvp&lt;/code&gt; (3 GB)&lt;/th&gt;
&lt;th&gt;In rg you would…&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Sequence detection &lt;code&gt;-seq login,timeout,restart&lt;/code&gt; (only flows in this order)&lt;/td&gt;
&lt;td&gt;0.99 s&lt;/td&gt;
&lt;td&gt;write a state machine in awk&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Second term within ±N lines &lt;code&gt;-C 3 &amp;lt;term2&amp;gt;&lt;/code&gt; (co-occurrence nearby)&lt;/td&gt;
&lt;td&gt;1.23 s&lt;/td&gt;
&lt;td&gt;`rg -C 3 \&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Click a tally value to drill into it&lt;/td&gt;
&lt;td&gt;instant in the GUI&lt;/td&gt;
&lt;td&gt;read {% raw %}`rg -o \&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Jump from a hit to the original text with line numbers&lt;/td&gt;
&lt;td&gt;instant in the GUI&lt;/td&gt;
&lt;td&gt;note the line number from {% raw %}&lt;code&gt;rg -n&lt;/code&gt;, then &lt;code&gt;sed -n 'Np'&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Before "faster or slower", these are rows where &lt;strong&gt;the comparison itself does not exist&lt;/strong&gt;. "Only the flows where a timeout follows a failed login within three lines", "only the hits with another term within five lines" — use any one of these in an investigation and whether the file is 3 GB stops mattering.&lt;/p&gt;

&lt;h2&gt;
  
  
  ③ A day the logs are kept as gz / zip — stop expanding them every time
&lt;/h2&gt;

&lt;p&gt;Logs are normally kept gzipped. To search them you expand and then rg — and &lt;code&gt;zgrep&lt;/code&gt; does not save you the expansion itself.&lt;/p&gt;

&lt;p&gt;I compressed the same 3 GB with &lt;code&gt;gzip -6&lt;/code&gt;, &lt;code&gt;zip -6&lt;/code&gt; and &lt;code&gt;.uwvz&lt;/code&gt;, and compared &lt;strong&gt;expand-then-search&lt;/strong&gt; with &lt;strong&gt;search directly&lt;/strong&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;3 GB&lt;/th&gt;
&lt;th&gt;Time to compress&lt;/th&gt;
&lt;th&gt;Size&lt;/th&gt;
&lt;th&gt;Search (expand + rg / direct)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;gzip -6&lt;/td&gt;
&lt;td&gt;16.8 s&lt;/td&gt;
&lt;td&gt;0.30 GB (1/10.1)&lt;/td&gt;
&lt;td&gt;6.47 s (expand 3.49 + rg 2.98)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;zip -6&lt;/td&gt;
&lt;td&gt;19.4 s&lt;/td&gt;
&lt;td&gt;0.30 GB (1/10.1)&lt;/td&gt;
&lt;td&gt;8.71 s (expand 8.08 + rg 0.63)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;.uwvz&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1.2 s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.25 GB (1/12.0)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;0.68 s&lt;/strong&gt; (direct search)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Same size as gzip, 14× faster to create, 9.5× faster to search.&lt;/strong&gt; gzip expands everything every time you search; &lt;code&gt;.uwvz&lt;/code&gt; carries an index and is searched without expanding. The second and third questions stay at 0.68 s. You could keep the expanded file around and get rg's 0.33 s from the second question on, but then there was no point compressing.&lt;/p&gt;

&lt;p&gt;And &lt;code&gt;.uwvz&lt;/code&gt; restores the original byte-for-byte with &lt;code&gt;-extract&lt;/code&gt; (free). &lt;strong&gt;Change the storage format from gz to &lt;code&gt;.uwvz&lt;/code&gt; and the per-search expansion disappears, even at 3 GB.&lt;/strong&gt; At 50 GB it is 7 s against gzip's 2 minutes — 17×.&lt;/p&gt;

&lt;h2&gt;
  
  
  ④ A day you do not yet know what you are looking for — this is the real point
&lt;/h2&gt;

&lt;p&gt;In the table of losses, &lt;code&gt;東京&lt;/code&gt; at 3 GB returns 11,274 hits. &lt;strong&gt;rg's 0.33 s does not include the time for a person to read those 11,274 lines.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There are two kinds of investigation. Checking whether a string is there — and &lt;strong&gt;the kind where you do not yet know what to search for&lt;/strong&gt;. The second is trial and error itself: scan the 11,274, look around the interesting ones, narrow by another term, count, go back. In a terminal you rebuild the command each time for 0.33 s — except that what you actually pay is &lt;strong&gt;the time to rebuild it and the time to follow output scrolling past&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Put &lt;code&gt;-open&lt;/code&gt; at the end of a &lt;code&gt;uvp&lt;/code&gt; command and the CLI result is handed straight to the GUI.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uvp japan-dv-ai.uwvz &lt;span class="s1"&gt;'東京'&lt;/span&gt; &lt;span class="nt"&gt;-uniq&lt;/span&gt; &lt;span class="s1"&gt;'k="([^"]+)"'&lt;/span&gt; &lt;span class="nt"&gt;-sort&lt;/span&gt; count &lt;span class="nt"&gt;-head&lt;/span&gt; 20 &lt;span class="nt"&gt;-open&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuvp.y42u.net%2Fwp-content%2Fuploads%2F2026%2F09%2Fuvp-cli-open1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuvp.y42u.net%2Fwp-content%2Fuploads%2F2026%2F09%2Fuvp-cli-open1.png" alt="A uvp search and tally run in the terminal with -open at the end; after the CLI result prints, the GUI launches" width="800" height="697"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The CLI finishes the search and the tally, and &lt;strong&gt;hands the result to the GUI through a temporary file&lt;/strong&gt; (the GUI does not search again; v1.6.2)&lt;/li&gt;
&lt;li&gt;The GUI reads the same &lt;code&gt;.uwvz&lt;/code&gt;, so &lt;strong&gt;reopening does not read 3 GB&lt;/strong&gt; (reopening a 47.73 GB file takes 0.02–0.07 s from the second time on; 3 GB is less)&lt;/li&gt;
&lt;li&gt;Click a row in the list to jump to it in the text. ±N lines of context. Click a value in the tally to drill into it as the next stage&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Narrow again with another term — 0.24 s per stage&lt;/strong&gt; (measured at 50 GB; 3 GB is less)&lt;/li&gt;
&lt;li&gt;Once you know what you are after, drop back to the command line and process in bulk. &lt;strong&gt;The GUI and the CLI use the same &lt;code&gt;.uwvz&lt;/code&gt;, so moving between them costs no waiting&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuvp.y42u.net%2Fwp-content%2Fuploads%2F2026%2F09%2Fuvp-cli-open2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuvp.y42u.net%2Fwp-content%2Fuploads%2F2026%2F09%2Fuvp-cli-open2.png" alt="The GUI after receiving the CLI result: the hit list, the tally ranking and the text side by side, ready to search again with a different term" width="800" height="494"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So the thing to compare is not "one grep, 0.33 s vs 1.01 s" but &lt;strong&gt;"total time and number of actions to reach the answer"&lt;/strong&gt;. rg alone means rebuilding the command, reading the output, rebuilding again for every re-narrowing. GUI alone means a slow open. Put an index between them so the CLI and the GUI use the same thing, and &lt;strong&gt;narrow broadly with a command, look closely on screen, drop back to a command&lt;/strong&gt; connects with no waiting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The CLI and the GUI call the same functions&lt;/strong&gt; (drill-down, tally, sequence), so the count the CLI prints and the count the GUI shows always agree. That was verified byte-for-byte against rg / sed across 81 combinations plus 12 gz/zip inputs plus every 258 GB case, with zero defects on the product side.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary — four situations where you win at 3 GB
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Situation&lt;/th&gt;
&lt;th&gt;Measured at 3 GB&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;A day that ends with grep&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Warm, rg wins&lt;/strong&gt; (0.33 s vs 1.01 s). &lt;strong&gt;Cold, uvp wins&lt;/strong&gt; (3.26 s vs 1.25 s = 2.6×). &lt;code&gt;-E -v&lt;/code&gt; goes to uvp even warm (1.74 s vs 3.13 s)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;① A day you fix and save&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Replace &lt;strong&gt;3.5×&lt;/strong&gt;, extract &lt;strong&gt;12.6×&lt;/strong&gt; over sed. &lt;strong&gt;2.3×&lt;/strong&gt; on "open → 2 queries → replace and save"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;② A day you need something rg lacks&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Sequence detection, nearby co-occurrence, click-to-drill, line-number jump. No comparison exists&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;③ A day the logs are gz/zip&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Expand + rg 6.47 s vs direct &lt;strong&gt;0.68 s&lt;/strong&gt; (&lt;strong&gt;9.5×&lt;/strong&gt;). Compressing takes 1/14 of gzip's time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;④ A day you do not yet know what you are looking for&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Narrow in the CLI, look in the GUI, narrow again. &lt;strong&gt;0.24 s per stage.&lt;/strong&gt; The unit of comparison changes from "one grep" to "time to the answer"&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;"A single grep on 3 GB" is ripgrep's ground, and it can be conceded. The moment an investigation involves any one of reading, narrowing, counting, fixing or saving, it becomes uvp's ground&lt;/strong&gt; — and past 10 GB, pure search wins by 5–10× as well.&lt;/p&gt;

&lt;h2&gt;
  
  
  The tools
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;uvp&lt;/code&gt; ships with &lt;a href="https://uvp.y42u.net/en/pro-en/" rel="noopener noreferrer"&gt;UwView Pro&lt;/a&gt; v1.6.3 (Windows, macOS and Linux, one licence for all three; one-time or monthly; 14-day free trial, during which &lt;code&gt;uvp&lt;/code&gt; and editing both work). Replace (&lt;code&gt;-replace&lt;/code&gt;) is part of the Edit Upgrade. Restoring from &lt;code&gt;.uwvz&lt;/code&gt; (&lt;code&gt;-extract&lt;/code&gt;) is free. The free &lt;a href="https://github.com/amru195704/UwView" rel="noopener noreferrer"&gt;UwView&lt;/a&gt; ships &lt;code&gt;uvf&lt;/code&gt;, which does search and &lt;code&gt;-open&lt;/code&gt; only and has no index (v1.6.3 rebuilt it as a single pass: 3.32 s cold at 3 GB, level with rg).&lt;/p&gt;

&lt;p&gt;All figures are my own measurements on my own machines, not an independent benchmark. ripgrep is among the fastest there is for searching across many files, and this article does not contest that. If you spot a configuration issue, tell me; I will check and correct.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conditions
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Apple M4 / 32 GB / macOS 26.3.1 / external SSD&lt;/li&gt;
&lt;li&gt;ripgrep 15.2.0 / BSD sed / gzip and zip at level 6 / &lt;strong&gt;the cold and warm search figures are uvp 1.6.3, measured 2026-09-17&lt;/strong&gt;; replace, compression and GUI figures are uvp 1.6.2 / &lt;code&gt;sudo purge&lt;/code&gt; before every run, cooldown between runs&lt;/li&gt;
&lt;li&gt;3 GB: 3,032,812,644 bytes cut from OpenStreetMap Japan XML. The &lt;code&gt;.uwvz&lt;/code&gt; is 251,829,224 bytes (1/12.0); gzip is 300,890,910 bytes (1/10.1)&lt;/li&gt;
&lt;li&gt;The search tables assume the &lt;code&gt;.uwvz&lt;/code&gt; is already built. Including the build, the first run is 4.64 s cold (rg 3.26 s)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Everything here was measured on a Mac (Apple M4 / 32 GB / external USB SSD).&lt;/strong&gt; Windows and Linux — a low-powered laptop, under VMware — may behave differently; those are being measured separately and will get their own article&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://uvp.y42u.net/en/blog/uvp-rg-one-place-to-win-en/" rel="noopener noreferrer"&gt;Picking the one place to beat ripgrep&lt;/a&gt; — the main article, at 258 GB. This is its 3 GB companion&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://uvp.y42u.net/en/blog/uwvz-compressed-archive-search-extract-en/" rel="noopener noreferrer"&gt;Keep .uwvz instead of gzip — 1/9 the size, searchable in 7 seconds, restored for free&lt;/a&gt; — all the numbers behind ③ (3 GB / 10 GB / 50 GB)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://uvp.y42u.net/en/blog/uvp-cli-release-vs-ripgrep-en/" rel="noopener noreferrer"&gt;We shipped a uvp command — the same answers as ripgrep, 7× faster at 10 GB. And at 3 GB too, if the cache is cold&lt;/a&gt; — 3 GB / 10 GB / 50 GB tables&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://uvp.y42u.net/en/uvp-manuaru-en/" rel="noopener noreferrer"&gt;uvp command manual&lt;/a&gt; — current &lt;code&gt;-open&lt;/code&gt;, &lt;code&gt;-seq&lt;/code&gt;, &lt;code&gt;-C N&lt;/code&gt;, &lt;code&gt;-replace&lt;/code&gt;, &lt;code&gt;-extract&lt;/code&gt; behaviour&lt;/li&gt;
&lt;li&gt;&lt;a href="https://uvp.y42u.net/en/benchmarks-en/" rel="noopener noreferrer"&gt;Measured results — large-file tools compared&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ripgrep</category>
      <category>cli</category>
      <category>productivity</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Picking the One Place to Beat ripgrep — Read 1/9 of the Bytes, Then Hand the Result From the CLI to the GUI</title>
      <dc:creator>y4u</dc:creator>
      <pubDate>Wed, 16 Sep 2026 10:16:05 +0000</pubDate>
      <link>https://dev.to/amru195704/picking-the-one-place-to-beat-ripgrep-read-19-of-the-bytes-then-hand-the-result-from-the-cli-to-41fo</link>
      <guid>https://dev.to/amru195704/picking-the-one-place-to-beat-ripgrep-read-19-of-the-bytes-then-hand-the-result-from-the-cli-to-41fo</guid>
      <description>&lt;p&gt;&lt;strong&gt;You cannot out-engineer ripgrep.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;ripgrep finishes a 258 GB, 4.5-billion-line file in 278 seconds. &lt;code&gt;wc -l&lt;/code&gt; finishes the same file in 268. &lt;strong&gt;The gap is 4%&lt;/strong&gt;, and for those 278 seconds the CPU is mostly asleep. When I saw that number, I knew there was no algorithmic path to catching up. So where do you fight?&lt;/p&gt;

&lt;h2&gt;
  
  
  Up front
&lt;/h2&gt;

&lt;p&gt;I ran ripgrep and my own index-based &lt;code&gt;uvp&lt;/code&gt; against a single 258 GB, 4.5-billion-line file under the same rules (Apple M4 / 32 GB / external SSD, &lt;code&gt;sudo purge&lt;/code&gt; before every run, &lt;code&gt;/usr/bin/time -p&lt;/code&gt; real, all figures second run = index present).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ripgrep runs at 1.04× the time it takes to read the file once.&lt;/strong&gt; It does not saturate a single core (user/real = 0.04). There is no way to beat that on efficiency&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;There is exactly one place to win: the number of bytes read.&lt;/strong&gt; Index plus compression takes 258.68 GB down to 28.6 GB (1/9.04). Read 1/9 the bytes, and the time follows: 278.35 s → 35.51 s&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The first question is always a loss&lt;/strong&gt; (building the index takes 333.67 s). Break-even is at &lt;strong&gt;question 1.37&lt;/strong&gt;. At 3 GB, ripgrep wins no matter how many questions you ask&lt;/li&gt;
&lt;li&gt;One by-product. The moment you hold an index, &lt;strong&gt;the CLI result can be handed straight to the GUI and narrowed again there as many times as you like.&lt;/strong&gt; A drill-down stage costs 0.24 s (50 GB, v1.6.2), which makes that round trip practical&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I once wrote &lt;a href="https://zenn.dev/amru195704/articles/8e90789c11dc95" rel="noopener noreferrer"&gt;about picking a single axis against commercial editors&lt;/a&gt; instead of counting features. This is the CLI edition. The opponent is ripgrep, and &lt;strong&gt;the losses come first&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The premise: fight fair and you lose
&lt;/h2&gt;

&lt;p&gt;Before any comparison, the physical floor.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;/usr/bin/time &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; us-260726.osm
4509830821
real 268.73
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;268.73 s = 963 MB/s.&lt;/strong&gt; That is the time to read 258 GB once. Against that, ripgrep:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Q&lt;/th&gt;
&lt;th&gt;Term&lt;/th&gt;
&lt;th&gt;Hits&lt;/th&gt;
&lt;th&gt;&lt;code&gt;rg -c&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;New York&lt;/td&gt;
&lt;td&gt;100,492&lt;/td&gt;
&lt;td&gt;275.80&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Brooklyn&lt;/td&gt;
&lt;td&gt;30,871&lt;/td&gt;
&lt;td&gt;278.35&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Central Park&lt;/td&gt;
&lt;td&gt;14,306&lt;/td&gt;
&lt;td&gt;276.75&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Statue of Liberty&lt;/td&gt;
&lt;td&gt;88&lt;/td&gt;
&lt;td&gt;293.85&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;coffee_shop&lt;/td&gt;
&lt;td&gt;36,506&lt;/td&gt;
&lt;td&gt;346.21&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;median&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;278.35&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;1.04× the physical floor.&lt;/strong&gt; Same for 88 hits or 100,000. ripgrep is a tool that "finishes when it has finished reading", and the reading speed is set by the disk.&lt;/p&gt;

&lt;p&gt;The CPU numbers make it sharper.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rg -c 'New York'   real 275.80   user 11.55   sys 51.60
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Of 275 seconds, 11 were spent on the CPU.&lt;/strong&gt; On a 10-core M4, that is 4% of one core. It does not need more — the disk cannot keep up.&lt;/p&gt;

&lt;p&gt;That is the starting point. &lt;strong&gt;In an I/O-bound world, a faster CPU changes nothing, and neither does parallelism.&lt;/strong&gt; Polish the algorithm all you like; the 268.73-second wall does not move.&lt;/p&gt;

&lt;p&gt;(A side note: this is also why "written in Rust, therefore fast" is not a general rule. amber, which parallelises within a single file, takes 357.69 s on the same file — slower than ripgrep. Burning 25–36× the CPU time does not help when there is one disk. Measurements are in &lt;a href="https://uvp.y42u.net/en/blog/uvp-258gb-cli-vs-gui-en/" rel="noopener noreferrer"&gt;the 258 GB article&lt;/a&gt;.)&lt;/p&gt;

&lt;h2&gt;
  
  
  The one move left: read fewer bytes
&lt;/h2&gt;

&lt;p&gt;If the disk is the ceiling, &lt;strong&gt;the only lever is how much you read.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;uvp&lt;/code&gt; builds an index and a compressed copy first.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;/usr/bin/time &lt;span class="nt"&gt;-p&lt;/span&gt; uvp convert us-260726.osm
real 333.67
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;333.67 s — 1.24× the physical floor&lt;/strong&gt; — to read 258 GB once and produce a &lt;code&gt;.uwvz&lt;/code&gt;. The result is &lt;strong&gt;28,608,409,953 bytes = 1/9.04 of the original&lt;/strong&gt;. Every later search reads only those 28.6 GB. The original 258 GB can be deleted.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Bytes read&lt;/th&gt;
&lt;th&gt;Floor at 963 MB/s&lt;/th&gt;
&lt;th&gt;Measured (median)&lt;/th&gt;
&lt;th&gt;Ratio to floor&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;rg -c&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;258.68 GB&lt;/td&gt;
&lt;td&gt;268.73 s&lt;/td&gt;
&lt;td&gt;278.35 s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1.04×&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;uvp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;28.6 GB&lt;/td&gt;
&lt;td&gt;29.72 s&lt;/td&gt;
&lt;td&gt;35.51 s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1.20×&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Both are pinned to their own I/O floor.&lt;/strong&gt; The only difference is how many bytes each must read. And &lt;strong&gt;per byte, ripgrep is the more efficient one&lt;/strong&gt; (1.04 versus 1.20). What wins is not the algorithm; it is the prepayment.&lt;/p&gt;

&lt;p&gt;The CPU picture flips too.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rg   real 275.80   user  11.55   → user/real = 0.04 (waiting on I/O)
uvp  real  34.70   user 168.63   → user/real = 4.86 (~5 cores decompressing and searching)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The moment you read 1/9 the bytes, the disk has slack and the CPU becomes the bottleneck. &lt;strong&gt;The bound flips&lt;/strong&gt; — that is what this approach really is.&lt;/p&gt;

&lt;h2&gt;
  
  
  The table of losses
&lt;/h2&gt;

&lt;p&gt;Pick one axis and you lose on the others. All of them, up front.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Situation&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;First question&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;ripgrep 278.4 s vs uvp 369.3 s (&lt;strong&gt;1/1.33&lt;/strong&gt;)&lt;/td&gt;
&lt;td&gt;You pay 333.67 s for the index. &lt;strong&gt;A consistent 1.2–1.3× loss at every size&lt;/strong&gt; (3 GB 1/1.18, 10 GB 1/1.31, 50 GB 1/1.29)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;3 GB&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;ripgrep faster from the second question on too (&lt;strong&gt;1/2.3&lt;/strong&gt;)&lt;/td&gt;
&lt;td&gt;The file sits in the OS cache and rg scans it in 0.33 s. uvp always pays 0.6–0.7 s of startup plus index load. &lt;strong&gt;Absolute gap: 0.4 s&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;-head N&lt;/code&gt; (first few only)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;`rg \&lt;/td&gt;
&lt;td&gt;head` 0.05 s vs uvp 0.32 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Efficiency per byte&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;rg 1.04× vs uvp 1.20×&lt;/td&gt;
&lt;td&gt;As above. Decompression costs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Drill-down with context (&lt;code&gt;-C N&lt;/code&gt;)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~8.5 s per stage (50 GB)&lt;/td&gt;
&lt;td&gt;It looks outside the previous hit lines, so it re-reads the &lt;code&gt;.uwvz&lt;/code&gt;. At ±0 it is 0.24 s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;3 GB is ripgrep's ground, and I concede it.&lt;/strong&gt; The gap is 0.4 s, so conceding costs nothing. The index starts to matter once the file no longer fits in memory.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which question pays for the index
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CLI(N) = N × 278.35            (rg, median of 5 questions)
uvp(N) = 333.67 + N × 35.58    (index build + mean .uwvz search)

N* = 333.67 ÷ (278.35 − 35.58) = 1.37
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Questions N&lt;/th&gt;
&lt;th&gt;ripgrep&lt;/th&gt;
&lt;th&gt;uvp&lt;/th&gt;
&lt;th&gt;rg ÷ uvp&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;278.4&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;369.3&lt;/td&gt;
&lt;td&gt;1/1.33 (ripgrep wins)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;556.7&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;404.8&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1.38×&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;1,391.8&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;511.6&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2.72×&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;5,567.0&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1,045.4&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;5.33×&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;It flips at the second question.&lt;/strong&gt; The formula applies to any index-based approach (&lt;code&gt;T_index ÷ (T_scan − T_search)&lt;/code&gt;). 10 GB and 50 GB show the same shape: from the second run on, 5.5–6.9× at 10 GB and 7.3–10.2× at 50 GB.&lt;/p&gt;

&lt;p&gt;That is the whole "speed" story. Honestly, on its own it would barely merit an article. &lt;strong&gt;Of course the second search is faster once you have built an index.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The by-product: the CLI and the GUI became one surface
&lt;/h2&gt;

&lt;p&gt;The prepayment bought something I had not planned for.&lt;/p&gt;

&lt;p&gt;Put &lt;code&gt;-open&lt;/code&gt; at the end of a &lt;code&gt;uvp&lt;/code&gt; command and &lt;strong&gt;the CLI's result is handed straight to the GUI.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uvp us-260726.osm.uwvz &lt;span class="s1"&gt;'New York'&lt;/span&gt; &lt;span class="nt"&gt;-C&lt;/span&gt; 0 &lt;span class="nt"&gt;-grep&lt;/span&gt; hospital &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="nt"&gt;-grep&lt;/span&gt; school &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="nt"&gt;-grep&lt;/span&gt; Brooklyn &lt;span class="nt"&gt;-open&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuvp.y42u.net%2Fwp-content%2Fuploads%2F2026%2F09%2Fuvp-cli-open1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuvp.y42u.net%2Fwp-content%2Fuploads%2F2026%2F09%2Fuvp-cli-open1.png" alt="A uvp drill-down and tally run in the terminal with -open at the end; after the CLI result prints, the GUI launches" width="800" height="697"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Step by step:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The CLI searches the 258 GB file (via its 28.6 GB index), narrows through three stages to 35 lines — &lt;strong&gt;34.88 s&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Those 35 lines are &lt;strong&gt;handed to the GUI through a temporary file&lt;/strong&gt;. The GUI does not search again (v1.6.2; until then only the conditions were passed and the GUI re-ran the search, so on a huge file you waited for the same search twice)&lt;/li&gt;
&lt;li&gt;The GUI opens. &lt;strong&gt;The index already exists, so reopening does not read 258 GB&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Click a row in the list to jump to it in the text. Show ±N lines of context. &lt;strong&gt;Narrow again with another term — that costs 0.24 s per stage&lt;/strong&gt; (v1.6.2)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuvp.y42u.net%2Fwp-content%2Fuploads%2F2026%2F09%2Fuvp-cli-open2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuvp.y42u.net%2Fwp-content%2Fuploads%2F2026%2F09%2Fuvp-cli-open2.png" alt="The GUI after receiving the CLI result: the hit list, the tally ranking and the text side by side, ready to search again with a different term" width="800" height="494"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Step 4 is the point. In the interest of honesty: there was a period when &lt;strong&gt;only the drill-down was oddly slow&lt;/strong&gt; in the comparison against rg. Single searches ran 7–9× faster, yet stacked drill-down stages managed only 3–4×. It turned out one code path was re-reading per stage; v1.6.2 fixes it. At 50 GB, three stages went from &lt;strong&gt;29.46 s to 8.10 s&lt;/strong&gt; — 0.24 s per stage. Three stages on 258 GB now take 34.88 s, the same as a single search (34.85 s). The cause and how it was isolated will get their own article.&lt;/p&gt;

&lt;p&gt;With that, the following workflow finally holds together:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Narrow in a script, look at it as a person, and narrow again while looking.&lt;/strong&gt;&lt;br&gt;
Not waiting 278 s for every re-narrowing (CLI only), not re-reading 258 GB every time the GUI opens (GUI only) — &lt;strong&gt;build the index once, and let both the CLI and the GUI use it.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A grep pipeline is "one pass no matter how many stages", and that is the right design for a CLI. But &lt;strong&gt;change the condition and try again&lt;/strong&gt; and it is another full pass. The GUI is quick to change conditions but slow to open. Put an index in between and both of those "slow"s disappear.&lt;/p&gt;

&lt;p&gt;Because the CLI and the GUI call the same functions (drill-down, tally, sequence), &lt;strong&gt;the count the CLI prints and the count the GUI shows always agree.&lt;/strong&gt; Verification: 81 command combinations plus 12 gz/zip inputs plus every 258 GB case were checked byte-for-byte against rg / sed, with zero defects on the product side.&lt;/p&gt;

&lt;h2&gt;
  
  
  What was done, in short
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ripgrep cannot be beaten on efficiency.&lt;/strong&gt; 1.04× the physical floor, 4% CPU. It is finished&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The only place to win was "bytes read".&lt;/strong&gt; Read 1/9 and you get 1/8 the time — no more, no less&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The first question is always a loss.&lt;/strong&gt; Break-even at question 1.37. At 3 GB you lose every question. That ground is conceded&lt;/li&gt;
&lt;li&gt;As a by-product of the prepayment, &lt;strong&gt;the CLI and the GUI share one index and pass results between them&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;The one place I picked is not "fast" but "&lt;strong&gt;inside a single investigation, moving between CLI and GUI without waiting&lt;/strong&gt;"&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The tools
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;uvp&lt;/code&gt; ships with &lt;a href="https://uvp.y42u.net/en/pro-en/" rel="noopener noreferrer"&gt;UwView Pro&lt;/a&gt; v1.6.2 (Windows, macOS and Linux, one licence for all three; one-time or monthly; 14-day free trial, and &lt;code&gt;uvp&lt;/code&gt; works during the trial). The free &lt;a href="https://github.com/amru195704/UwView" rel="noopener noreferrer"&gt;UwView&lt;/a&gt; ships &lt;code&gt;uvf&lt;/code&gt;, which has no index — search and &lt;code&gt;-open&lt;/code&gt; only, and no speed to speak of.&lt;/p&gt;

&lt;p&gt;All figures are my own measurements on my own machines, not an independent benchmark. ripgrep is among the fastest there is for searching across many files, and this article does not contest that. It is only about &lt;strong&gt;asking a gigantic single file many questions&lt;/strong&gt;. If you spot a configuration issue, tell me; I will check and correct.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conditions and commands
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Apple M4 / 32 GB / macOS 26.3.1 / external SSD (963 MB/s)&lt;/li&gt;
&lt;li&gt;ripgrep 15.2.0 / uvp 1.6.1–1.6.2 / &lt;code&gt;sudo purge&lt;/code&gt; before every run, 150 s (258 GB) or 60 s (50 GB) cooldown between runs&lt;/li&gt;
&lt;li&gt;OpenStreetMap USA XML 258,679,440,228 bytes / 4,509,830,821 lines; OpenStreetMap Japan 51,254,526,392 bytes
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/usr/bin/time &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; us-260726.osm                       &lt;span class="c"&gt;# physical floor&lt;/span&gt;
&lt;span class="nv"&gt;LC_ALL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;C rg &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s1"&gt;'New York'&lt;/span&gt; us-260726.osm                 &lt;span class="c"&gt;# rg&lt;/span&gt;
uvp convert us-260726.osm                                  &lt;span class="c"&gt;# build the .uwvz index&lt;/span&gt;
uvp us-260726.osm.uwvz &lt;span class="s1"&gt;'New York'&lt;/span&gt; &lt;span class="nt"&gt;--no-line-number&lt;/span&gt;         &lt;span class="c"&gt;# uvp (second run)&lt;/span&gt;
uvp us-260726.osm.uwvz &lt;span class="s1"&gt;'New York'&lt;/span&gt; &lt;span class="nt"&gt;-C&lt;/span&gt; 0 &lt;span class="nt"&gt;-grep&lt;/span&gt; hospital &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="nt"&gt;-grep&lt;/span&gt; school &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="nt"&gt;-grep&lt;/span&gt; Brooklyn &lt;span class="nt"&gt;-open&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://uvp.y42u.net/en/blog/uvp-258gb-cli-vs-gui-en/" rel="noopener noreferrer"&gt;Five questions against 258 GB and 4.5 billion lines — where grep loses to a GUI&lt;/a&gt; — where these numbers come from&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://uvp.y42u.net/en/blog/uvp-cli-release-vs-ripgrep-en/" rel="noopener noreferrer"&gt;We shipped a uvp command — the same answers as ripgrep, 6× faster at 10 GB. And slower at 3 GB&lt;/a&gt; — 3 GB / 10 GB / 50 GB&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://uvp.y42u.net/en/uvp-manuaru-en/" rel="noopener noreferrer"&gt;uvp command manual&lt;/a&gt; — current &lt;code&gt;-open&lt;/code&gt;, &lt;code&gt;-head&lt;/code&gt;, &lt;code&gt;-limit&lt;/code&gt; behaviour&lt;/li&gt;
&lt;li&gt;&lt;a href="https://uvp.y42u.net/en/benchmarks-en/" rel="noopener noreferrer"&gt;Measured results — large-file tools compared&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ripgrep</category>
      <category>cli</category>
      <category>performance</category>
      <category>showdev</category>
    </item>
    <item>
      <title>The Morning the Monthly Batch Died — Four Realities of Running a Data Pipeline</title>
      <dc:creator>y4u</dc:creator>
      <pubDate>Tue, 15 Sep 2026 16:12:08 +0000</pubDate>
      <link>https://dev.to/amru195704/the-morning-the-monthly-batch-died-four-realities-of-running-a-data-pipeline-144e</link>
      <guid>https://dev.to/amru195704/the-morning-the-monthly-batch-died-four-realities-of-running-a-data-pipeline-144e</guid>
      <description>&lt;p&gt;&lt;code&gt;line 48,213,996&lt;/code&gt;. That's all you got.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can you look at that line right now?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The pipeline hands you a coordinate. The thing it points at is somewhere else entirely.&lt;/p&gt;

&lt;p&gt;Pipeline failures don't behave like application failures. What breaks isn't your code — it's the &lt;strong&gt;input data&lt;/strong&gt;, produced outside your control, and the evidence is one line inside tens of gigabytes. Four scenes here — reading around the line that died, hunting the odd rows in a training corpus, reopening the same monthly input every month, and looking at the original before you transform it — each starting from what actually jams.&lt;/p&gt;

&lt;h2&gt;
  
  
  Up front: In UwView Pro you type the line number and land on 48,213,996 — and you land in the &lt;strong&gt;original&lt;/strong&gt;, not an extracted fragment, so you can widen the context as far as you like without re-reading the file. The index and the compression are saved to a sidecar, so next month the same input file reopens &lt;strong&gt;with line numbers still attached&lt;/strong&gt; in 0.02–0.07 s (measured on a 47.73 GB text file; one specific setup, results vary). When you move on to fixing things, the original stays byte-identical: edits accumulate in a diff file, and you can stop halfway and resume tomorrow (details at the end)
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;This article is about reading the raw data on either side of a pipeline, on your own machine. What you may handle, move, or retain is governed by your organisation's policy and your client's.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  1. All that's left is a line number
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Situation
&lt;/h3&gt;

&lt;p&gt;The monthly batch dies. The alert is waiting for you in the morning.&lt;/p&gt;

&lt;p&gt;You open the log. There's one line in it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ERROR: failed to parse record at line 48213996: unexpected field count (expected 12, got 13)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That looks like enough. A line number, an expectation, an actual. &lt;strong&gt;And you still can't fix it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Why there are thirteen fields is not something you can determine without seeing the line. A delimiter inside the data, an unclosed quote, a missing newline that welded two records together — all of these happen, and which one happened is decided by the bytes, not by reasoning.&lt;/p&gt;

&lt;p&gt;The input file is 31 GB.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why it happens
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;A parser is not built to report a broken line. It's built to stop.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;What it can tell you is where it stopped and how reality differed from its expectation. &lt;em&gt;Why&lt;/em&gt; the count became thirteen lies outside its concerns entirely.&lt;/p&gt;

&lt;p&gt;Worse, &lt;strong&gt;the reported line number is not necessarily the line you want&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;An unclosed quote&lt;/strong&gt; makes the parser swallow dozens of lines as one record. The error surfaces where it finished swallowing; the cause is dozens of lines earlier&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mixed line endings&lt;/strong&gt; make the parser's idea of a line differ from your editor's (&lt;a href="https://uvp.y42u.net/en/blog/uwview-ps13-huge-data-inspection-en/" rel="noopener noreferrer"&gt;part 13&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Header-skipping implementations&lt;/strong&gt; report a number that's off by one&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Parallel readers&lt;/strong&gt; sometimes report a position relative to a chunk, not the file&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So a line number is &lt;strong&gt;where you start looking&lt;/strong&gt;, not where the answer is. Which is why "extract just that line" doesn't get you there — you need to be able to &lt;strong&gt;widen&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  General tools, and where they stop
&lt;/h3&gt;

&lt;p&gt;You go look at the line.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s1"&gt;'48213976,48214016p'&lt;/span&gt; input.tsv

&lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s1"&gt;'48213976,48214016p;48214017q'&lt;/span&gt; input.tsv

&lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="nt"&gt;-F&lt;/span&gt;&lt;span class="s1"&gt;'\t'&lt;/span&gt; &lt;span class="s1"&gt;'NF!=12 {print NR": "NF}'&lt;/span&gt; input.tsv | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-50&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That third command is the one that matters. &lt;strong&gt;Whether there is one broken line or four thousand changes the entire response&lt;/strong&gt; — one you fix by hand, four thousand means you go argue with whoever generated the file.&lt;/p&gt;

&lt;p&gt;Three limits.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First, &lt;code&gt;sed&lt;/code&gt; makes you choose the range before you know what you need.&lt;/strong&gt; You look at ±20, realise the cause is further back, and reissue with ±200. Each attempt counts 48,213,976 lines from the top of a 31 GB file. Every wrong guess is a full pass.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Second, the original coordinates vanish the moment you excerpt.&lt;/strong&gt; Line 1 of that &lt;code&gt;sed&lt;/code&gt; output is line 48,213,976 of the original, and that mapping now exists only in your head. Find something suspicious inside the excerpt and you're doing arithmetic to say where it really lives. What goes in the handover document is only as good as that arithmetic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Third, &lt;code&gt;awk&lt;/code&gt; over the whole file means one pass per idea.&lt;/strong&gt; Check field counts; then quote counts; then line lengths. Every new way of looking costs another 31 GB read. &lt;strong&gt;Your investigation runs at the speed of I/O, not at the speed of thought&lt;/strong&gt; (&lt;a href="https://uvp.y42u.net/en/blog/uwview-ps15-cli-craft-limits-en/" rel="noopener noreferrer"&gt;part 15&lt;/a&gt;).&lt;/p&gt;




&lt;h2&gt;
  
  
  2. The odd rows in a 50 GB corpus
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Situation
&lt;/h3&gt;

&lt;p&gt;You're assembling training data. The concatenated corpus is 50 GB.&lt;/p&gt;

&lt;p&gt;You write the preprocessing, kick off a run. Hours later the loss curve has a shape it shouldn't. Or the outputs contain strings you don't recognise.&lt;/p&gt;

&lt;p&gt;The cause is somewhere in the data. &lt;strong&gt;But "somewhere" isn't something the training side can localise for you.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Why it happens
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Because you cannot state the criterion in advance.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The pipeline failure in part 1 had an explicit one: twelve fields. Corpus validation has none. You're looking for "odd rows", and &lt;em&gt;odd&lt;/em&gt; gets defined after you see them.&lt;/p&gt;

&lt;p&gt;List what actually turns up and the impossibility of enumerating it ahead of time becomes obvious.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Mass-duplicated lines&lt;/strong&gt; — a crawler's error page, tens of thousands of times, byte-identical&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HTML and JSON debris&lt;/strong&gt; — tags the extractor missed, un-unescaped &lt;code&gt;&amp;amp;amp;&lt;/code&gt;, raw script tags&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One absurdly long line&lt;/strong&gt; — a Base64-embedded image arriving as a single multi-megabyte row&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Encoding accidents&lt;/strong&gt; — one source concatenated in Shift_JIS, read back as UTF-8 (&lt;a href="https://uvp.y42u.net/en/blog/uwview-ps03-japanese-encoding-traps-en/" rel="noopener noreferrer"&gt;part 3&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Personal data that shouldn't be there&lt;/strong&gt; — not a matter of counts; one occurrence is one too many&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last one is what makes "sampling is fine" indefensible. &lt;strong&gt;Ten thousand clean sampled rows tell you nothing about the rest.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  General tools, and where they stop
&lt;/h3&gt;

&lt;p&gt;Start with distributions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'{print length}'&lt;/span&gt; corpus.txt | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; | &lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-20&lt;/span&gt;

&lt;span class="nb"&gt;sort &lt;/span&gt;corpus.txt | &lt;span class="nb"&gt;uniq&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-rn&lt;/span&gt; | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-20&lt;/span&gt;

&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-cP&lt;/span&gt; &lt;span class="s1"&gt;'[\x00-\x08\x0B\x0C\x0E-\x1F]'&lt;/span&gt; corpus.txt

&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-cE&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;/?(script|div|span|br)\b'&lt;/span&gt; corpus.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Line length is the cheapest signal. &lt;strong&gt;A multi-megabyte line is itself the evidence.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Three limits.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First, &lt;code&gt;sort&lt;/code&gt; does not survive 50 GB gracefully.&lt;/strong&gt; Counting exact duplicates the honest way means sorting the file, which wants temp space on the order of the file itself plus the time to match. &lt;code&gt;--parallel&lt;/code&gt; and &lt;code&gt;-T&lt;/code&gt; help, but &lt;strong&gt;this is no longer in the "let me just check something" weight class&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Second, a count doesn't give you the thing.&lt;/strong&gt; &lt;code&gt;grep -c&lt;/code&gt; says 1,247. Whether those 1,247 are one problem or seven different problems is decided by looking. You can &lt;code&gt;grep&lt;/code&gt; them out, but extraction erases their position in the corpus — and &lt;strong&gt;"a cluster from one source" versus "scattered throughout" is a judgement you can only make with position in hand&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Third, validation is a round trip.&lt;/strong&gt; Find an anomaly, fix the preprocessing, rebuild, validate again. If one lap is forty minutes, every lap is forty minutes. &lt;strong&gt;The lap count is why corpus work never seems to end.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Reopening the same 31 GB every month
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Situation
&lt;/h3&gt;

&lt;p&gt;The monthly batch input is a large file with roughly the same shape every time.&lt;/p&gt;

&lt;p&gt;You opened it last month. And the month before. Each time you do the same things: check the header, count records, look at the date range, search for the failure patterns you already know about.&lt;/p&gt;

&lt;p&gt;The work itself takes thirty minutes. But &lt;strong&gt;opening takes several minutes&lt;/strong&gt;, and each search costs tens of seconds. Every month, the same wait.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why it happens
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Because the wait is being accounted for as a one-time cost.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Three minutes is tolerable in the moment — it's a coffee. The problem is that it compounds across twelve months and however many people do this, and, more fundamentally, that &lt;strong&gt;a three-minute reopen makes people check less&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"While I'm here, let me also look at…" stops happening. Reopening is expensive, so you try to do it all in one go&lt;/li&gt;
&lt;li&gt;You think of a search after closing the file, and you don't run it&lt;/li&gt;
&lt;li&gt;The net effect: &lt;strong&gt;the thoroughness of your checking silently adjusts itself to the wait time&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And one more thing. Monthly work carries &lt;strong&gt;no record of what last month's you actually checked&lt;/strong&gt;. You rebuild the same queries from memory. Each rebuild differs slightly, and comparability quietly disappears — the same failure mode as an investigation procedure that lives only in one person's head.&lt;/p&gt;

&lt;h3&gt;
  
  
  General tools, and where they stop
&lt;/h3&gt;

&lt;p&gt;It's routine work, so scripting it is the right instinct.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/sh&lt;/span&gt;
&lt;span class="nv"&gt;f&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"== header =="&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"== lines  =="&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; &amp;lt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"== date range =="&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-2&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt; | &lt;span class="nb"&gt;cut&lt;/span&gt; &lt;span class="nt"&gt;-f1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;cut&lt;/span&gt; &lt;span class="nt"&gt;-f1&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"== known bad patterns =="&lt;/span&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-cE&lt;/span&gt; &lt;span class="s1"&gt;'\t\t|^\t|\t$'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;        &lt;span class="c"&gt;# empty fields&lt;/span&gt;
&lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="nt"&gt;-F&lt;/span&gt;&lt;span class="s1"&gt;'\t'&lt;/span&gt; &lt;span class="s1"&gt;'NF!=12'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt;    &lt;span class="c"&gt;# field count anomalies&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the checks are fixed and comparable month to month. &lt;strong&gt;Writing the checklist down is worth something on its own.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Two limits.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First, a script only does the checks you decided on.&lt;/strong&gt; When a number differs from last month, what happens next is not in the script. From there you open the file and look — and the wait arrives exactly there. Scripting removes the routine part only.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Second, the read-from-scratch structure is untouched.&lt;/strong&gt; The script above walks 31 GB three times: once for &lt;code&gt;wc -l&lt;/code&gt;, once for &lt;code&gt;grep -c&lt;/code&gt;, once for &lt;code&gt;awk&lt;/code&gt;. It did the same last month. &lt;strong&gt;Nothing from the previous read survives anywhere.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Look at the original before you transform it
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Situation
&lt;/h3&gt;

&lt;p&gt;A partner sends you 258 GB of XML to ingest.&lt;/p&gt;

&lt;p&gt;There's a spec. There's a schema. You write the transform, put it on the pipeline. It runs for hours and dies. Or it completes and &lt;strong&gt;the record count doesn't match what you expected&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Either way the next step is the same. &lt;strong&gt;You go look at the original.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And 258 GB of XML doesn't open in most editors.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why it happens
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Because a spec describes what should be, and the file describes what happened.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Between organisations, those two diverge essentially always.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tags not in the spec&lt;/strong&gt; — the sender extended something and your copy of the spec predates it&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Different handling of absence&lt;/strong&gt; — empty element or no element at all. The spec is silent; the parser is not&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Character references and escaping&lt;/strong&gt; — &lt;code&gt;&amp;amp;amp;amp;&lt;/code&gt; is a fossil of a double-escaping step upstream&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The format changes mid-file&lt;/strong&gt; — receive several years in one delivery and you may be crossing a system replacement on the sender's side&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A truncated tail&lt;/strong&gt; — a transfer that stopped halfway looks perfectly fine at the head&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last one is the emblem of the whole problem. &lt;strong&gt;A defect you could see by glancing at the end of the file stays invisible because you can't open the file.&lt;/strong&gt; You find out hours into the transform instead.&lt;/p&gt;

&lt;h3&gt;
  
  
  General tools, and where they stop
&lt;/h3&gt;

&lt;p&gt;Inspect without opening.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; 2000 huge.xml
&lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; 2000 huge.xml

&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-oE&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;[a-zA-Z_][a-zA-Z0-9_:.-]*'&lt;/span&gt; huge.xml | &lt;span class="nb"&gt;sort&lt;/span&gt; | &lt;span class="nb"&gt;uniq&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-rn&lt;/span&gt; | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-30&lt;/span&gt;

xmllint &lt;span class="nt"&gt;--noout&lt;/span&gt; &lt;span class="nt"&gt;--stream&lt;/span&gt; huge.xml

&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt; 5 &lt;span class="nt"&gt;-A&lt;/span&gt; 10 &lt;span class="s1"&gt;'&amp;lt;UnexpectedTag'&lt;/span&gt; huge.xml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;tail -c&lt;/code&gt; is cheap and decisive. &lt;strong&gt;If the file doesn't end in a closing tag, the investigation is over&lt;/strong&gt; and you're asking for a resend.&lt;/p&gt;

&lt;p&gt;Three limits.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First, &lt;code&gt;grep&lt;/code&gt; doesn't know XML.&lt;/strong&gt; You can count tag names, but nesting depth and parentage are outside its world. "Which record is this tag inside?" needs either a structure-aware tool or a pair of eyes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Second, &lt;code&gt;xmllint --stream&lt;/code&gt; stops at the first error.&lt;/strong&gt; A hundred defects report as one. Fix, rerun, learn about the next one — a hundred times, at tens of minutes a lap.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Third, the going-and-looking round trip is heavy.&lt;/strong&gt; You're told line 18 million, you look, it isn't enough, you look earlier, then earlier still. &lt;strong&gt;Whether that loop is cheap or expensive is decided entirely by whether the original opens.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What the four had in common
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scene&lt;/th&gt;
&lt;th&gt;What you're given&lt;/th&gt;
&lt;th&gt;Where it jams&lt;/th&gt;
&lt;th&gt;General-tool approach&lt;/th&gt;
&lt;th&gt;What's left over&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Batch died&lt;/td&gt;
&lt;td&gt;One line number&lt;/td&gt;
&lt;td&gt;Reported position ≠ cause position&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;sed&lt;/code&gt; to excerpt ±N&lt;/td&gt;
&lt;td&gt;Range must be chosen first; excerpting kills coordinates&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Corpus validation&lt;/td&gt;
&lt;td&gt;No criterion&lt;/td&gt;
&lt;td&gt;"Odd" gets defined by looking&lt;/td&gt;
&lt;td&gt;Length / duplicate / control-char distributions&lt;/td&gt;
&lt;td&gt;Counts without the rows or their positions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Monthly input check&lt;/td&gt;
&lt;td&gt;Last month's procedure&lt;/td&gt;
&lt;td&gt;Checking thins out to fit the wait&lt;/td&gt;
&lt;td&gt;Freeze the checks in a script&lt;/td&gt;
&lt;td&gt;Re-read from zero; nothing carries over&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pre-transform check&lt;/td&gt;
&lt;td&gt;A spec&lt;/td&gt;
&lt;td&gt;Spec and file diverge&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;head&lt;/code&gt; / &lt;code&gt;tail&lt;/code&gt; / tag inventory&lt;/td&gt;
&lt;td&gt;Heavy round trips; one error at a time&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Four different stages, four different people. The right-hand column rhymes anyway, because &lt;strong&gt;all four require seeing a specific place in the actual file, with its context — and that is exactly the expensive part.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In a pipeline, data is something that passes through. Read, transformed, written. A human looking at the bytes is the exceptional case — and so &lt;strong&gt;nobody has tooling for the exceptional case&lt;/strong&gt;. When it dies, you improvise a window out of &lt;code&gt;sed&lt;/code&gt; and &lt;code&gt;awk&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Three conditions, then.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Jump by line number, and land in the original&lt;/strong&gt; — with an excerpt, widening the context means rebuilding it&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Make the second open cheap&lt;/strong&gt; — neither validation nor monthly checking is a one-shot job. If you can't reduce the number of laps, reduce the cost of a lap&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep the original when you start fixing&lt;/strong&gt; — migration and data repair need the baseline available to the very end&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Back to &lt;code&gt;line 48,213,996&lt;/code&gt;. The line number wasn't insufficient. &lt;strong&gt;The route to that line just stopped at the edge of 31 GB.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The tool I use
&lt;/h2&gt;

&lt;p&gt;I build &lt;a href="https://uvp.y42u.net/en/" rel="noopener noreferrer"&gt;UwView&lt;/a&gt; (free), a viewer that makes huge text &lt;strong&gt;readable, scrollable and searchable from the moment it opens&lt;/strong&gt;. It doesn't pull the file into memory, so files larger than RAM open fine. Indexing runs in the background and line numbers appear when it completes (most viewers show you only the head until indexing is done). It never splits or extracts, so the original stays one file, unmodified.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Jump by line number.&lt;/strong&gt; Type 48,213,996 and you're there — in the &lt;strong&gt;original&lt;/strong&gt;, so you widen the context as far as you want. No choosing a range up front, and a wrong guess costs nothing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reach the tail in one move.&lt;/strong&gt; Part 4's "does it end in a closing tag" is answerable on 258 GB right after opening — and unlike &lt;code&gt;tail -c&lt;/code&gt;, you can walk back upward from there&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Switch encodings without reopening&lt;/strong&gt; (UTF-8 / Shift-JIS (CP932) / EUC-JP / UTF-16, auto-detected). Part 2's Shift_JIS contamination is confirmable without producing a converted copy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Beyond that is &lt;a href="https://uvp.y42u.net/en/pro-en/" rel="noopener noreferrer"&gt;UwView Pro&lt;/a&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The index and compression are saved.&lt;/strong&gt; This is part 3, directly. From the second open onward the file comes back &lt;strong&gt;with line numbers, instantly&lt;/strong&gt; (0.02–0.07 s measured on a 47.73 GB text file; one specific setup, results vary). "Reopening is expensive, so I'll check less" stops being a decision you have to make&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Save the queries, resume tomorrow.&lt;/strong&gt; Against part 3's rebuilt-from-memory searches, the conditions themselves become an artefact. Sessions restore, so monthly work starts as a continuation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Drill-down search.&lt;/strong&gt; Narrow a result set further, again. Part 2's "are those 1,247 one problem or seven?" gets sorted on screen without producing extract files. Tabs carry &lt;code&gt;term (count)&lt;/code&gt; and &lt;strong&gt;the original line numbers survive to the last stage&lt;/strong&gt; (&lt;a href="https://uvp.y42u.net/en/blog/uvp-drilldown-search-en/" rel="noopener noreferrer"&gt;drill-down article&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tally (frequency ranking).&lt;/strong&gt; Count occurrences captured by a regex and click a row to descend to it — close to part 2's distribution work, without walking the file once per idea. It does &lt;strong&gt;not&lt;/strong&gt; sum or average; the lineage is &lt;code&gt;grep -oE | sort | uniq -c&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Store at roughly 1/9 and still search it.&lt;/strong&gt; Keeping twelve monthly inputs a year becomes compatible with rechecking them (&lt;a href="https://uvp.y42u.net/en/blog/uwview-ps08-compressed-archive-search-en/" rel="noopener noreferrer"&gt;part 8&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And the part this series kept flagging — "once you've found it, fixing it means rewriting the original" — got an answer in v1.4.0 with &lt;strong&gt;Edit Upgrade&lt;/strong&gt;. It lands squarely on migration and data-repair work.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The original is never rewritten.&lt;/strong&gt; Edits and bulk replacements accumulate in a diff file (&lt;code&gt;.ewvz&lt;/code&gt;). Fixing the broken row from part 1 leaves the baseline intact to the end&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Repair cost doesn't scale with file size.&lt;/strong&gt; In development measurement, 21,994 replacements across a 10 GB, 100-million-line file took 16.8 s (&lt;a href="https://uvp.y42u.net/en/blog/uep-100m-lines-replace-all-en/" rel="noopener noreferrer"&gt;measurement article&lt;/a&gt;; macOS, external SSD, &lt;code&gt;.uwvz&lt;/code&gt;-backed file, single run, results vary)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stop halfway, resume tomorrow.&lt;/strong&gt; Pause and continue without resaving the whole file — the property that multi-day work like part 2's corpus validation actually needs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Stated plainly: &lt;strong&gt;UwView is neither a validator nor an ETL tool.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Part 1's "pull every row with the wrong field count" is &lt;code&gt;awk&lt;/code&gt;'s job. This tool's turn comes &lt;strong&gt;after&lt;/strong&gt; — putting the resulting line number onto the real thing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It does not understand CSV or XML structure.&lt;/strong&gt; No sorting by column name, no type checking, no schema inference, no nesting validation. Expect a "huge CSV editor" or an "XML editor" and you'll be disappointed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tally has no sum or average.&lt;/strong&gt; Counting part 2's duplicates is &lt;code&gt;sort&lt;/code&gt; and &lt;code&gt;uniq&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;It does not run, schedule, retry, or monitor pipelines&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Edit Upgrade operates on &lt;code&gt;.uwvz&lt;/code&gt;-backed files&lt;/strong&gt;, not on a raw CSV in place — you convert first, and it works as an &lt;strong&gt;add-on to a View License&lt;/strong&gt; (it does not run standalone)&lt;/li&gt;
&lt;li&gt;It handles text. Parquet and binary database dumps are out of scope&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The same things, from the command line
&lt;/h3&gt;

&lt;p&gt;v1.6.0 added a &lt;code&gt;uvp&lt;/code&gt; command (and &lt;code&gt;uvf&lt;/code&gt; for the free build). It uses &lt;strong&gt;the same &lt;code&gt;.uwvz&lt;/code&gt; as the GUI&lt;/strong&gt;, so an index built from the shell is already there when you open the file in the app. Mapped onto this article's four parts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uvp input.tsv &lt;span class="s1"&gt;'order_id=A1B2C3'&lt;/span&gt; &lt;span class="nt"&gt;-C&lt;/span&gt; 40

uvp input.tsv &lt;span class="s1"&gt;'order_id=A1B2C3'&lt;/span&gt; &lt;span class="nt"&gt;-C&lt;/span&gt; 40 &lt;span class="nt"&gt;-open&lt;/span&gt;

uvp corpus.txt &lt;span class="nt"&gt;-uniq&lt;/span&gt; &lt;span class="s1"&gt;'^(.{0,30})'&lt;/span&gt; &lt;span class="nt"&gt;-head&lt;/span&gt; 20

uvp 2026-09.tsv &lt;span class="s1"&gt;'ERROR'&lt;/span&gt; &lt;span class="s1"&gt;'timeout'&lt;/span&gt; &lt;span class="nt"&gt;-out&lt;/span&gt; check-202609.txt.gz

uvp huge.xml.uwvz &lt;span class="s1"&gt;'&amp;lt;UnexpectedTag'&lt;/span&gt;
uvp huge.xml.uwvz &lt;span class="nt"&gt;-extract&lt;/span&gt; &lt;span class="nt"&gt;-out&lt;/span&gt; restored/   &lt;span class="c"&gt;# restoring the original is free&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Exit codes are grep's — &lt;strong&gt;0 found, 1 not found&lt;/strong&gt; — plus &lt;strong&gt;2 when the 1,000,000-hit cap is exceeded&lt;/strong&gt;. &lt;code&gt;if uvp input.tsv 'ERROR'; then&lt;/code&gt; works as written, so it drops straight into part 3's check script.&lt;/p&gt;

&lt;p&gt;Stated honestly: &lt;strong&gt;on the first question ripgrep is 15–20% faster&lt;/strong&gt;, because &lt;code&gt;uvp&lt;/code&gt; builds its index first. On a 3 GB file that fits in RAM, rg stays ahead on the second question too. &lt;code&gt;uvp&lt;/code&gt; pays off past 10 GB, when you ask &lt;strong&gt;the same file more than one question&lt;/strong&gt; (&lt;a href="https://uvp.y42u.net/en/blog/uvp-cli-release-vs-ripgrep-en/" rel="noopener noreferrer"&gt;the measurements&lt;/a&gt;; Mac M4, external USB SSD, OpenStreetMap XML — one setup, results vary).&lt;/p&gt;

&lt;p&gt;What it does is find things, show you the real thing, and let you repair it without breaking the original. If you're doing monthly input checks or multi-day migration repairs, try it.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://uvp.y42u.net/en/pro-en/" rel="noopener noreferrer"&gt;&lt;strong&gt;14-day free trial&lt;/strong&gt;&lt;/a&gt; (everything in View + Edit, no payment details)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://uvp.y42u.net/en/pro-en/" rel="noopener noreferrer"&gt;&lt;strong&gt;UwView Pro (View)&lt;/strong&gt;&lt;/a&gt; — persistent index, compressed-cache search and ~1/9 storage make both reopening and searching a step faster (all OS, one-time or monthly)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://uvp.y42u.net/en/pro-en/" rel="noopener noreferrer"&gt;&lt;strong&gt;Edit Upgrade&lt;/strong&gt;&lt;/a&gt; — non-destructive diff editing, bulk replace on huge files, pause and resume (add-on to a View License)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Part 1 — Four standard tools that sink under a huge file: &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps01-huge-file-tool-limits-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uwview-ps01-huge-file-tool-limits-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 3 — Four encoding traps and how to isolate them: &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps03-japanese-encoding-traps-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uwview-ps03-japanese-encoding-traps-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 8 — Compressed storage and searchability at once: &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps08-compressed-archive-search-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uwview-ps08-compressed-archive-search-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 9 — Reading structured data raw: &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps09-read-raw-structured-data-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uwview-ps09-read-raw-structured-data-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 13 — Four techniques for inspecting huge data: &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps13-huge-data-inspection-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uwview-ps13-huge-data-inspection-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 15 — Four limits of command-line craft: &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps15-cli-craft-limits-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uwview-ps15-cli-craft-limits-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 17 — Friction between logs and your dev environment: &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps17-dev-env-log-friction-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uwview-ps17-dev-env-log-friction-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 20 — Surviving an audit by design: &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps20-audit-log-retrieval-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uwview-ps20-audit-log-retrieval-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Measuring bulk replace on 100 million lines: &lt;a href="https://uvp.y42u.net/en/blog/uep-100m-lines-replace-all-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uep-100m-lines-replace-all-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;We shipped a &lt;code&gt;uvp&lt;/code&gt; command (measured against ripgrep): &lt;a href="https://uvp.y42u.net/en/blog/uvp-cli-release-vs-ripgrep-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uvp-cli-release-vs-ripgrep-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Drill-down search: &lt;a href="https://uvp.y42u.net/en/blog/uvp-drilldown-search-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uvp-drilldown-search-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Sequence search: &lt;a href="https://uvp.y42u.net/en/blog/uvp-sequence-search-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uvp-sequence-search-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Source code (GitHub): &lt;a href="https://github.com/amru195704/UwView" rel="noopener noreferrer"&gt;https://github.com/amru195704/UwView&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;From the developer&lt;/strong&gt;: A full list of my apps, Kindle books and open-source work lives at &lt;a href="https://github.com/amru195704" rel="noopener noreferrer"&gt;GitHub: amru195704&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A note&lt;/strong&gt;&lt;br&gt;
This article is provided for reference and makes no guarantee of accuracy or completeness. The error messages, line numbers, file sizes and counts are illustrative and do not refer to any real dataset or engagement. Quote handling in CSV/TSV, line-ending behaviour, and XML character-reference handling vary by parser and library version. Command examples may need adjusting for your environment (GNU vs BSD, differences among &lt;code&gt;awk&lt;/code&gt;, &lt;code&gt;sed&lt;/code&gt;, &lt;code&gt;sort&lt;/code&gt;, &lt;code&gt;grep&lt;/code&gt;, &lt;code&gt;xmllint&lt;/code&gt;, availability of &lt;code&gt;grep -P&lt;/code&gt;). Always confirm option names and defaults against your local &lt;code&gt;man&lt;/code&gt;. Figures described as measured come from one specific setup and are not a guarantee of the same result; disk type, filesystem, fragmentation, encryption, page-cache state and concurrent processes change outcomes substantially. Handling of training data and migration data is governed by your organisation's policy, your client's, and applicable law. If you spot an error, please leave a comment and I'll check and correct it.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>dataengineering</category>
      <category>etl</category>
      <category>devops</category>
      <category>cli</category>
    </item>
    <item>
      <title>"Please Produce the Logs from Three Years Ago" — Four Ways to Survive an Audit by Design</title>
      <dc:creator>y4u</dc:creator>
      <pubDate>Tue, 15 Sep 2026 16:10:37 +0000</pubDate>
      <link>https://dev.to/amru195704/please-produce-the-logs-from-three-years-ago-four-ways-to-survive-an-audit-by-design-2ecn</link>
      <guid>https://dev.to/amru195704/please-produce-the-logs-from-three-years-ago-four-ways-to-survive-an-audit-by-design-2ecn</guid>
      <description>&lt;p&gt;Two weeks to produce them.&lt;/p&gt;

&lt;p&gt;You retained them, yes. &lt;strong&gt;But how many days does it take to get them back out?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Retention period and retrieval time are two different designs. Skip one and the other breaks.&lt;/p&gt;

&lt;p&gt;Being asked for logs by an auditor runs on a different clock than incident response. An incident is about &lt;em&gt;now&lt;/em&gt;. An audit is about three years ago, in a shape &lt;strong&gt;somebody else decided&lt;/strong&gt;. Four scenes here — a request scoped by date, checking raw logs before ingestion, the economics of retention, and archives that outlive everything around them — each starting from what actually jams.&lt;/p&gt;

&lt;h2&gt;
  
  
  Up front: UwView Pro's sequence search returns only the places where terms appear &lt;em&gt;in that order&lt;/em&gt;, so an audit trail like "request → approval → execution" becomes the query itself. Drill-down search stacks the narrowing and &lt;strong&gt;keeps the original line numbers all the way to the last stage&lt;/strong&gt;, so an excerpt can carry the one thing a submission needs: which line of which original. The index and the compression are saved to a sidecar, so from the second open onward the file comes back with line numbers in 0.02–0.07 s (measured on a 47.73 GB text file; one specific setup, results vary). Everything runs on your own machine; the file is never sent anywhere (details at the end)
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;This article covers retrieving and reading a log you already hold. What you must retain, for how long, and whether a submission satisfies a requirement belong to your organisation's policy and the applicable law and regulators.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  1. The retention period is met. The retrieval was never designed.
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Situation
&lt;/h3&gt;

&lt;p&gt;The request arrives: "Produce the operation logs for one account, for October 2023."&lt;/p&gt;

&lt;p&gt;Policy says three years. You're inside it. The files exist. You go to open them, and stop.&lt;/p&gt;

&lt;p&gt;That month's archive is 31 daily files, compressed, a few GB each. The names are sequential; the dates inside are unknown until you open them. And &lt;strong&gt;nothing here tells you which file that account appears in, or where&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So you decompress them one at a time and &lt;code&gt;grep&lt;/code&gt;. Check free space, run one, read the output, delete, next. This does not finish today.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why it happens
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Because the policy only ever specified a period.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;What's written down is "retain for N years." What isn't written down is "retrievable within N hours" — and what isn't written down doesn't get designed. So &lt;strong&gt;retention gets automated and retrieval stays manual&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;There's a second mismatch. An audit request &lt;strong&gt;does not arrive in the units you stored things in&lt;/strong&gt;. You stored by host, by date, by service. The request comes as "this account," "this transaction ID," "this window" — cuts that run across your storage layout. The way you filed it is not an index into the way you're asked for it. That's the jam.&lt;/p&gt;

&lt;h3&gt;
  
  
  Working with general-purpose tools, and where it stops
&lt;/h3&gt;

&lt;p&gt;Narrow the file set first.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; /archive/app-202310&lt;span class="k"&gt;*&lt;/span&gt;.log.gz

&lt;span class="k"&gt;for &lt;/span&gt;f &lt;span class="k"&gt;in&lt;/span&gt; /archive/&lt;span class="k"&gt;*&lt;/span&gt;.log.gz&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'%s\t'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;gzip&lt;/span&gt; &lt;span class="nt"&gt;-dc&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then search across them without unpacking to disk.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;zgrep &lt;span class="nt"&gt;-h&lt;/span&gt; &lt;span class="s1"&gt;'user_id=A1B2C3'&lt;/span&gt; /archive/app-202310&lt;span class="k"&gt;*&lt;/span&gt;.log.gz &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; hits.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;zgrep&lt;/code&gt; saves you the unpacking step in the shell, but &lt;strong&gt;what it does internally is decompress-and-scan, sequentially&lt;/strong&gt;. Run it over tens of gigabytes and it costs what that costs — the point of &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps08-compressed-archive-search-en/" rel="noopener noreferrer"&gt;Part 8&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Three limits.&lt;/p&gt;

&lt;p&gt;First, &lt;strong&gt;&lt;code&gt;-h&lt;/code&gt; drops the filename and omitting it prefixes every line with one.&lt;/strong&gt; A submission needs "which file, which line," and &lt;code&gt;zgrep&lt;/code&gt; cannot give you the original line number at all (&lt;code&gt;-n&lt;/code&gt; exists, but it counts within the decompressed stream).&lt;/p&gt;

&lt;p&gt;Second, &lt;strong&gt;each extra condition is another pass.&lt;/strong&gt; To confirm "that account made a configuration change somewhere in October," you filter by account, then look for the change within the result. &lt;code&gt;zgrep A | grep B&lt;/code&gt; looks like one pass, but in practice &lt;strong&gt;every re-narrowing means reading the compressed source from the top again&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Third, and this one hurts most in practice: &lt;strong&gt;&lt;code&gt;grep&lt;/code&gt; output has no context.&lt;/strong&gt; Matching lines alone can't answer "what happened just before this action," so you end up wanting ±20 lines. Add &lt;code&gt;-C 20&lt;/code&gt; and the output inflates twentyfold — and you're back to a human reading 31 files' worth.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Look at the real thing before you write the ingest rule
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Situation
&lt;/h3&gt;

&lt;p&gt;Application logs are going into the log platform. You write the parse definition.&lt;/p&gt;

&lt;p&gt;The spec documents the log format. You build the regex to match, and load. Ingestion runs. A few days later the dashboard shows &lt;strong&gt;about sixty percent of the volume you expected&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Lines that failed to parse were dropped. What kind of lines they were, you can't tell — they were dropped.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why it happens
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Because a spec documents one line, from the happy path.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Real logs contain lines no spec mentions.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Stack traces.&lt;/strong&gt; One exception spans dozens of lines. From line two on there's no timestamp, so a line-oriented parser either discards all of it or counts each line as its own event.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lines that differ by level.&lt;/strong&gt; &lt;code&gt;DEBUG&lt;/code&gt; carries an extra field — the kind of difference that never reaches a spec.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The day the format changed.&lt;/strong&gt; A library upgrade moved a delimiter. During the transition, both shapes coexist in one file.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mixed encodings.&lt;/strong&gt; Lines that arrived from an external system still in Shift_JIS is a real situation, not a hypothetical (&lt;a href="https://uvp.y42u.net/en/blog/uwview-ps03-japanese-encoding-traps-en/" rel="noopener noreferrer"&gt;Part 3&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Broken lines.&lt;/strong&gt; Cut at a rotation boundary, or half-written when the process died.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In an audit context this matters for a specific reason: &lt;strong&gt;"some lines didn't make it in" is itself a finding.&lt;/strong&gt; When completeness is the question, "sixty percent is loaded" is not an answer.&lt;/p&gt;

&lt;h3&gt;
  
  
  Working with general-purpose tools, and where it stops
&lt;/h3&gt;

&lt;p&gt;Count the shapes in the real file before you load it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-cv&lt;/span&gt; &lt;span class="s1"&gt;'^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}'&lt;/span&gt; app.log

&lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="nt"&gt;-F&lt;/span&gt;&lt;span class="s1"&gt;'\t'&lt;/span&gt; &lt;span class="s1"&gt;'{print NF}'&lt;/span&gt; app.log | &lt;span class="nb"&gt;sort&lt;/span&gt; | &lt;span class="nb"&gt;uniq&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-rn&lt;/span&gt; | &lt;span class="nb"&gt;head

awk&lt;/span&gt; &lt;span class="s1"&gt;'{print length}'&lt;/span&gt; app.log | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; | &lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-5&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second one earns its keep. &lt;strong&gt;If you expect eight fields and sevens and nines show up, the definition is already broken&lt;/strong&gt; — before you load anything.&lt;/p&gt;

&lt;p&gt;Three limits.&lt;/p&gt;

&lt;p&gt;First, &lt;strong&gt;you can only count what you thought to count.&lt;/strong&gt; Field counts, sure. "What do the seven-field lines actually look like?" needs eyes on the real lines. &lt;code&gt;awk 'NF==7'&lt;/code&gt; will pull them, and that is &lt;strong&gt;one more full pass through the file, per idea you have.&lt;/strong&gt; Tens of gigabytes, once per idea.&lt;/p&gt;

&lt;p&gt;Second, &lt;strong&gt;the meaning lives at the tail of the distribution.&lt;/strong&gt; If three lines out of a million have a different shape, those three are the ones worth reading. &lt;code&gt;sort | uniq -c&lt;/code&gt; is excellent at ranking the common cases and no help at all at &lt;em&gt;finding&lt;/em&gt; the rare one — you still have to go look at those three.&lt;/p&gt;

&lt;p&gt;Third, &lt;strong&gt;sampling isn't enough&lt;/strong&gt; in some of these cases. &lt;code&gt;head -10000&lt;/code&gt; tells you nothing about a malformed line sitting forty gigabytes in. As &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps13-huge-data-inspection-en/" rel="noopener noreferrer"&gt;Part 13&lt;/a&gt; put it, inspection sometimes presupposes being able to see all of it.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Compression ratio and retrievability draw on the same budget
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Situation
&lt;/h3&gt;

&lt;p&gt;You review storage. Better compression means more years on the same disk.&lt;/p&gt;

&lt;p&gt;You set a policy: last month uncompressed, a year at normal compression, everything older at high compression in the archive. Capacity visibly improves.&lt;/p&gt;

&lt;p&gt;Six months later, an audit asks for something two years old. That's the high-compression tier. Decompression takes a while. The scratch volume isn't big enough, so you do it in chunks. &lt;strong&gt;What you saved on storage has come back as retrieval labour.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Why it happens
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Because the cost you cut and the cost you added land in different ledgers.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Storage is billed monthly and visible. Retrieval labour appears as one engineer's overtime in the month an audit happens. Both are costs; they never sit in the same table, so &lt;strong&gt;you end up optimising exactly one of them&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;And the trade-off isn't binary. There are at least three axes.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Compression ratio&lt;/strong&gt; (drives storage cost)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decompression speed&lt;/strong&gt; (drives retrieval time; higher ratios tend to be slower)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Whether random access is possible at all&lt;/strong&gt; — the one that gets overlooked&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That third one is the crux. Most compression formats are &lt;strong&gt;built as streams&lt;/strong&gt;, so "decompress only the middle" isn't available. To read the byte at 1 GB you decompress the first 1 GB. While that holds, "pull out only the part you need" is not merely slow — it doesn't exist.&lt;/p&gt;

&lt;p&gt;So the real trade-off isn't "compression versus searchability." It's &lt;strong&gt;whether the format carries an index&lt;/strong&gt;. With one, you can raise the ratio and keep retrieval fast.&lt;/p&gt;

&lt;h3&gt;
  
  
  Working with general-purpose tools, and where it stops
&lt;/h3&gt;

&lt;p&gt;Measure on your own data first.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;for &lt;/span&gt;lv &lt;span class="k"&gt;in &lt;/span&gt;1 6 9&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;&lt;span class="nb"&gt;time gzip&lt;/span&gt; -&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$lv&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; app.log &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"/tmp/t-&lt;/span&gt;&lt;span class="nv"&gt;$lv&lt;/span&gt;&lt;span class="s2"&gt;.gz"&lt;/span&gt;
  &lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; &lt;span class="s2"&gt;"/tmp/t-&lt;/span&gt;&lt;span class="nv"&gt;$lv&lt;/span&gt;&lt;span class="s2"&gt;.gz"&lt;/span&gt;
&lt;span class="k"&gt;done

&lt;/span&gt;&lt;span class="nb"&gt;time gzip&lt;/span&gt; &lt;span class="nt"&gt;-dc&lt;/span&gt; /tmp/t-9.gz &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /dev/null
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As a general tendency, raising the level makes compression much slower while decompression changes far less. Given that &lt;strong&gt;you compress once and retrieve many times&lt;/strong&gt;, that asymmetry is on your side.&lt;/p&gt;

&lt;p&gt;The numbers move a lot with format, implementation, and the data itself. Repetitive text like logs behaves nothing like already-compressed payloads. &lt;strong&gt;Building that table for your own data is the actual work of this section.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Two limits.&lt;/p&gt;

&lt;p&gt;First, &lt;strong&gt;block-level indexing costs operations.&lt;/strong&gt; Split, compress per block, and keep a separate catalogue of what's where, and random access works. Building, updating, and retaining that catalogue happens in a different budget line than the capacity you saved.&lt;/p&gt;

&lt;p&gt;Second, &lt;strong&gt;the person deciding compression is not the person retrieving.&lt;/strong&gt; Platform engineering watches capacity; audit response does the pulling. A policy set for one side's convenience is inherited by the other side six months later. The "delete, keep, or compress" call from &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps04-log-retention-decision-en/" rel="noopener noreferrer"&gt;Part 4&lt;/a&gt; has to be made with that split in view.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Will it still be readable in seven years?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Situation
&lt;/h3&gt;

&lt;p&gt;A financial system. Transaction logs, retained seven years.&lt;/p&gt;

&lt;p&gt;You accumulate them, quietly. Five years pass. An audit asks for an older year. The files are there. You open one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You can't read it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The encoding is different — that system wrote Shift_JIS. The delimiter differs from today's format. The in-house tool that used to read these no longer runs; its OS left support. The person who wrote it has left.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why it happens
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Because the retention period outlives everything around the data.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;List what changes in seven years and the size of the problem shows.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Encoding.&lt;/strong&gt; Somewhere in there is the year the house standard moved from Shift_JIS to UTF-8. Logs with the same name differ on either side of it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Format.&lt;/strong&gt; Fields get added, delimiters move, timestamp formats change.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The tool that read it.&lt;/strong&gt; Bespoke viewers stop running after an OS upgrade. When a runtime leaves support, keeping the tool alive becomes a question of keeping a whole environment alive.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;People.&lt;/strong&gt; Nobody guarantees that, seven years on, the organisation still contains someone who knows what field 4 meant.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Media.&lt;/strong&gt; Optical and tape formats retire along with their readers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And the last point is the essential one. &lt;strong&gt;What you need in seven years isn't "to read it with the tool of the day" — it's for a human to read what's inside.&lt;/strong&gt; A log is text. As long as it's text, it is in principle readable. What makes it unreadable is almost always &lt;strong&gt;something around it&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Working with general-purpose tools, and where it stops
&lt;/h3&gt;

&lt;p&gt;For long-term archives, store the instructions next to the data.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /archive/2019/README.txt &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
encoding : Shift_JIS (CP932)
delimiter: TAB
fields   : 1=timestamp(JST, yyyy/MM/dd HH:mm:ss) 2=account 3=action 4=result
note     : field 5 (session_id) was added on and after 2019-08-01
&lt;/span&gt;&lt;span class="no"&gt;EOF

&lt;/span&gt;file &lt;span class="nt"&gt;-i&lt;/span&gt; old.log
&lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; 3 old.log | &lt;span class="nb"&gt;od&lt;/span&gt; &lt;span class="nt"&gt;-An&lt;/span&gt; &lt;span class="nt"&gt;-tx1&lt;/span&gt;        &lt;span class="c"&gt;# see a BOM with your own eyes&lt;/span&gt;

iconv &lt;span class="nt"&gt;-f&lt;/span&gt; CP932 &lt;span class="nt"&gt;-t&lt;/span&gt; UTF-8 old.log &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /tmp/old-utf8.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;README.txt&lt;/code&gt; is unglamorous and has the best odds of anything here of still working in seven years. &lt;strong&gt;Plain text needs no tool in order to read itself.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Three limits.&lt;/p&gt;

&lt;p&gt;First, &lt;strong&gt;&lt;code&gt;iconv&lt;/code&gt; makes a copy.&lt;/strong&gt; Leaving the original untouched is correct practice (&lt;a href="https://uvp.y42u.net/en/blog/uwview-ps16-log-as-evidence-en/" rel="noopener noreferrer"&gt;Part 16&lt;/a&gt;), but it doubles the volume and adds "which of these is the original" to your bookkeeping. Hand a converted file to an auditor and you acquire the job of explaining that nothing was lost in conversion.&lt;/p&gt;

&lt;p&gt;Second, &lt;strong&gt;a mixed file can't be converted in one pass.&lt;/strong&gt; If Shift_JIS and UTF-8 coexist inside one file, &lt;code&gt;iconv&lt;/code&gt; either stops somewhere or emits mojibake — the situation covered in &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps11-legacy-encoding-euc-utf16-en/" rel="noopener noreferrer"&gt;Part 11&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Third, &lt;strong&gt;nobody has a motive to write that README at the time it must be written.&lt;/strong&gt; The person who suffers is someone else, seven years out. Which is why this can't be an individual good habit — it has to be &lt;strong&gt;a step inside the procedure that creates an archive&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the four had in common
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scene&lt;/th&gt;
&lt;th&gt;Shape of the request&lt;/th&gt;
&lt;th&gt;What jams&lt;/th&gt;
&lt;th&gt;With general-purpose tools&lt;/th&gt;
&lt;th&gt;What's left over&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Dated production request&lt;/td&gt;
&lt;td&gt;"This subject, three years ago"&lt;/td&gt;
&lt;td&gt;Storage units ≠ request units&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;zgrep&lt;/code&gt; across the set&lt;/td&gt;
&lt;td&gt;No original line numbers. Every re-narrowing rereads&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pre-ingest check&lt;/td&gt;
&lt;td&gt;"Is anything missing?"&lt;/td&gt;
&lt;td&gt;Real lines the spec omits&lt;/td&gt;
&lt;td&gt;Count field-count and length distributions&lt;/td&gt;
&lt;td&gt;You can only count what you thought to count&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Retention economics&lt;/td&gt;
&lt;td&gt;"Cut the capacity"&lt;/td&gt;
&lt;td&gt;Saved cost and added labour, different ledgers&lt;/td&gt;
&lt;td&gt;Measure per compression level&lt;/td&gt;
&lt;td&gt;Without an index, random access is impossible&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Long-term archive&lt;/td&gt;
&lt;td&gt;"From seven years ago"&lt;/td&gt;
&lt;td&gt;Retention outlives everything around it&lt;/td&gt;
&lt;td&gt;Ship the instructions; &lt;code&gt;iconv&lt;/code&gt; to read only&lt;/td&gt;
&lt;td&gt;Conversion makes copies. Mixed files resist one pass&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Four different departments, four different clocks. The right-hand column rhymes anyway, because in all four &lt;strong&gt;the assumptions present at storage time are absent at retrieval time.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Whoever filed it knew the split, the encoding, the meaning of each field. Whoever retrieves it knows none of that. A stranger, seven years later, standing in front of tens of compressed gigabytes with no context and no query. That is the shape of audit response.&lt;/p&gt;

&lt;p&gt;Three conditions, then.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Readable while still compressed&lt;/strong&gt; — as long as retrieval requires scratch space and a wait, it stays a labour cost.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The original coordinates survive narrowing&lt;/strong&gt; — a submission needs a correspondence ("line N of the original"), not a loose fragment. If every stage renumbers, you can't build one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It finishes on your own machine&lt;/strong&gt; — whether audit-scope logs may go to an external service is a policy question, not a technical one. Where the answer is no, this is the first filter anything has to pass.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Back to those two weeks. That was never a retention problem. It was &lt;strong&gt;the time when nobody storing the logs thought about the person who would have to get them back out.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The tool I use
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://uvp.y42u.net/en/" rel="noopener noreferrer"&gt;UwView&lt;/a&gt; (free), which I develop, is a viewer that &lt;strong&gt;displays, scrolls, and searches from the moment it opens&lt;/strong&gt;. It doesn't load the whole file into memory, so it opens files larger than RAM. The index builds in the background; when it completes, line numbers appear.&lt;/p&gt;

&lt;p&gt;Of the three conditions above, the free edition covers the third.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Everything runs on your own machine.&lt;/strong&gt; The file isn't sent anywhere. Where taking audit-scope logs off-site is the contentious part, that's a precondition rather than a feature.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It never writes to the original.&lt;/strong&gt; No splitting, no extracting, so a file pulled from an archive stays one file, unmodified.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Encoding switches while the file stays open&lt;/strong&gt; (UTF-8 / Shift-JIS (CP932) / EUC-JP / UTF-16, auto-detected). For reading, the second copy &lt;code&gt;iconv&lt;/code&gt; makes in section 4 stops being necessary.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The first two conditions are &lt;a href="https://uvp.y42u.net/en/pro-en/-en" rel="noopener noreferrer"&gt;UwView Pro&lt;/a&gt; territory.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Sequence search&lt;/strong&gt;: only the places where &lt;code&gt;w1 → w2 → w3&lt;/code&gt; appear in that order. The audit-shaped sequences — "request → approval → execution," "login → privilege change → logout" — &lt;strong&gt;become the query directly&lt;/strong&gt; (&lt;a href="https://uvp.y42u.net/en/blog/uvp-sequence-search-en/" rel="noopener noreferrer"&gt;the implementation post&lt;/a&gt;). Honestly: each stage scans forward from the previous hit, so &lt;strong&gt;it costs roughly what a full-text search costs&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Drill-down search&lt;/strong&gt;: narrow a result by another term. Section 1's "filter by account, then find the configuration change" becomes a second stage instead of a second read of the source. Tabs show &lt;code&gt;term (count)&lt;/code&gt;, and &lt;strong&gt;the original line numbers survive to the last stage&lt;/strong&gt; (&lt;a href="https://uvp.y42u.net/en/blog/uvp-drilldown-search-en/" rel="noopener noreferrer"&gt;the drill-down post&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;±N is per stage&lt;/strong&gt;: ±1 while narrowing, ±20 on the stage you actually read. You choose the width &lt;em&gt;after&lt;/em&gt; you know what you need, so section 1's "&lt;code&gt;-C 20&lt;/code&gt; inflates the output twentyfold" doesn't happen (free edition is fixed at ±1; variable ±N is Pro).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tally (frequency ranking)&lt;/strong&gt;: counts the values a regex captures, ranked, click a row to descend to it. Close to section 2's field-count distribution, without a pass per idea. It does &lt;strong&gt;not&lt;/strong&gt; compute sums or averages — its lineage is &lt;code&gt;grep -oE | sort | uniq -c&lt;/code&gt;, not &lt;code&gt;awk&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The index and the compression are saved&lt;/strong&gt;: from the second open onward the file comes back &lt;strong&gt;with line numbers, instantly&lt;/strong&gt; (0.02–0.07 s measured on a 47.73 GB text file; one specific setup, results vary). Audit work is never read-once. Follow-up questions, clarifications, writing the submission — the same file, reopened across days.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;~1/9 storage that stays searchable&lt;/strong&gt;: directly against section 3's "no index, no random access." Pro's sidecar (&lt;code&gt;.uwvz&lt;/code&gt;) carries a per-block offset table, so you jump to the part you need while it stays compressed — no decompress-from-the-top scan the way &lt;code&gt;zgrep&lt;/code&gt; does it (&lt;a href="https://uvp.y42u.net/en/blog/uwview-ps08-compressed-archive-search-en/" rel="noopener noreferrer"&gt;Part 8&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The sidecar also keeps an XxHash3 table per compressed block and verifies on every decompression, so bit rot during long storage, or a copy that was truncated in transit, won't be read straight past unnoticed. &lt;strong&gt;This is not tamper detection.&lt;/strong&gt; XxHash3 is a non-cryptographic hash chosen for speed. What tamper resistance needs is &lt;code&gt;sha256sum&lt;/code&gt; plus separated custody; this check exists to &lt;strong&gt;catch accidents&lt;/strong&gt; (&lt;a href="https://uvp.y42u.net/en/blog/uwview-ps16-log-as-evidence-en/" rel="noopener noreferrer"&gt;Part 16&lt;/a&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  Where it honestly stops
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;UwView is a viewer. It is not a SIEM, not a log management platform, not an audit-trail system.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It doesn't write your ingest definitions (section 2). No parse validation, no loading, no automatic detection of what got dropped.&lt;/li&gt;
&lt;li&gt;It doesn't automate retention policy (section 3). No generation management, no deletion, no inventory reports.&lt;/li&gt;
&lt;li&gt;Writing the &lt;code&gt;README.txt&lt;/code&gt; in section 4 is your job. There's no metadata management.&lt;/li&gt;
&lt;li&gt;It records no access trail — who opened what, when. &lt;strong&gt;If your audit requires viewing logs, this tool alone cannot satisfy it.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;No cross-log correlation, no alerting, no report generation, no threat-intel matching.&lt;/li&gt;
&lt;li&gt;It handles text. Binary database dumps and disk images are out of scope.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One more constraint, stated plainly for people who touch archive storage. &lt;strong&gt;Pro's sidecar (&lt;code&gt;.uwvz&lt;/code&gt;) is created as a new file beside the original.&lt;/strong&gt; The original itself doesn't change by a single byte, but if your practice is that &lt;strong&gt;nothing new appears inside the archive area&lt;/strong&gt;, copy the file to a working volume before opening it. (The sidecar keys on the original's length and mtime, so it invalidates itself automatically if the original changes.)&lt;/p&gt;

&lt;p&gt;What this tool covers is the step before all of that: read the stored raw log, as it is, on your own machine, with the original coordinates intact, with your own eyes. If you're at the scale that needs correlation and alerting, that's a different product's job.&lt;/p&gt;

&lt;p&gt;For reference, non-destructive differential editing (Edit Upgrade) exists as a separate licence, but &lt;strong&gt;every one of this article's four sections is read-only work.&lt;/strong&gt; What you need here is the View side.&lt;/p&gt;

&lt;h3&gt;
  
  
  The same things, from the command line
&lt;/h3&gt;

&lt;p&gt;v1.6.0 added a &lt;code&gt;uvp&lt;/code&gt; command (and &lt;code&gt;uvf&lt;/code&gt; for the free build). It uses &lt;strong&gt;the same &lt;code&gt;.uwvz&lt;/code&gt; as the GUI&lt;/strong&gt;, so an index built from the shell is already there when you open the file in the app. Mapped onto this article's four parts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uvp /archive/app-20231015.log &lt;span class="s1"&gt;'user_id=A1B2C3'&lt;/span&gt; &lt;span class="s1"&gt;'config.change'&lt;/span&gt; &lt;span class="nt"&gt;-C&lt;/span&gt; 20 &lt;span class="nt"&gt;-out&lt;/span&gt; audit-202310.txt

uvp /archive/app-20231015.log &lt;span class="nt"&gt;-seq&lt;/span&gt; &lt;span class="s1"&gt;'REQUEST,APPROVE,EXECUTE'&lt;/span&gt; &lt;span class="nt"&gt;-C&lt;/span&gt; 5

uvp app.log &lt;span class="nt"&gt;-uniq&lt;/span&gt; &lt;span class="s1"&gt;'^(.{0,10})'&lt;/span&gt; &lt;span class="nt"&gt;-head&lt;/span&gt; 20

uvp /archive/2023/app-20231015.log.uwvz &lt;span class="s1"&gt;'user_id=A1B2C3'&lt;/span&gt;

uvp /archive/2019/old.log.uwvz &lt;span class="nt"&gt;-extract&lt;/span&gt; &lt;span class="nt"&gt;-out&lt;/span&gt; /tmp/restore/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Exit codes are grep's — &lt;strong&gt;0 found, 1 not found&lt;/strong&gt; — plus &lt;strong&gt;2 when the 1,000,000-hit cap is exceeded&lt;/strong&gt;. Section 1's "work through 31 files" becomes one &lt;code&gt;uvp&lt;/code&gt; line inside &lt;code&gt;for f in /archive/app-202310*.log; do ... done&lt;/code&gt;, with the exit code telling you &lt;strong&gt;which files actually held something&lt;/strong&gt;. Stated plainly, though: &lt;strong&gt;&lt;code&gt;uvp&lt;/code&gt; records no access trail either.&lt;/strong&gt; An audit that requires a log of who viewed what, and when, is still not something this tool answers on its own.&lt;/p&gt;

&lt;p&gt;Stated honestly: &lt;strong&gt;on the first question ripgrep is 15–20% faster&lt;/strong&gt;, because &lt;code&gt;uvp&lt;/code&gt; builds its index first. On a 3 GB file that fits in RAM, rg stays ahead on the second question too. &lt;code&gt;uvp&lt;/code&gt; pays off past 10 GB, when you ask &lt;strong&gt;the same file more than one question&lt;/strong&gt; (&lt;a href="https://uvp.y42u.net/en/blog/uvp-cli-release-vs-ripgrep-en/" rel="noopener noreferrer"&gt;the measurements&lt;/a&gt;; Mac M4, external USB SSD, OpenStreetMap XML — one setup, results vary).&lt;/p&gt;

&lt;p&gt;And if a huge log is eating your disk and you want it &lt;strong&gt;compressed for storage while staying searchable at speed, give &lt;a href="https://uvp.y42u.net/en/pro-en/-en" rel="noopener noreferrer"&gt;UwView Pro&lt;/a&gt; a look&lt;/strong&gt; — persistent index, compressed-cache search, and ~1/9 storage make both reopening and searching a step faster (all OS, one-time or monthly).&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Part 1: four go-to tools that sink under huge files: &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps01-huge-file-tool-limits-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uwview-ps01-huge-file-tool-limits-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 3: four character-encoding traps and how to isolate them: &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps03-japanese-encoding-traps-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uwview-ps03-japanese-encoding-traps-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 4: deciding whether to delete, keep, or compress: &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps04-log-retention-decision-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uwview-ps04-log-retention-decision-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 8: keeping logs compressed and still searchable: &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps08-compressed-archive-search-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uwview-ps08-compressed-archive-search-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 11: reading legacy encodings in 2026: &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps11-legacy-encoding-euc-utf16-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uwview-ps11-legacy-encoding-euc-utf16-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 13: four techniques for inspecting huge data: &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps13-huge-data-inspection-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uwview-ps13-huge-data-inspection-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 14: the traces of an attack are in the raw log: &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps14-attack-traces-raw-logs-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uwview-ps14-attack-traces-raw-logs-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 16: four principles for preserving, excerpting, and proving integrity: &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps16-log-as-evidence-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uwview-ps16-log-as-evidence-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 19: four situations where tail -f can't keep up: &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps19-streaming-log-follow-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uwview-ps19-streaming-log-follow-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;We shipped a &lt;code&gt;uvp&lt;/code&gt; command (measured against ripgrep): &lt;a href="https://uvp.y42u.net/en/blog/uvp-cli-release-vs-ripgrep-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uvp-cli-release-vs-ripgrep-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Drill-down search — narrowing a result by another term: &lt;a href="https://uvp.y42u.net/en/blog/uvp-drilldown-search-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uvp-drilldown-search-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Sequence search — finding only what appears in that order: &lt;a href="https://uvp.y42u.net/en/blog/uvp-sequence-search-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uvp-sequence-search-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Archive plus session restore, as a working flow: &lt;a href="https://uvp.y42u.net/en/blog/uwview-archive-session-restore-workflow-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uwview-archive-session-restore-workflow-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Source code (GitHub): &lt;a href="https://github.com/amru195704/UwView" rel="noopener noreferrer"&gt;https://github.com/amru195704/UwView&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;From the developer&lt;/strong&gt;: a full list of my apps, Kindle books, and open-source work is on &lt;a href="https://github.com/amru195704" rel="noopener noreferrer"&gt;GitHub: amru195704&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A note&lt;/strong&gt;&lt;br&gt;
This article is provided for reference and makes no guarantee of accuracy or completeness. Actual audit response, internal control, and retention policy must follow your organisation's rules and the applicable law and regulators. Nothing here is advice on statutory retention obligations or on what a submission must contain. Retention periods, dates, account names, filenames, and field layouts are illustrative and describe no specific regime, organisation, or case. The behaviour of &lt;code&gt;gzip&lt;/code&gt;, &lt;code&gt;zgrep&lt;/code&gt;, &lt;code&gt;grep&lt;/code&gt;, &lt;code&gt;awk&lt;/code&gt;, &lt;code&gt;iconv&lt;/code&gt;, &lt;code&gt;file&lt;/code&gt;, and &lt;code&gt;od&lt;/code&gt; varies by implementation (GNU/BSD/busybox), version, and build options, as do option names and defaults — check your own &lt;code&gt;man&lt;/code&gt; pages. Statements about compression ratio and decompression speed are general tendencies; results change with the data, the format, and the implementation. Measured figures come from one specific setup and are not a guarantee of the same result — disk type, filesystem, fragmentation, encryption, page-cache state, and concurrent processes all move them substantially. If you spot an error, a comment is welcome and I'll check and correct it.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>audit</category>
      <category>security</category>
      <category>devops</category>
      <category>logging</category>
    </item>
    <item>
      <title>Following a Log That Won't Stop Growing — Four Situations Where tail -f Can't Keep Up</title>
      <dc:creator>y4u</dc:creator>
      <pubDate>Tue, 15 Sep 2026 16:03:56 +0000</pubDate>
      <link>https://dev.to/amru195704/following-a-log-that-wont-stop-growing-four-situations-where-tail-f-cant-keep-up-14ke</link>
      <guid>https://dev.to/amru195704/following-a-log-that-wont-stop-growing-four-situations-where-tail-f-cant-keep-up-14ke</guid>
      <description>&lt;p&gt;Three thousand lines a second is unreadable.&lt;/p&gt;

&lt;p&gt;Your eyes manage maybe a dozen lines a second. &lt;strong&gt;So where did that &lt;code&gt;ERROR&lt;/code&gt; go — the one that went past at two hundred times reading speed?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A log that keeps growing is hard in ways a static log isn't. Mid-incident triage, an overnight load test, an embedded serial console, a dev box turned up to TRACE: four different situations, and the dead end has the same shape in all of them. Here they are in order.&lt;/p&gt;

&lt;h2&gt;
  
  
  Up front: UwView Pro saves the index and the compression, so even on a file that keeps growing you can repeat "reopen and read to the end" in 0.02–0.07 s from the second open onward (measured on a 47.73 GB text file; one specific setup, results vary), and reapply a saved search condition as-is. Note that following a file &lt;em&gt;while it is being written&lt;/em&gt; — real-time tail — lives in the &lt;strong&gt;free edition of UwView&lt;/strong&gt; (details at the end)
&lt;/h2&gt;




&lt;h2&gt;
  
  
  1. The screen only shows "now" — after the ERROR has scrolled away
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Situation
&lt;/h3&gt;

&lt;p&gt;You're in the middle of an incident, with &lt;code&gt;tail -f&lt;/code&gt; on the application log.&lt;/p&gt;

&lt;p&gt;It pours. You thought you saw an &lt;code&gt;ERROR&lt;/code&gt; and tried to follow it, but it's already off the top. Hit Ctrl+C and &lt;strong&gt;nothing after that point reaches the screen at all&lt;/strong&gt; — while the file keeps growing.&lt;/p&gt;

&lt;p&gt;Scrolling back up gets you a few thousand lines. At 3,000 lines a second, that's a few seconds' worth.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why it happens
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;tail -f&lt;/code&gt; is a tool for forwarding appends to the end of a file. It has no notion of going backwards.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Then human reading speed multiplies in. You can follow maybe a dozen lines a second, and a handful if you're actually extracting meaning. At 3,000 lines a second, &lt;strong&gt;the gap between output rate and reading rate is more than two orders of magnitude.&lt;/strong&gt; With a gap that wide, "watch all of it" was never on the table.&lt;/p&gt;

&lt;p&gt;Adding a pipe to narrow things down introduces a second problem. When its stdout is a pipe or a file, &lt;code&gt;grep&lt;/code&gt; switches to block buffering, so nothing reaches you until a few kilobytes have accumulated. A few seconds of lag mid-incident is a few seconds of lag on the decision.&lt;/p&gt;

&lt;h3&gt;
  
  
  Working with general-purpose tools, and where it stops
&lt;/h3&gt;

&lt;p&gt;Turn off the buffering, then narrow.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; app.log | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;--line-buffered&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'ERROR|FATAL'&lt;/span&gt;

&lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; app.log | &lt;span class="nb"&gt;stdbuf&lt;/span&gt; &lt;span class="nt"&gt;-oL&lt;/span&gt; &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'ERROR'&lt;/span&gt; | &lt;span class="nb"&gt;tee &lt;/span&gt;triage.log

&lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; 200000 &lt;span class="nt"&gt;-f&lt;/span&gt; app.log

&lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-F&lt;/span&gt; app.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;--line-buffered&lt;/code&gt; and &lt;code&gt;stdbuf&lt;/code&gt; assume GNU coreutils and GNU grep. BSD and busybox behave differently, so check your own &lt;code&gt;man&lt;/code&gt; pages.&lt;/p&gt;

&lt;p&gt;Three limits.&lt;/p&gt;

&lt;p&gt;First, &lt;strong&gt;you have to choose the condition before you start.&lt;/strong&gt; Once you're running &lt;code&gt;grep 'ERROR'&lt;/code&gt;, realising you also want &lt;code&gt;WARN&lt;/code&gt; doesn't help: &lt;strong&gt;the WARN lines that went by while you were watching only ERROR never appeared.&lt;/strong&gt; The stream doesn't wait, so there's no do-over.&lt;/p&gt;

&lt;p&gt;Second, &lt;strong&gt;you cannot go backwards.&lt;/strong&gt; &lt;code&gt;-n 200000&lt;/code&gt; rewinds to a point &lt;em&gt;at the moment you start&lt;/em&gt;. Deciding ten minutes in that you want to see ten minutes ago means stopping &lt;code&gt;tail -f&lt;/code&gt; and running something else — and while it's stopped, you see nothing.&lt;/p&gt;

&lt;p&gt;Third, &lt;strong&gt;there are no line numbers.&lt;/strong&gt; &lt;code&gt;tail&lt;/code&gt;'s output doesn't carry them. Appending &lt;code&gt;grep -n&lt;/code&gt; gives you "which line arrived on the pipe," not a coordinate in the original. Not being able to write "behaviour changes from this line of the original" in the incident report is exactly the excerpting problem from &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps16-log-as-evidence-en/" rel="noopener noreferrer"&gt;Part 16&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. The overnight load test — picking up "yesterday's" thread first thing
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Situation
&lt;/h3&gt;

&lt;p&gt;A 24-hour load test, unattended overnight.&lt;/p&gt;

&lt;p&gt;You arrive to find an 18 GB log. The monitoring graph says latency spiked some time around 3 a.m. The terminal where you'd left &lt;code&gt;tail -f&lt;/code&gt; running shows — &lt;strong&gt;a dropped session.&lt;/strong&gt; The scrollback wrapped hours ago.&lt;/p&gt;

&lt;p&gt;That afternoon a developer asks to look at the same window from a different angle. You face the 18 GB again.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why it happens
&lt;/h3&gt;

&lt;p&gt;Three things stack up.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Terminal scrollback wraps by line count.&lt;/strong&gt; Defaults sit in the thousands to tens of thousands. At a few hundred lines a second, that's minutes. The screen you were "watching" isn't kept.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ssh sessions drop.&lt;/strong&gt; A network blip, a sleeping laptop, a VPN reconnect. None of it was designed to survive eight unattended hours.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;And nothing records how far you got.&lt;/strong&gt; This is the one that really costs. Picking up the next morning and re-examining in the afternoon both &lt;strong&gt;start from zero, because there's no record of where you were.&lt;/strong&gt; Reopening the same 18 GB several times a day is &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps10-oncall-night-preparation-en/" rel="noopener noreferrer"&gt;Part 10&lt;/a&gt;'s "ten reopens a day," transplanted into an overnight test.&lt;/p&gt;

&lt;h3&gt;
  
  
  Working with general-purpose tools, and where it stops
&lt;/h3&gt;

&lt;p&gt;Keep the screen somewhere durable, and cut by time.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tmux new &lt;span class="nt"&gt;-s&lt;/span&gt; load &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'tail -f run.log | tee -a session.log'&lt;/span&gt;

&lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s1"&gt;'/2026-09-13T03:/,/2026-09-13T04:/p'&lt;/span&gt; run.log &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; 3am.log

&lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'$0 &amp;gt;= "2026-09-13T03:00" &amp;amp;&amp;amp; $0 &amp;lt; "2026-09-13T04:00"'&lt;/span&gt; run.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three limits.&lt;/p&gt;

&lt;p&gt;First, &lt;strong&gt;&lt;code&gt;tee -a&lt;/code&gt; means storing it twice.&lt;/strong&gt; Another log appears alongside the 18 GB — and if you run the test nightly, it appears nightly (&lt;a href="https://uvp.y42u.net/en/blog/uwview-ps08-compressed-archive-search-en/" rel="noopener noreferrer"&gt;Part 8&lt;/a&gt; is about the storage side).&lt;/p&gt;

&lt;p&gt;Second, &lt;strong&gt;excerpting renumbers from 1.&lt;/strong&gt; What line of the original is line 500 of &lt;code&gt;3am.log&lt;/code&gt;? You need that coordinate the moment you paste it into the test report.&lt;/p&gt;

&lt;p&gt;Third, &lt;strong&gt;both &lt;code&gt;sed -n&lt;/code&gt; and &lt;code&gt;awk&lt;/code&gt; scan from the top every time.&lt;/strong&gt; One pass over 18 GB is minutes, and comparing the 3 a.m. hour against the 4 a.m. hour is two passes. Jumping by timestamp was &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps07-timestamp-driven-triage-en/" rel="noopener noreferrer"&gt;Part 7&lt;/a&gt;'s subject — but when the target is &lt;strong&gt;still growing&lt;/strong&gt;, the tail moves while you scan.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Reading days of serial-console capture end to end
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Situation
&lt;/h3&gt;

&lt;p&gt;You ran an embedded board for three days and captured everything on the serial line. 2.4 GB of text.&lt;/p&gt;

&lt;p&gt;There are several reboots in there. Some were planned; some weren't. You want to know &lt;strong&gt;which reboots were the abnormal ones.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Your editor hangs on it. &lt;code&gt;less&lt;/code&gt; opens it, but the screen goes strangely coloured and the layout breaks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why it happens
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;A serial capture isn't "text for people to read." It's the byte stream that went to the terminal, verbatim.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Control characters and escape sequences, raw.&lt;/strong&gt; Colour codes like &lt;code&gt;ESC[32m&lt;/code&gt; (ESC is 0x1B), cursor moves, carriage returns used to overwrite a line. The bootloader and kernel were writing &lt;em&gt;to a terminal&lt;/em&gt;, so of course they're in there.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mixed line endings.&lt;/strong&gt; Bare &lt;code&gt;CR&lt;/code&gt;, &lt;code&gt;CRLF&lt;/code&gt;, and &lt;code&gt;LF&lt;/code&gt; in one file, varying with how &lt;code&gt;picocom&lt;/code&gt; / &lt;code&gt;minicom&lt;/code&gt; / &lt;code&gt;screen&lt;/code&gt; was configured. With bare CR, what counts as "one line" stops agreeing between tools — the same accident as the factory logs in &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps18-device-generated-logs-en/" rel="noopener noreferrer"&gt;Part 18&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No timestamps, or relative ones.&lt;/strong&gt; The board may only have milliseconds since boot. Answering "when" across three days means keeping a separate mapping to wall-clock time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Two different kinds of mojibake.&lt;/strong&gt; Byte-level corruption from a baud-rate mismatch, and vendor messages emitted in Shift_JIS. The second is fixed by switching encodings; the first is not recoverable (&lt;a href="https://uvp.y42u.net/en/blog/uwview-ps03-japanese-encoding-traps-en/" rel="noopener noreferrer"&gt;Part 3&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Working with general-purpose tools, and where it stops
&lt;/h3&gt;

&lt;p&gt;See what's mixed in, then count.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; capture.log | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-40&lt;/span&gt;

perl &lt;span class="nt"&gt;-pe&lt;/span&gt; &lt;span class="s1"&gt;'s/\e\[[0-9;]*[a-zA-Z]//g'&lt;/span&gt; capture.log &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; clean.log

&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s1"&gt;'Booting Linux'&lt;/span&gt; capture.log

&lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'\r'&lt;/span&gt; &amp;lt; capture.log | &lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three limits.&lt;/p&gt;

&lt;p&gt;First, &lt;strong&gt;cleaning it produces a different file.&lt;/strong&gt; &lt;code&gt;clean.log&lt;/code&gt; is readable, but it isn't the original, and dropping CR changes the line count outright. The coordinate you'd give the board vendor — "from this line of the original" — is lost in the cleanup step.&lt;/p&gt;

&lt;p&gt;Second, &lt;strong&gt;baud-corrupted text doesn't match a search.&lt;/strong&gt; If the &lt;code&gt;o&lt;/code&gt; in &lt;code&gt;Booting&lt;/code&gt; came through as a corrupted byte, &lt;code&gt;grep 'Booting Linux'&lt;/code&gt; won't count that reboot. &lt;strong&gt;The answer says four; it might really be five.&lt;/strong&gt; No amount of cleverness in the search term fixes that. Reading it through is what fixes it.&lt;/p&gt;

&lt;p&gt;Third, &lt;strong&gt;it can't answer "when."&lt;/strong&gt; With only milliseconds since boot, &lt;code&gt;grep&lt;/code&gt; gives you a place but not a time. Finding "the second day, late afternoon" in three days of capture comes down to &lt;strong&gt;reading around it and estimating.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  4. TRACE produced 40 GB in an hour — designing for the flow rate itself
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Situation
&lt;/h3&gt;

&lt;p&gt;To catch a bug that won't reproduce, you turned the log level up to TRACE and started a run.&lt;/p&gt;

&lt;p&gt;40 GB in an hour. The disk warning fires. And &lt;strong&gt;the bug hasn't shown up yet.&lt;/strong&gt; You want several more hours, which means clearing what's already there.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why it happens
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Log level multiplies rather than adds.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;What was one line per request becomes 200 at TRACE. At 100 requests a second, that's 20,000 lines a second. Extrapolating linearly from how DEBUG felt is guaranteed to miss by an order of magnitude.&lt;/p&gt;

&lt;p&gt;And everything around it is still configured for DEBUG.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Rotation can't keep up.&lt;/strong&gt; "100 MB × 10 generations" cycles several times an hour, so &lt;strong&gt;the earliest part — the part you most want — is the first to go&lt;/strong&gt; (evidence vanishing in the rotation gap was &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps16-log-as-evidence-en/" rel="noopener noreferrer"&gt;Part 16&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compression eats CPU.&lt;/strong&gt; The post-rotate &lt;code&gt;gzip&lt;/code&gt; runs on the same host as the application and contaminates the latency numbers you're measuring.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The dev box doesn't have the disk.&lt;/strong&gt; You can't reproduce production's flow rate locally (&lt;a href="https://uvp.y42u.net/en/blog/uwview-ps17-dev-env-log-friction-en/" rel="noopener noreferrer"&gt;Part 17&lt;/a&gt; is that friction).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Working with general-purpose tools, and where it stops
&lt;/h3&gt;

&lt;p&gt;Emit less, decide retention first, discard while streaming.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- TRACE only where you suspect (logback) --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;logger&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"com.example.payment"&lt;/span&gt; &lt;span class="na"&gt;level=&lt;/span&gt;&lt;span class="s"&gt;"TRACE"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;root&lt;/span&gt; &lt;span class="na"&gt;level=&lt;/span&gt;&lt;span class="s"&gt;"INFO"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;logrotate &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LOGROTATE_CONF&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-F&lt;/span&gt; app.log | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;--line-buffered&lt;/span&gt; &lt;span class="s1"&gt;'txnId=7f3a'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; trace-7f3a.log

watch &lt;span class="nt"&gt;-n&lt;/span&gt; 60 &lt;span class="s1"&gt;'df -h /var/log; ls -l --block-size=M /var/log/app.log'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three limits.&lt;/p&gt;

&lt;p&gt;First, &lt;strong&gt;narrowing erases the lines you assumed were irrelevant.&lt;/strong&gt; This is the nastiest one here. You set TRACE on &lt;code&gt;com.example.payment&lt;/code&gt; only — and the cause turns out to be contention with a different module, whose evidence was never emitted. &lt;strong&gt;You raised the level precisely because you didn't know what was relevant&lt;/strong&gt;, and now you're being asked to narrow. The order is backwards (the same contradiction shows up in &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps05-debug-log-practices-en/" rel="noopener noreferrer"&gt;Part 5&lt;/a&gt;'s printf debugging).&lt;/p&gt;

&lt;p&gt;Second, &lt;strong&gt;it disappears while you wait for rotation.&lt;/strong&gt; More generations and the disk won't hold; fewer and the old end goes. Either way, &lt;strong&gt;you're forced to choose before you've decided anything.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Third, &lt;strong&gt;without a way to open 40 GB, you end up throwing away the log you just produced.&lt;/strong&gt; This genuinely happens. You turn on TRACE, get 40 GB, &lt;code&gt;grep&lt;/code&gt; a handful of lines because nothing will open it, and delete the rest. &lt;strong&gt;Raising the output volume loses its meaning at the point where it can't be read&lt;/strong&gt; (&lt;a href="https://uvp.y42u.net/en/blog/uwview-ps01-huge-file-tool-limits-en/" rel="noopener noreferrer"&gt;Part 1&lt;/a&gt;'s "won't open" comes back around here).&lt;/p&gt;




&lt;h2&gt;
  
  
  What all four had in common
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Situation&lt;/th&gt;
&lt;th&gt;How it flows&lt;/th&gt;
&lt;th&gt;General-purpose approach&lt;/th&gt;
&lt;th&gt;What's left over&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Mid-incident tailing&lt;/td&gt;
&lt;td&gt;Thousands of lines/s, two orders above reading speed&lt;/td&gt;
&lt;td&gt;`tail -f \&lt;/td&gt;
&lt;td&gt;grep --line-buffered`&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Overnight load test&lt;/td&gt;
&lt;td&gt;8 unattended hours, 18 GB&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;tmux&lt;/code&gt; + &lt;code&gt;tee&lt;/code&gt;, &lt;code&gt;sed -n&lt;/code&gt; by time&lt;/td&gt;
&lt;td&gt;Stored twice. Renumbered from 1. No coordinate to resume from&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Serial console&lt;/td&gt;
&lt;td&gt;3 days, 2.4 GB, control characters mixed in&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;cat -v&lt;/code&gt; / &lt;code&gt;sed&lt;/code&gt; to strip ANSI / &lt;code&gt;grep -c&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Cleaning yields a different file. Corrupted terms don't match&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dev box at TRACE&lt;/td&gt;
&lt;td&gt;40 GB/hour, growing multiplicatively&lt;/td&gt;
&lt;td&gt;Per-logger levels / &lt;code&gt;logrotate&lt;/code&gt; / filtered &lt;code&gt;tee&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Narrowing suppresses the evidence. Rotation deletes it. Unreadable means wasted&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Four unrelated situations. They jam in the same place because &lt;strong&gt;a flowing log has three properties at once:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;"Now" and "a moment ago" don't fit on one screen.&lt;/strong&gt; A follower sticks to the end and has no reverse gear; the tools that &lt;em&gt;do&lt;/em&gt; go backwards (&lt;code&gt;less&lt;/code&gt;, &lt;code&gt;sed -n&lt;/code&gt;) aren't watching the end while they do it. &lt;strong&gt;Pick one and you lose sight of the other.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;There are no do-overs.&lt;/strong&gt; On a static file you can change the condition and try again as often as you like. On a flowing one, the lines that pass while you're changing the condition are simply gone from view. Hence "decide the right condition first" — an impossible request.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No coordinates survive.&lt;/strong&gt; Not in the follower's output, not at the end of the pipe, not in the cleaned-up file. You can't get back to "that line" afterwards.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With those three together, &lt;strong&gt;"read it as it flows" and "go back and read it later" become two separate jobs&lt;/strong&gt; — &lt;code&gt;tail -f&lt;/code&gt; for the first, &lt;code&gt;less&lt;/code&gt; or &lt;code&gt;sed&lt;/code&gt; for the second. Two tools isn't the problem. &lt;strong&gt;The coordinate not surviving the handoff between them&lt;/strong&gt; is the dead end.&lt;/p&gt;

&lt;p&gt;What's needed is to make the second job cheap.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Open everything that exists right now, even while the file grows&lt;/strong&gt; — look at the past in another window without stopping the follower.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Make reopening cheap&lt;/strong&gt; — the faster the flow, the more often you reread to the end. If one wait is short, the count stops mattering.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don't rebuild the condition&lt;/strong&gt; — apply last night's filter to this morning's file as-is.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Back to that &lt;code&gt;ERROR&lt;/code&gt; that scrolled away. It didn't vanish. &lt;strong&gt;It's still in the file, with a line number.&lt;/strong&gt; It only left the screen. What couldn't keep up was the eye; the record held.&lt;/p&gt;




&lt;h2&gt;
  
  
  The tool I use
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://uvp.y42u.net/en/" rel="noopener noreferrer"&gt;UwView&lt;/a&gt; (free), which I develop, is a viewer that &lt;strong&gt;displays, scrolls, and searches&lt;/strong&gt; huge text from the moment it opens. It never loads the whole file into memory, so it opens files larger than RAM. The index is built in the background; when it finishes, line numbers appear.&lt;/p&gt;

&lt;p&gt;For flowing logs, here's what the free version covers.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It opens files that are being written.&lt;/strong&gt; It opens for shared reading, so you can read while the application keeps appending. Section 1's "look at ten minutes ago in another window without stopping &lt;code&gt;tail -f&lt;/code&gt;" works.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real-time tail — following appends — is in the free version.&lt;/strong&gt; It detects growth and auto-scrolls to the end. &lt;strong&gt;As noted below, Pro does not have it&lt;/strong&gt; (desktop only; it doesn't work in the browser build).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How much you can read isn't decided by RAM.&lt;/strong&gt; Section 4's 40 GB, section 2's 18 GB, and section 3's 2.4 GB all open unsplit (measured ceiling on the free version: 47.73 GB, about 890 million lines — one specific setup).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It never writes to the original, and never excerpts or splits it.&lt;/strong&gt; No &lt;code&gt;clean.log&lt;/code&gt; from section 3, no &lt;code&gt;3am.log&lt;/code&gt; from section 2, which means &lt;strong&gt;line numbers stay the original's.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Encoding can be switched while the file is open&lt;/strong&gt; (UTF-8 / Shift-JIS (CP932) / EUC-JP / UTF-16, auto-detected). Section 3's "only the vendor messages are Shift_JIS" needs no intermediate file.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Highlighting colours lines rather than removing them.&lt;/strong&gt; Colour section 1's &lt;code&gt;ERROR|FATAL&lt;/code&gt; and you can pick them out of the stream without filtering — and because nothing is removed, &lt;strong&gt;their surroundings stay on the same screen.&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Section 2's "pick up where I left off" and section 4's 40 GB of retention are &lt;a href="https://uvp.y42u.net/en/pro-en/-en" rel="noopener noreferrer"&gt;UwView Pro&lt;/a&gt; territory.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The index and the compression are saved&lt;/strong&gt;: from the second open onward, the same file comes back &lt;strong&gt;instantly, with line numbers&lt;/strong&gt; (0.02–0.07 s measured on a 47.73 GB text file; one setup, results vary). When the workflow is "reopen and read to the end," &lt;strong&gt;the number of reopens &lt;em&gt;is&lt;/em&gt; the cost.&lt;/strong&gt; Section 2's morning and afternoon, and section 1's repeated review, are exactly that.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Search conditions can be saved and reapplied&lt;/strong&gt;: last night's filter goes straight onto this morning's file. Section 2's "from zero every time" disappears (&lt;a href="https://uvp.y42u.net/en/blog/uwview-filter-popup-jump-save-context-en/" rel="noopener noreferrer"&gt;the filter popup&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Drill-down search&lt;/strong&gt;: filter a result by another term, and another. &lt;strong&gt;The original line numbers survive to the last step.&lt;/strong&gt; Section 1's "started with &lt;code&gt;ERROR&lt;/code&gt;, now I also want &lt;code&gt;WARN&lt;/code&gt;" becomes something you can do &lt;strong&gt;afterwards&lt;/strong&gt;, without racing the stream (&lt;a href="https://uvp.y42u.net/en/blog/uvp-drilldown-search-en/" rel="noopener noreferrer"&gt;drill-down search&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;±N is independent per step&lt;/strong&gt;: ±1 while narrowing, ±200 while reading. Section 3's "found the reboot line, now show me the 100 lines before it" works directly (the free version is fixed at ±1; a variable ±N is Pro).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sequence search&lt;/strong&gt;: find only the places where &lt;code&gt;w1 → w2 → w3&lt;/code&gt; appear in that order. Section 3's planned and unplanned reboots can be separated by the order of the messages that precede them (&lt;a href="https://uvp.y42u.net/en/blog/uvp-sequence-search-en/" rel="noopener noreferrer"&gt;how it works&lt;/a&gt;). Honestly: each step scans the text from the previous position, so &lt;strong&gt;it takes about as long as a full-text search.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tally (frequency ranking)&lt;/strong&gt;: counts per captured value, and clicking a row descends to where it occurred. It's section 3's reboot count, with the difference that &lt;strong&gt;you can get back to the actual lines&lt;/strong&gt; after counting.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stored at roughly 1/9 and still searchable&lt;/strong&gt;: section 4's 40 GB an hour stays compressed and searchable, without &lt;code&gt;zgrep&lt;/code&gt;'s decompress-as-you-scan on every query (&lt;a href="https://uvp.y42u.net/en/blog/uwview-ps08-compressed-archive-search-en/" rel="noopener noreferrer"&gt;Part 8&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Larger files&lt;/strong&gt;: 258.68 GB and 4.5 billion lines measured on Pro (one specific setup; results vary).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The honest limits
&lt;/h3&gt;

&lt;p&gt;This article is about following a log, so the most important limit comes first.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;UwView Pro does not have real-time tail.&lt;/strong&gt; Following appends lives in the free edition of UwView. Pro manages edits as a diff file (&lt;code&gt;.ewvz&lt;/code&gt;), which sits badly with an original that keeps changing, so the feature isn't there. &lt;strong&gt;If you need to follow a log while it's being written, use the free version&lt;/strong&gt;; if you only need to reopen and read to the latest, Pro does that fine. Worth checking before you buy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No alerting and no notifications.&lt;/strong&gt; "Page me when &lt;code&gt;FATAL&lt;/code&gt; appears" is monitoring's job. Section 1 assumes a human is watching.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No automated cross-log correlation.&lt;/strong&gt; Lining section 2's test log up against the monitoring graph belongs to another tool.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No automatic stripping of control characters or ANSI escapes.&lt;/strong&gt; Section 3's capture is displayed as it is — read through, not scrubbed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No involvement in log levels or rotation.&lt;/strong&gt; How much section 4 emits is the application's business.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lines display up to the first 8,192 characters.&lt;/strong&gt; On a file where one line gets extremely long — section 3's bare-CR capture, for instance — the rest is elided.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Text only.&lt;/strong&gt; A capture recorded in binary has to be written out as text first.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All four items above are read-only work. Non-destructive diff editing (Edit Upgrade) exists as a separate licence, but what you need here is the View side.&lt;/p&gt;

&lt;h3&gt;
  
  
  The same things, from the command line
&lt;/h3&gt;

&lt;p&gt;v1.6.0 added a &lt;code&gt;uvp&lt;/code&gt; command (and &lt;code&gt;uvf&lt;/code&gt; for the free build). It uses &lt;strong&gt;the same &lt;code&gt;.uwvz&lt;/code&gt; as the GUI&lt;/strong&gt;, so an index built from the shell is already there when you open the file in the app. Mapped onto this article's four parts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uvp app.log &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'ERROR|FATAL'&lt;/span&gt; &lt;span class="nt"&gt;-C&lt;/span&gt; 5

uvp app.log &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'ERROR|WARN|FATAL'&lt;/span&gt; &lt;span class="s1"&gt;'txnId=7f3a'&lt;/span&gt; &lt;span class="nt"&gt;-C&lt;/span&gt; 5

uvp run.log &lt;span class="s1"&gt;'2026-09-13T03:'&lt;/span&gt; &lt;span class="s1"&gt;'latency'&lt;/span&gt; &lt;span class="nt"&gt;-C&lt;/span&gt; 10 &lt;span class="nt"&gt;-out&lt;/span&gt; 3am-latency.txt.gz

uvp capture.log &lt;span class="nt"&gt;-seq&lt;/span&gt; &lt;span class="s1"&gt;'reboot: Restarting system,Booting Linux'&lt;/span&gt; &lt;span class="nt"&gt;-C&lt;/span&gt; 10

uvp app.log.uwvz &lt;span class="nt"&gt;-uniq&lt;/span&gt; &lt;span class="s1"&gt;'txnId=([0-9a-f]+)'&lt;/span&gt; &lt;span class="nt"&gt;-head&lt;/span&gt; 20

uvp app.log.uwvz &lt;span class="s1"&gt;'txnId=7f3a'&lt;/span&gt; &lt;span class="nt"&gt;-open&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Exit codes are grep's — &lt;strong&gt;0 found, 1 not found&lt;/strong&gt; — plus &lt;strong&gt;2 when the 1,000,000-hit cap is exceeded&lt;/strong&gt;. Put &lt;code&gt;if uvp run.log 'FATAL'; then&lt;/code&gt; at the end of section 2's overnight job and the morning starts with the one thing you needed to know. Stated plainly, though: &lt;strong&gt;there is no tail equivalent in the CLI either.&lt;/strong&gt; Following a log while it's being written is the free UwView's real-time tail; what &lt;code&gt;uvp&lt;/code&gt; covers is the other side — taking the stream back, fast, in a form you can keep.&lt;/p&gt;

&lt;p&gt;Stated honestly: &lt;strong&gt;on the first question ripgrep is 15–20% faster&lt;/strong&gt;, because &lt;code&gt;uvp&lt;/code&gt; builds its index first. On a 3 GB file that fits in RAM, rg stays ahead on the second question too. &lt;code&gt;uvp&lt;/code&gt; pays off past 10 GB, when you ask &lt;strong&gt;the same file more than one question&lt;/strong&gt; (&lt;a href="https://uvp.y42u.net/en/blog/uvp-cli-release-vs-ripgrep-en/" rel="noopener noreferrer"&gt;the measurements&lt;/a&gt;; Mac M4, external USB SSD, OpenStreetMap XML — one setup, results vary).&lt;/p&gt;

&lt;p&gt;And if a huge log is eating your disk and you want it &lt;strong&gt;compressed for storage while staying searchable at speed, give &lt;a href="https://uvp.y42u.net/en/pro-en/-en" rel="noopener noreferrer"&gt;UwView Pro&lt;/a&gt; a look&lt;/strong&gt; — persistent index, compressed-cache search, and ~1/9 storage make both reopening and searching a step faster (all OS, one-time or monthly).&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Part 1: four go-to tools that sink under huge files: &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps01-huge-file-tool-limits-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uwview-ps01-huge-file-tool-limits-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 3: four character-encoding traps and how to isolate them: &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps03-japanese-encoding-traps-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uwview-ps03-japanese-encoding-traps-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 5: four practices for living with development logs: &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps05-debug-log-practices-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uwview-ps05-debug-log-practices-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 7: four ways to read a timestamp as a weapon: &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps07-timestamp-driven-triage-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uwview-ps07-timestamp-driven-triage-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 8: keeping logs compressed and still searchable: &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps08-compressed-archive-search-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uwview-ps08-compressed-archive-search-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 10: four things to set up for the you at 2 a.m.: &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps10-oncall-night-preparation-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uwview-ps10-oncall-night-preparation-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 16: four principles for preserving, excerpting, and proving integrity: &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps16-log-as-evidence-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uwview-ps16-log-as-evidence-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 17: four points of friction between logs and your dev environment: &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps17-dev-env-log-friction-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uwview-ps17-dev-env-log-friction-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 18: four fields where machines write the logs: &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps18-device-generated-logs-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uwview-ps18-device-generated-logs-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Why search results got their own window (the filter popup): &lt;a href="https://uvp.y42u.net/en/blog/uwview-filter-popup-jump-save-context-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uwview-filter-popup-jump-save-context-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;We shipped a &lt;code&gt;uvp&lt;/code&gt; command (measured against ripgrep): &lt;a href="https://uvp.y42u.net/en/blog/uvp-cli-release-vs-ripgrep-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uvp-cli-release-vs-ripgrep-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Drill-down search — narrowing a result by another term: &lt;a href="https://uvp.y42u.net/en/blog/uvp-drilldown-search-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uvp-drilldown-search-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Sequence search — finding only what appears in that order: &lt;a href="https://uvp.y42u.net/en/blog/uvp-sequence-search-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uvp-sequence-search-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Source code (GitHub): &lt;a href="https://github.com/amru195704/UwView" rel="noopener noreferrer"&gt;https://github.com/amru195704/UwView&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;From the developer&lt;/strong&gt;: a full list of my apps, Kindle books, and open-source work is on &lt;a href="https://github.com/amru195704" rel="noopener noreferrer"&gt;GitHub: amru195704&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A note&lt;/strong&gt;&lt;br&gt;
This article is provided for reference and makes no guarantee of accuracy or completeness. Line counts, sizes, filenames, log contents, and configuration values are illustrative and do not describe any real product or incident. The behaviour of &lt;code&gt;tail&lt;/code&gt;, &lt;code&gt;grep&lt;/code&gt;, &lt;code&gt;sed&lt;/code&gt;, &lt;code&gt;awk&lt;/code&gt;, &lt;code&gt;tr&lt;/code&gt;, &lt;code&gt;cat&lt;/code&gt;, &lt;code&gt;stdbuf&lt;/code&gt;, &lt;code&gt;tee&lt;/code&gt;, &lt;code&gt;logrotate&lt;/code&gt;, &lt;code&gt;tmux&lt;/code&gt;, and &lt;code&gt;watch&lt;/code&gt; varies by implementation (GNU/BSD/busybox), version, and build options, as do option names and defaults — in particular the difference between &lt;code&gt;tail -f&lt;/code&gt; and &lt;code&gt;tail -F&lt;/code&gt;, and whether &lt;code&gt;--line-buffered&lt;/code&gt; or &lt;code&gt;stdbuf&lt;/code&gt; is available at all. Check your own &lt;code&gt;man&lt;/code&gt; pages and the official documentation. Before changing log levels in production, confirm disk capacity, rotation settings, and performance impact, and follow your organisation's change-management process. Measured figures come from one specific setup and are not a guarantee of the same result. If you spot an error, a comment is welcome and I'll check and correct it.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>logging</category>
      <category>devops</category>
      <category>sre</category>
      <category>linux</category>
    </item>
    <item>
      <title>Ten Thousand Sensors, 1.4 Billion Lines a Day — Four Fields Where Machines Write the Logs</title>
      <dc:creator>y4u</dc:creator>
      <pubDate>Mon, 14 Sep 2026 02:57:06 +0000</pubDate>
      <link>https://dev.to/amru195704/ten-thousand-sensors-14-billion-lines-a-day-four-fields-where-machines-write-the-logs-hoh</link>
      <guid>https://dev.to/amru195704/ten-thousand-sensors-14-billion-lines-a-day-four-fields-where-machines-write-the-logs-hoh</guid>
      <description>&lt;p&gt;1.44 billion lines. In one day.&lt;/p&gt;

&lt;p&gt;That's what ten thousand sensors produce when each one emits 100 metrics, one per line, every minute. &lt;strong&gt;The fault is in one of those sensors, during one of those minutes.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Four fields, four ways of looking for it: IoT, medical devices, PLCs on a factory line, and research simulation output. Completely different worlds — and they get stuck in exactly the same place.&lt;/p&gt;

&lt;p&gt;Unlike logs a person wrote, &lt;strong&gt;logs a machine writes were never meant to be read.&lt;/strong&gt; That single fact produces all four problems.&lt;/p&gt;

&lt;h2&gt;
  
  
  Up front: UwView Pro opens a 258.68 GB, 4.5-billion-line log and displays and searches all of it from the moment it opens, so even when line counts multiply out as devices × metrics × frequency you can follow the original, unsplit, by eye. It saves the index and the compression, so the second open comes back instantly with line numbers, and the file stays searchable at roughly 1/9 the storage (measured on one specific setup; results vary — details at the end)
&lt;/h2&gt;




&lt;h2&gt;
  
  
  1. IoT: line count is devices × metrics × frequency
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Situation
&lt;/h3&gt;

&lt;p&gt;Ten thousand sensors, reporting once a minute.&lt;/p&gt;

&lt;p&gt;They're stored long-format, one reading per line. Each device emits 100 metrics — temperature, humidity, voltage, current, and so on — as 100 separate lines per report, so a day comes to 10,000 × 1,440 × 100 = &lt;strong&gt;1.44 billion lines.&lt;/strong&gt; The lines are short, so it's only tens of gigabytes; it's the line count that goes up an order of magnitude.&lt;/p&gt;

&lt;p&gt;The plant tells you "one unit in Building 3 was behaving strangely yesterday evening." Nobody knows the device_id.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why it happens
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Machine logs are short lines in enormous numbers.&lt;/strong&gt; Application logs written by people are the opposite — long lines, comparatively few of them. Same phrase, "huge log," opposite shape.&lt;/p&gt;

&lt;p&gt;Long format compounds it. Each line is just &lt;code&gt;timestamp,device_id,metric,value&lt;/code&gt;, so &lt;strong&gt;reading one device's state at one moment means reading 100 lines.&lt;/strong&gt; Pivoting them side by side is possible, but that's work you do &lt;em&gt;after&lt;/em&gt; you know where the anomaly is.&lt;/p&gt;

&lt;p&gt;And the way anomalies surface makes it worse. Averaging ten thousand devices &lt;strong&gt;buries the one faulty unit.&lt;/strong&gt; Aggregation answers "is the fleet healthy"; it does not answer "which one."&lt;/p&gt;

&lt;h3&gt;
  
  
  Working with general-purpose tools, and where it stops
&lt;/h3&gt;

&lt;p&gt;The obvious approach is to narrow first, then look.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Cut roughly by time (two hours of that evening)&lt;/span&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'2026-09-11T1[6-7]:'&lt;/span&gt; sensor-20260911.csv &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; evening.csv

&lt;span class="c"&gt;# Look at one metric&lt;/span&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s1"&gt;',voltage,'&lt;/span&gt; evening.csv | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-50&lt;/span&gt;

&lt;span class="c"&gt;# Pick off outliers mechanically (column 4 is the value)&lt;/span&gt;
&lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="nt"&gt;-F&lt;/span&gt;, &lt;span class="s1"&gt;'$4+0 &amp;gt; 250 || $4+0 &amp;lt; 180'&lt;/span&gt; evening.csv | &lt;span class="nb"&gt;head&lt;/span&gt;

&lt;span class="c"&gt;# Which device_id shows up most&lt;/span&gt;
&lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="nt"&gt;-F&lt;/span&gt;, &lt;span class="s1"&gt;'$4+0 &amp;gt; 250 {print $2}'&lt;/span&gt; evening.csv | &lt;span class="nb"&gt;sort&lt;/span&gt; | &lt;span class="nb"&gt;uniq&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-rn&lt;/span&gt; | &lt;span class="nb"&gt;head&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When that lands, it's fast. When it doesn't, three limits show up.&lt;/p&gt;

&lt;p&gt;First, &lt;strong&gt;you have to pick the threshold before you look.&lt;/strong&gt; The example calls 180–250 normal, but that range is something you learn &lt;em&gt;from&lt;/em&gt; the anomaly. The order is backwards. Guess wrong and you get either zero hits or several million — neither is useful.&lt;/p&gt;

&lt;p&gt;Second, &lt;strong&gt;narrowing hides the neighbours.&lt;/strong&gt; Pull only &lt;code&gt;,voltage,&lt;/code&gt; and the current and temperature readings from the same instant drop out of view. Device faults very often live in the &lt;em&gt;relationship between&lt;/em&gt; metrics, and the filter severs exactly that. Adding ±N lines of context doesn't fix it either: in long format, "the same device's other metrics" aren't necessarily adjacent lines.&lt;/p&gt;

&lt;p&gt;Third, &lt;strong&gt;line numbers disappear.&lt;/strong&gt; What line of the original is line 1 of &lt;code&gt;evening.csv&lt;/code&gt;? When you want to tell the vendor "behaviour changes from this line," you no longer have the coordinate (&lt;a href="https://uvp.y42u.net/en/blog/uwview-ps16-log-as-evidence-en/" rel="noopener noreferrer"&gt;Part 16&lt;/a&gt; covered exactly this cost of excerpting).&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Medical: doing the first-pass triage in-house instead of handing it to the vendor
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Situation
&lt;/h3&gt;

&lt;p&gt;An analyser halts about once a week for no stated reason.&lt;/p&gt;

&lt;p&gt;You contact the vendor; they ask for the logs; you send them; the answer comes a week later. The machine keeps halting in the meantime. The log exported from the device's service mode is 12 GB.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why it happens
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Instrument logs are written on the assumption that only the people who built the instrument will read them.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Internal codes.&lt;/strong&gt; Lines like &lt;code&gt;E-1042&lt;/code&gt; or &lt;code&gt;SUBSYS3 STATE=7&lt;/code&gt;. The meanings live only in the service manual.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Several subsystems in one stream.&lt;/strong&gt; Control, transport, optics, and communications all write to the same file in time order. The lines are chronological; the context is per-subsystem (&lt;a href="https://uvp.y42u.net/en/blog/uwview-ps05-debug-log-practices-en/" rel="noopener noreferrer"&gt;Part 5&lt;/a&gt; covered the same shape with threads).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Old encodings.&lt;/strong&gt; Instruments from domestic manufacturers may still emit Shift_JIS, which breaks the moment a UTF-8-assuming tool opens it (&lt;a href="https://uvp.y42u.net/en/blog/uwview-ps03-japanese-encoding-traps-en/" rel="noopener noreferrer"&gt;Part 3&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Export restrictions.&lt;/strong&gt; If patient or examination identifiers are mixed in, you can't simply upload the file to a cloud analysis service.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The real reason these investigations get handed off isn't difficulty. It's that &lt;strong&gt;nobody in the building has a tool that opens 12 GB.&lt;/strong&gt; You can't open it, so you can't look; you can't look, so the ticket says no more than "once a week, roughly in the evening"; thin information means more round trips. That's the loop.&lt;/p&gt;

&lt;h3&gt;
  
  
  Working with general-purpose tools, and where it stops
&lt;/h3&gt;

&lt;p&gt;This is about as far as you get on your own.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Check the encoding before converting — never convert on a guess&lt;/span&gt;
file &lt;span class="nt"&gt;-i&lt;/span&gt; device.log
iconv &lt;span class="nt"&gt;-f&lt;/span&gt; CP932 &lt;span class="nt"&gt;-t&lt;/span&gt; UTF-8 device.log &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; device-utf8.log

&lt;span class="c"&gt;# Which error codes appear, and how often&lt;/span&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-oE&lt;/span&gt; &lt;span class="s1"&gt;'E-[0-9]{4}'&lt;/span&gt; device-utf8.log | &lt;span class="nb"&gt;sort&lt;/span&gt; | &lt;span class="nb"&gt;uniq&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-rn&lt;/span&gt; | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-20&lt;/span&gt;

&lt;span class="c"&gt;# Cut out what surrounds the halt&lt;/span&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nt"&gt;-B&lt;/span&gt; 200 &lt;span class="nt"&gt;-A&lt;/span&gt; 20 &lt;span class="s1"&gt;'E-1042'&lt;/span&gt; device-utf8.log | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-100&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both &lt;code&gt;file&lt;/code&gt;'s detection and &lt;code&gt;iconv&lt;/code&gt;'s behaviour vary by implementation and version, so check your own &lt;code&gt;man&lt;/code&gt; pages.&lt;/p&gt;

&lt;p&gt;Three limits.&lt;/p&gt;

&lt;p&gt;First, &lt;strong&gt;running &lt;code&gt;iconv&lt;/code&gt; adds an intermediate file whose line numbers may not match the original.&lt;/strong&gt; Instrument logs in particular may use CRLF endings, or pad the tail with null bytes.&lt;/p&gt;

&lt;p&gt;Second, &lt;strong&gt;you can count occurrences but not order.&lt;/strong&gt; &lt;code&gt;uniq -c&lt;/code&gt; throws the sequence away, and instrument faults cascade — what you actually want is &lt;strong&gt;which subsystem broke first&lt;/strong&gt; (&lt;a href="https://uvp.y42u.net/en/blog/uwview-ps02-log-causality-tracing-en/" rel="noopener noreferrer"&gt;Part 2&lt;/a&gt; is the same problem).&lt;/p&gt;

&lt;p&gt;Third, &lt;strong&gt;these logs become maintenance records.&lt;/strong&gt; A fault investigation ends up supporting a service report. If all you kept is a processed copy, that support is weaker (&lt;a href="https://uvp.y42u.net/en/blog/uwview-ps16-log-as-evidence-en/" rel="noopener noreferrer"&gt;Part 16&lt;/a&gt;).&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Manufacturing: the PLC alarm history won't open in the vendor's own software
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Situation
&lt;/h3&gt;

&lt;p&gt;The line stopped. To find out why, you export the PLC's alarm history.&lt;/p&gt;

&lt;p&gt;Out comes a 3.8 GB CSV. The manufacturer's own history viewer spins for a while and then reports that it is out of memory. The spreadsheet doesn't even get that far — the 1,048,576-row ceiling means it can't take the whole file in (&lt;a href="https://uvp.y42u.net/en/blog/uwview-ps01-huge-file-tool-limits-en/" rel="noopener noreferrer"&gt;Part 1&lt;/a&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  Why it happens
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Vendor software is built for the volume that machine was expected to produce.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The expectation usually breaks for one of these reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Contact chatter.&lt;/strong&gt; Micro-vibration on a contact records the same alarm dozens of times per second, swelling the day to millions of lines.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Line expansion.&lt;/strong&gt; More points are collected than before, while the retention setting stays where it was years ago.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Log everything.&lt;/strong&gt; Somebody enabled all I/O points "so we won't be stuck later," and the alarm history quietly became a data logger.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On top of that, most vendor viewers &lt;strong&gt;read the entire file into memory before displaying anything.&lt;/strong&gt; At 3.8 GB, installed RAM becomes the ceiling — and the machine on the floor is an office laptop that can't be upgraded.&lt;/p&gt;

&lt;p&gt;Two more wrinkles specific to the factory floor: &lt;strong&gt;alarm names are often Japanese&lt;/strong&gt; in Shift_JIS, and &lt;strong&gt;bare CR line endings&lt;/strong&gt; are still in service. With bare CR, what counts as "one line" changes from tool to tool, and line numbers stop agreeing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Working with general-purpose tools, and where it stops
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Look at both ends first (column order and time range)&lt;/span&gt;
&lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-5&lt;/span&gt; alarm.csv
&lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-5&lt;/span&gt; alarm.csv

&lt;span class="c"&gt;# Collapse repeats to see which alarms, and how many&lt;/span&gt;
&lt;span class="nb"&gt;cut&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;, &lt;span class="nt"&gt;-f3&lt;/span&gt; alarm.csv | &lt;span class="nb"&gt;uniq&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-rn&lt;/span&gt; | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-20&lt;/span&gt;

&lt;span class="c"&gt;# Pull out what surrounds the stop&lt;/span&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s1"&gt;'2026/09/11 14:2'&lt;/span&gt; alarm.csv | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-40&lt;/span&gt;

&lt;span class="c"&gt;# If nothing opens it, split it&lt;/span&gt;
&lt;span class="nb"&gt;split&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; 500000 alarm.csv part_
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three limits.&lt;/p&gt;

&lt;p&gt;First, &lt;strong&gt;&lt;code&gt;split&lt;/code&gt; cuts the context.&lt;/strong&gt; A cascade that crosses a boundary lands in two files, and each piece renumbers from 1, so the numbers no longer correspond to what the vendor viewer shows.&lt;/p&gt;

&lt;p&gt;Second, &lt;strong&gt;collapsing the chatter erases the fact that there was chatter.&lt;/strong&gt; &lt;code&gt;uniq -c&lt;/code&gt; makes it readable, but "480 occurrences in ten seconds" is a case where &lt;strong&gt;the density itself is the symptom.&lt;/strong&gt; Readability and evidential value pull in opposite directions.&lt;/p&gt;

&lt;p&gt;Third, &lt;strong&gt;there's no command line on that PC.&lt;/strong&gt; Control-system machines frequently forbid installing anything. "Copy it to the analysis PC" means starting an export-approval process (&lt;a href="https://uvp.y42u.net/en/blog/uwview-ps17-dev-env-log-friction-en/" rel="noopener noreferrer"&gt;Part 17&lt;/a&gt; is about exactly that friction).&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Research: look at the raw values before you plot them
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Situation
&lt;/h3&gt;

&lt;p&gt;A simulation ran for 48 hours and produced a 50 GB numeric log.&lt;/p&gt;

&lt;p&gt;You load it and plot it, and part of the curve is suspiciously flat. A bug, a genuine physical result, or an output-side problem? &lt;strong&gt;The only way to tell is to look at the raw numbers.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Why it happens
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Numeric logs break in ways that aggregation and plotting hide.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Four common forms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;NaN&lt;/code&gt; and &lt;code&gt;inf&lt;/code&gt; slipping in.&lt;/strong&gt; If the reader silently substitutes zero or a missing value, the plot simply looks flat.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inconsistent notation.&lt;/strong&gt; &lt;code&gt;1.0E-05&lt;/code&gt;, &lt;code&gt;1.0e-05&lt;/code&gt;, and &lt;code&gt;0.00001&lt;/code&gt; in the same file; some parsers quietly drop one form.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Column count changing mid-file.&lt;/strong&gt; Around a restart or a checkpoint resume, the header may be written again, or a column may appear.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A truncated tail.&lt;/strong&gt; When a job is killed at the wall-clock limit, &lt;strong&gt;the final line ends mid-line.&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Research logs also differ from operational logs in one decisive way: &lt;strong&gt;reproducing them takes 48 hours.&lt;/strong&gt; "It was broken, so I'll rerun it" is expensive, which makes it essential to check &lt;strong&gt;without disturbing the original&lt;/strong&gt; (&lt;a href="https://uvp.y42u.net/en/blog/uwview-ps13-huge-data-inspection-en/" rel="noopener noreferrer"&gt;Part 13&lt;/a&gt; is the same shape).&lt;/p&gt;

&lt;h3&gt;
  
  
  Working with general-purpose tools, and where it stops
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Did it finish cleanly? (look at the shape of the last line)&lt;/span&gt;
&lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-3&lt;/span&gt; sim-output.dat

&lt;span class="c"&gt;# Where does NaN / inf start (line number of the first one)&lt;/span&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt; 1 &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'NaN|nan|inf'&lt;/span&gt; sim-output.dat

&lt;span class="c"&gt;# Did the column count change partway through&lt;/span&gt;
&lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'{print NF}'&lt;/span&gt; sim-output.dat | &lt;span class="nb"&gt;uniq&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt;

&lt;span class="c"&gt;# Was the header written more than once&lt;/span&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s1"&gt;'^#'&lt;/span&gt; sim-output.dat
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three limits.&lt;/p&gt;

&lt;p&gt;First, &lt;strong&gt;&lt;code&gt;awk '{print NF}'&lt;/code&gt; scans all 50 GB.&lt;/strong&gt; That's minutes, during which you learn nothing. And the answer is only "the column count changed" — &lt;strong&gt;seeing where it changed means scanning again.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Second, &lt;strong&gt;&lt;code&gt;grep -n&lt;/code&gt; gives you the line number but not the neighbourhood.&lt;/strong&gt; Knowing there's a NaN at line 1,203,884,112 doesn't show you the 200 lines around it; for that you write a &lt;code&gt;sed -n&lt;/code&gt; range and scan from the top again (&lt;a href="https://uvp.y42u.net/en/blog/uwview-ps15-cli-craft-limits-en/" rel="noopener noreferrer"&gt;Part 15&lt;/a&gt; is where pipeline craft starts to hurt).&lt;/p&gt;

&lt;p&gt;Third, &lt;strong&gt;the compute cluster and your analysis environment are different machines.&lt;/strong&gt; Reading it in place with &lt;code&gt;less&lt;/code&gt; over &lt;code&gt;ssh&lt;/code&gt; means waiting a round trip for every screen. Pulling it local means transferring 50 GB and having room for it.&lt;/p&gt;




&lt;h2&gt;
  
  
  What all four had in common
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;What the log really is&lt;/th&gt;
&lt;th&gt;Why the line count explodes&lt;/th&gt;
&lt;th&gt;General-purpose approach&lt;/th&gt;
&lt;th&gt;What's left over&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;IoT sensors&lt;/td&gt;
&lt;td&gt;Long-format time series, 4 columns&lt;/td&gt;
&lt;td&gt;devices × metrics × frequency&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;grep&lt;/code&gt; by time and metric, &lt;code&gt;awk&lt;/code&gt; for outliers&lt;/td&gt;
&lt;td&gt;Threshold must be chosen first. Filtering hides the neighbouring metrics&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Medical devices&lt;/td&gt;
&lt;td&gt;Internal codes, subsystems interleaved&lt;/td&gt;
&lt;td&gt;Continuous operation, all subsystems in one file&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;iconv&lt;/code&gt; to convert, &lt;code&gt;uniq -c&lt;/code&gt; to count&lt;/td&gt;
&lt;td&gt;Order is lost. Only a processed copy survives&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Manufacturing PLC&lt;/td&gt;
&lt;td&gt;Alarm history CSV&lt;/td&gt;
&lt;td&gt;Contact chatter, expansion, log-everything&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;cut&lt;/code&gt; / &lt;code&gt;uniq -c&lt;/code&gt; / &lt;code&gt;split&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Splitting cuts the cascade. The density that &lt;em&gt;is&lt;/em&gt; the symptom gets collapsed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Research simulation&lt;/td&gt;
&lt;td&gt;Continuous numeric output&lt;/td&gt;
&lt;td&gt;Long jobs × high output frequency&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;tail&lt;/code&gt; / &lt;code&gt;grep -n&lt;/code&gt; / &lt;code&gt;awk&lt;/code&gt; to inspect&lt;/td&gt;
&lt;td&gt;Minutes per scan. Reading around a hit means scanning again&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Four unrelated fields. They jam in the same place because &lt;strong&gt;machine-written logs share three properties:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Line counts multiply.&lt;/strong&gt; Raise devices, metrics, or frequency and it multiplies against the others, moving the order of magnitude. Logs people write grow by addition; these grow by multiplication.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Anomalies are local.&lt;/strong&gt; They don't appear in the aggregate. They appear in one device, one minute, one line. Aggregation answers "healthy or not," never "where."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The original can't be damaged.&lt;/strong&gt; Maintenance records in medicine, quality records in manufacturing, reproducibility in research, contractual retention in IoT. In all four there are moments when a processed copy isn't enough.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With those three together, &lt;strong&gt;"narrow it down, then look" stops working as an order of operations.&lt;/strong&gt; The filter depends on seeing the anomaly, and seeing the anomaly requires the filter. That's the dead end shared by all four.&lt;/p&gt;

&lt;p&gt;What's needed is a tool that lets you reverse the order.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Open the original, whole&lt;/strong&gt; — look before narrowing. 1.44 billion lines, a 3.8 GB CSV, a 50 GB numeric dump, all the same.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Narrow while looking&lt;/strong&gt; — take a filtered result and filter it again, &lt;strong&gt;keeping the original line numbers&lt;/strong&gt; the whole way down.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stay on this machine&lt;/strong&gt; — patient identifiers, quality records, research data: none of it leaves.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Back to the 1.44 billion lines. That number doesn't mean "too much to read." It means &lt;strong&gt;you can't throw away the other 1.44 billion until you've found the one device and the one minute.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The tool I use
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://uvp.y42u.net/en/" rel="noopener noreferrer"&gt;UwView&lt;/a&gt; (free), which I develop, is a viewer that &lt;strong&gt;displays, scrolls, and searches&lt;/strong&gt; huge text from the moment it opens. It never loads the whole file into memory, so it opens files larger than RAM. The index is built in the background; when it finishes, line numbers appear.&lt;/p&gt;

&lt;p&gt;Of the three requirements above, the free version already covers the first and the third.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;How much you can read isn't decided by RAM.&lt;/strong&gt; An office laptop opens files of tens of gigabytes (measured ceiling on the free version: 47.73 GB, about 890 million lines — one specific setup). Section 3's "out of memory" no longer has a premise.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It never writes to the original, and never splits or excerpts it.&lt;/strong&gt; No &lt;code&gt;split&lt;/code&gt; from section 3, no intermediate files from section 4 — which means &lt;strong&gt;line numbers stay the original's.&lt;/strong&gt; The coordinate you give the vendor in section 1, the maintenance record in section 2, reproducibility in section 4: all of them rest on the original's line numbers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Everything runs on your own machine.&lt;/strong&gt; The file is never sent anywhere. For section 2's instrument logs containing patient data and section 3's export approvals, that's often a precondition rather than a nicety.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Encoding can be switched while the file is open&lt;/strong&gt; (UTF-8 / Shift-JIS (CP932) / EUC-JP / UTF-16, auto-detected). Sections 2 and 3's Shift_JIS instrument logs open without an &lt;code&gt;iconv&lt;/code&gt; intermediate (&lt;a href="https://uvp.y42u.net/en/blog/uwview-ps03-japanese-encoding-traps-en/" rel="noopener noreferrer"&gt;Part 3&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Highlighting colours lines rather than removing them.&lt;/strong&gt; Section 2's interleaved subsystems can be untangled by eye with colour per subsystem, and because nothing is removed, &lt;strong&gt;the density stays visible&lt;/strong&gt; — which is exactly what section 3's chatter needs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The second requirement — narrowing while looking — and the case of reopening the same log repeatedly are &lt;a href="https://uvp.y42u.net/en/pro-en/" rel="noopener noreferrer"&gt;UwView Pro&lt;/a&gt; territory.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Drill-down search&lt;/strong&gt;: filter a result by another term, and another. &lt;strong&gt;The original line numbers survive to the last step.&lt;/strong&gt; Section 1's "narrow by time without a device_id, then by metric, then by magnitude" works without cutting the original (&lt;a href="https://uvp.y42u.net/en/blog/uvp-drilldown-search-en/" rel="noopener noreferrer"&gt;drill-down search&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;±N is independent per step&lt;/strong&gt;: ±1 while narrowing, ±200 while reading. Section 4's "I have the NaN's line number but have to scan again to read around it" disappears (the free version is fixed at ±1; a variable ±N is Pro).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sequence search&lt;/strong&gt;: find only the places where &lt;code&gt;w1 → w2 → w3&lt;/code&gt; appear in that order. Section 2's "which subsystem broke first" becomes a condition over the order of the codes (&lt;a href="https://uvp.y42u.net/en/blog/uvp-sequence-search-en/" rel="noopener noreferrer"&gt;how it works&lt;/a&gt;). Honestly: each step scans the text from the previous position, so &lt;strong&gt;it takes about as long as a full-text search.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tally (frequency ranking)&lt;/strong&gt;: counts per captured value, and clicking a row descends to where it occurred. It's section 2's &lt;code&gt;uniq -c&lt;/code&gt;, with the difference that &lt;strong&gt;you can get back to the actual lines&lt;/strong&gt; after counting.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The index and the compression are saved&lt;/strong&gt;: from the second open onward, the same file comes back &lt;strong&gt;instantly, with line numbers&lt;/strong&gt; (0.02–0.07 s measured on a 47.73 GB text file; one setup, results vary). That's what a line-stop investigation like section 3 needs, since &lt;strong&gt;the same history gets reopened for days.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stored at roughly 1/9 and still searchable&lt;/strong&gt;: section 1's 1.44 billion lines a day and section 4's 50 GB output stay compressed and searchable, without &lt;code&gt;zgrep&lt;/code&gt;'s decompress-as-you-scan on every query (&lt;a href="https://uvp.y42u.net/en/blog/uwview-ps08-compressed-archive-search-en/" rel="noopener noreferrer"&gt;Part 8&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Larger files&lt;/strong&gt;: 258.68 GB and 4.5 billion lines measured on Pro (one specific setup; results vary).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The honest limits
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;UwView is a viewer. It is not a time-series analysis tool and not an analytics platform for data loggers.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No statistics.&lt;/strong&gt; Means, sums, standard deviations, moving averages — section 1's outlier test and section 4's sanity checks belong to &lt;code&gt;awk&lt;/code&gt; or your analysis environment. What this does is &lt;strong&gt;find the location of the anomaly fast, by eye.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No plotting.&lt;/strong&gt; Section 4's chart belongs to another tool. The premise of this article is checking the raw values &lt;em&gt;before&lt;/em&gt; the plot.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No pivoting from long to wide.&lt;/strong&gt; Section 1's reshape is out of scope.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No understanding of CSV columns.&lt;/strong&gt; Section 3's alarm history is treated as text. "Show me only the third column" isn't something it does.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No decoding of instrument codes.&lt;/strong&gt; The meaning of section 2's &lt;code&gt;E-1042&lt;/code&gt; lives in the service manual. All this gives you is reading the order and context of those codes quickly.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No live tailing, no alerting, no automated cross-log correlation.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Text only.&lt;/strong&gt; Instruments that record in a binary format have to be exported to text first.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For completeness: non-destructive diff editing (Edit Upgrade) exists as a separate licence, but &lt;strong&gt;all four items above are read-only work.&lt;/strong&gt; What you need here is the View side.&lt;/p&gt;

&lt;p&gt;And if a huge log is eating your disk and you want it &lt;strong&gt;compressed for storage while staying searchable at speed, give &lt;a href="https://uvp.y42u.net/en/pro-en/" rel="noopener noreferrer"&gt;UwView Pro&lt;/a&gt; a look&lt;/strong&gt; — persistent index, compressed-cache search, and ~1/9 storage make both reopening and searching a step faster (all OS, one-time or monthly).&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Part 1: four go-to tools that sink under huge files: &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps01-huge-file-tool-limits-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uwview-ps01-huge-file-tool-limits-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 2: four techniques for tracing causality in logs: &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps02-log-causality-tracing-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uwview-ps02-log-causality-tracing-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 3: four character-encoding traps and how to isolate them: &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps03-japanese-encoding-traps-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uwview-ps03-japanese-encoding-traps-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 5: four practices for living with development logs: &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps05-debug-log-practices-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uwview-ps05-debug-log-practices-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 8: keeping logs compressed and still searchable: &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps08-compressed-archive-search-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uwview-ps08-compressed-archive-search-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 13: four techniques for inspecting huge data: &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps13-huge-data-inspection-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uwview-ps13-huge-data-inspection-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 15: four limits of command-line craft: &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps15-cli-craft-limits-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uwview-ps15-cli-craft-limits-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 16: four principles for preserving, excerpting, and proving integrity: &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps16-log-as-evidence-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uwview-ps16-log-as-evidence-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 17: four points of friction between logs and your dev environment: &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps17-dev-env-log-friction-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uwview-ps17-dev-env-log-friction-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Drill-down search — narrowing a result by another term: &lt;a href="https://uvp.y42u.net/en/blog/uvp-drilldown-search-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uvp-drilldown-search-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Sequence search — finding only what appears in that order: &lt;a href="https://uvp.y42u.net/en/blog/uvp-sequence-search-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uvp-sequence-search-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Source code (GitHub): &lt;a href="https://github.com/amru195704/UwView" rel="noopener noreferrer"&gt;https://github.com/amru195704/UwView&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;From the developer&lt;/strong&gt;: a full list of my apps, Kindle books, and open-source work is on &lt;a href="https://github.com/amru195704" rel="noopener noreferrer"&gt;GitHub: amru195704&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A note&lt;/strong&gt;&lt;br&gt;
This article is provided for reference and makes no guarantee of accuracy or completeness. Fault investigation on medical devices and production equipment must follow each device's manual and service documentation and the manufacturer's instructions; nothing here is intended to replace vendor maintenance, only to support first-pass triage on site. Handling of patient data, quality records, and research data must follow your organisation's policy and applicable law. Line counts, sizes, error codes, and filenames are illustrative and do not describe any real product or incident. The behaviour of &lt;code&gt;grep&lt;/code&gt;, &lt;code&gt;awk&lt;/code&gt;, &lt;code&gt;cut&lt;/code&gt;, &lt;code&gt;split&lt;/code&gt;, &lt;code&gt;iconv&lt;/code&gt;, &lt;code&gt;file&lt;/code&gt;, &lt;code&gt;sort&lt;/code&gt;, and &lt;code&gt;uniq&lt;/code&gt; varies by implementation (GNU/BSD/busybox), version, and build options, as do option names and defaults — check your own &lt;code&gt;man&lt;/code&gt; pages and the official documentation. Measured figures come from one specific setup and are not a guarantee of the same result. If you spot an error, a comment is welcome and I'll check and correct it.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>iot</category>
      <category>logging</category>
      <category>devops</category>
      <category>datascience</category>
    </item>
    <item>
      <title>Before You Carry Production Logs Home — Four Points of Friction Between Logs and Your Dev Environment</title>
      <dc:creator>y4u</dc:creator>
      <pubDate>Mon, 14 Sep 2026 02:56:40 +0000</pubDate>
      <link>https://dev.to/amru195704/before-you-carry-production-logs-home-four-points-of-friction-between-logs-and-your-dev-2pn2</link>
      <guid>https://dev.to/amru195704/before-you-carry-production-logs-home-four-points-of-friction-between-logs-and-your-dev-2pn2</guid>
      <description>&lt;p&gt;A build log, 480,000 lines.&lt;/p&gt;

&lt;p&gt;One line in there explains the failure. &lt;strong&gt;What do you do before you reach for the scrollbar?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There are four places this gets stuck. All four happen when you bring a log &lt;strong&gt;into your own environment to read it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;CI build logs, the dump you carry home from production, file access across a WSL boundary, and the gap between one teammate's machine and another's. In none of these four cases is anything wrong with the log. What's wrong is &lt;strong&gt;the path between the log and the person reading it.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Up front: UwView opens a 480,000-line build log or an 80 GB dump without splitting it, and displays and searches it from the moment it opens. It never loads the whole file into memory, so how much you can read isn't decided by how much RAM you have. Pro saves the index and the compression, so from the second open onward the same file comes back instantly, with line numbers (0.02–0.07 s measured on a 47.73 GB text file; one setup, results vary). Everything runs on your own machine — the file is never sent anywhere (details at the end)
&lt;/h2&gt;




&lt;h2&gt;
  
  
  1. The build log is too long to find the failure in
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Situation
&lt;/h3&gt;

&lt;p&gt;The pipeline goes red. You open the log in the browser. 480,000 lines.&lt;/p&gt;

&lt;p&gt;Searching for &lt;code&gt;ERROR&lt;/code&gt; gives 137 hits. The first one says dependency resolution failed, which is a result, not a cause. Scrolling on, you notice &lt;strong&gt;the same log appears three times.&lt;/strong&gt; Retries.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why it happens
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;CI logs aren't written for humans to read.&lt;/strong&gt; Four things stack up.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Interleaved parallel jobs.&lt;/strong&gt; Several jobs' output lands in one stream. The lines are in time order; the &lt;em&gt;context&lt;/em&gt; is not (&lt;a href="https://uvp.y42u.net/en/blog/uwview-ps05-debug-log-practices-en/" rel="noopener noreferrer"&gt;Part 5&lt;/a&gt; covered the same shape with threads).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ANSI escapes.&lt;/strong&gt; Colour control characters sit inside the text, so &lt;code&gt;grep 'ERROR'&lt;/code&gt; sometimes misses. &lt;code&gt;[31mERROR&lt;/code&gt; does contain &lt;code&gt;ERROR&lt;/code&gt;, but any regex that anchors on what comes before or after falls apart.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Progress bars.&lt;/strong&gt; Output that overwrites itself with carriage returns becomes &lt;strong&gt;a single line of several megabytes&lt;/strong&gt; in a file. That "480,000 lines" figure is already a lie.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retries.&lt;/strong&gt; The same failure is recorded two or three times, and nothing on screen tells you which one came first.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On top of that, browser log viewers usually &lt;strong&gt;only hold part of the file&lt;/strong&gt; in a virtual scroller. &lt;code&gt;Ctrl+F&lt;/code&gt; reaches what has been loaded. "I searched and it wasn't there" is not the same as "it isn't there."&lt;/p&gt;

&lt;h3&gt;
  
  
  Working with general-purpose tools, and where it stops
&lt;/h3&gt;

&lt;p&gt;The basic move is to fetch the raw log and clean it up locally.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1) Pull the raw log with the CI's own CLI, bypassing the browser viewer&lt;/span&gt;
&lt;span class="c"&gt;#    -&amp;gt; build-raw.log&lt;/span&gt;

&lt;span class="c"&gt;# 2) Strip the colour control characters (ansi2txt from colorized-logs is easy)&lt;/span&gt;
ansi2txt &amp;lt; build-raw.log &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; build-plain.log

&lt;span class="c"&gt;# 3) Expand carriage returns into newlines (progress-bar lines unfold)&lt;/span&gt;
&lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="s1"&gt;'\r'&lt;/span&gt; &lt;span class="s1"&gt;'\n'&lt;/span&gt; &amp;lt; build-plain.log &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; build-lines.log

&lt;span class="c"&gt;# 4) Look at only the first failure, with context&lt;/span&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt; 1 &lt;span class="nt"&gt;-B&lt;/span&gt; 20 &lt;span class="nt"&gt;-A&lt;/span&gt; 5 &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'ERROR|FAIL'&lt;/span&gt; build-lines.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Command names and options vary by CI service and by implementation (GNU / BSD). If &lt;code&gt;ansi2txt&lt;/code&gt; isn't available, a &lt;code&gt;sed&lt;/code&gt; or &lt;code&gt;perl&lt;/code&gt; substitution does the same job — but &lt;strong&gt;how you write the escape character changes with the shell and the implementation&lt;/strong&gt;, so check your own &lt;code&gt;man&lt;/code&gt; pages.&lt;/p&gt;

&lt;p&gt;Three limits.&lt;/p&gt;

&lt;p&gt;First, &lt;strong&gt;every cleanup step produces another file.&lt;/strong&gt; You end up with &lt;code&gt;build-raw&lt;/code&gt;, &lt;code&gt;build-plain&lt;/code&gt;, &lt;code&gt;build-lines&lt;/code&gt; — and &lt;strong&gt;their line numbers no longer agree.&lt;/strong&gt; When the CI UI says "line 12,345," that's a different line in the cleaned-up file.&lt;/p&gt;

&lt;p&gt;Second, &lt;strong&gt;retention.&lt;/strong&gt; Most services keep logs for a few weeks. The moment you want to check "I think it failed in the same place last month," the comparison is gone.&lt;/p&gt;

&lt;p&gt;Third, &lt;strong&gt;it's still big afterwards.&lt;/strong&gt; Expanding carriage returns multiplies the line count. Your editor stalls trying to open it — straight back to &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps01-huge-file-tool-limits-en/" rel="noopener noreferrer"&gt;Part 1&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. The bug only happens in production — and carrying the log home has two constraints at once
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Situation
&lt;/h3&gt;

&lt;p&gt;It doesn't reproduce in dev, so you ask for the production log.&lt;/p&gt;

&lt;p&gt;The estimate comes back: 80 GB. An hour to transfer, and you have 60 GB free on the SSD. Then ops mentions that the log contains personal data.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why it happens
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Two constraints of different kinds apply simultaneously&lt;/strong&gt; — size and confidentiality — and each fix makes the other worse.&lt;/p&gt;

&lt;p&gt;To solve size, you narrow or you split. But &lt;strong&gt;the filter you'd narrow by is only knowable after you've found the cause.&lt;/strong&gt; You're carrying the log home &lt;em&gt;because&lt;/em&gt; you don't know, yet you're asked for the filter before you leave. That's the ordering contradiction. In practice you &lt;code&gt;grep&lt;/code&gt; out "just that day," and later discover the answer was outside it.&lt;/p&gt;

&lt;p&gt;To solve confidentiality, you mask — and masking is &lt;strong&gt;irreversible.&lt;/strong&gt; Once email addresses are redacted, "I want to correlate two actions by the same person" is no longer possible. And a masking regex tends to fall one of two ways: it leaks, or it breaks. Leaking is a disclosure incident; breaking means the JSON no longer parses.&lt;/p&gt;

&lt;h3&gt;
  
  
  Working with general-purpose tools, and where it stops
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Transfer compressed (pre-compressing beats on-the-fly compression on a thin link)&lt;/span&gt;
&lt;span class="nb"&gt;gzip&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; app.log &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; app.log.gz
&lt;span class="c"&gt;# then move app.log.gz with whatever transfer you normally use&lt;/span&gt;

&lt;span class="c"&gt;# If you narrow, keep the filter in the filename&lt;/span&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'2026-09-1[01]'&lt;/span&gt; app.log &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; app-20260910-11.log

&lt;span class="c"&gt;# Masking can't be made reversible; at least keep the original and mask a copy&lt;/span&gt;
&lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'s/[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+/MASKED/g'&lt;/span&gt; app.log &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; app-masked.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three limits.&lt;/p&gt;

&lt;p&gt;First, &lt;strong&gt;storing it compressed makes searching slow.&lt;/strong&gt; 80 GB becomes 9 GB, but &lt;code&gt;zgrep&lt;/code&gt; decompresses as it scans, every time (&lt;a href="https://uvp.y42u.net/en/blog/uwview-ps08-compressed-archive-search-en/" rel="noopener noreferrer"&gt;Part 8&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Second, &lt;strong&gt;splitting destroys the coordinate.&lt;/strong&gt; Line 1 of a &lt;code&gt;split&lt;/code&gt; fragment is line 1, and nothing tells you which line of the original it was. &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps16-log-as-evidence-en/" rel="noopener noreferrer"&gt;Part 16&lt;/a&gt;'s "three things an excerpt breaks" applies here unchanged.&lt;/p&gt;

&lt;p&gt;Third, and this is the real one: &lt;strong&gt;whether data may leave the building is a question about the path, not the size.&lt;/strong&gt; Keeping a file local and reading it on your own machine is a different review from uploading it to a cloud log-analysis service. With a tool that works entirely locally, the question stops being "where do we send it" and becomes only "where do we put it."&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Reading 30 GB across a WSL boundary is ten times slower
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Situation
&lt;/h3&gt;

&lt;p&gt;You investigate a 30 GB log that lives on the Windows side, using &lt;code&gt;grep&lt;/code&gt; inside WSL.&lt;/p&gt;

&lt;p&gt;Something that takes two minutes on a Linux server hasn't finished after twenty. The CPU is idle. The disk light is calm. You can't see where the waiting happens.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why it happens
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;File access that crosses an OS boundary pays a round trip per access.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In WSL2, the Windows drives you see from Linux are the Windows filesystem mounted over a protocol (9P by default). Each read becomes a round trip to the host side, so &lt;strong&gt;the more small reads a workload issues, the worse it does.&lt;/strong&gt; The reverse direction — Windows tools reading files on the WSL side — has the same shape.&lt;/p&gt;

&lt;p&gt;What matters here isn't bytes transferred but &lt;strong&gt;number of accesses.&lt;/strong&gt; Sequential scanners like &lt;code&gt;grep&lt;/code&gt; read in larger chunks and fare relatively better; anything that reads back line by line, or bounces repeatedly around the tail of a file, slows down in proportion to how often it asks. WSL1 and WSL2 behave differently, and behaviour changes between versions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Working with general-purpose tools, and where it stops
&lt;/h3&gt;

&lt;p&gt;The most effective move is &lt;strong&gt;not crossing the boundary at all.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# WINLOG = path to the log on the Windows side, as WSL sees it&lt;/span&gt;

&lt;span class="c"&gt;# Copy to the Linux-side native filesystem first, then work there&lt;/span&gt;
&lt;span class="nb"&gt;cp&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$WINLOG&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; ~/work/app.log

&lt;span class="c"&gt;# Or reduce what crosses the boundary (compress it first)&lt;/span&gt;
&lt;span class="nb"&gt;gzip&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$WINLOG&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ~/work/app.log.gz
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mount options and metadata settings in WSL's configuration file (&lt;code&gt;wsl.conf&lt;/code&gt;) leave some room for improvement, but the effect is configuration-dependent.&lt;/p&gt;

&lt;p&gt;Two limits.&lt;/p&gt;

&lt;p&gt;First, &lt;strong&gt;the copy doubles your disk usage and takes time itself.&lt;/strong&gt; You pay those minutes for 30 GB on every investigation.&lt;/p&gt;

&lt;p&gt;Second, &lt;strong&gt;reading on the Windows side avoids the boundary — but then you have no tool.&lt;/strong&gt; Notepad goes quiet at a few gigabytes (&lt;a href="https://uvp.y42u.net/en/blog/uwview-ps01-huge-file-tool-limits-en/" rel="noopener noreferrer"&gt;Part 1&lt;/a&gt;), and neither &lt;code&gt;grep&lt;/code&gt; nor &lt;code&gt;less&lt;/code&gt; ships as standard. So you go back to WSL. That round trip &lt;em&gt;is&lt;/em&gt; the problem. Which means this isn't a performance question but &lt;strong&gt;a placement question: which side of the boundary holds the tool.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  4. "It opened fine on my machine"
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Situation
&lt;/h3&gt;

&lt;p&gt;A colleague you handed a 30 GB log to says it won't open. It opened on your desktop.&lt;/p&gt;

&lt;p&gt;The difference is 64 GB of RAM versus 16. The result: &lt;strong&gt;log investigation collects around the one person with the memory.&lt;/strong&gt; While they're on leave, the investigation stops.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why it happens
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;If your tool assumes it will read everything into memory, your ceiling is your RAM.&lt;/strong&gt; When the ceiling differs per machine, the team's capability doesn't settle at the weakest machine — it &lt;strong&gt;becomes dependent on the one person with the strongest.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There's a second effect: when tools differ, &lt;strong&gt;the vocabulary stops being shared.&lt;/strong&gt; "Look at line 12,034,541" shows a different line if the other person's tool counts &lt;code&gt;\r&lt;/code&gt; as a line break. BOMs, UTF-16, and line-ending conventions all shift the numbering too (&lt;a href="https://uvp.y42u.net/en/blog/uwview-ps03-japanese-encoding-traps-en/" rel="noopener noreferrer"&gt;Part 3&lt;/a&gt;, &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps11-legacy-encoding-euc-utf16-en/" rel="noopener noreferrer"&gt;Part 11&lt;/a&gt;). Line numbers get used as a shared language, but &lt;strong&gt;they aren't one unless the tools agree.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Working with general-purpose tools, and where it stops
&lt;/h3&gt;

&lt;p&gt;The standard answer is that everybody &lt;code&gt;ssh&lt;/code&gt;es to a shared box and reads with &lt;code&gt;less&lt;/code&gt;. The log never leaves, and the environment gap disappears.&lt;/p&gt;

&lt;p&gt;Three limits.&lt;/p&gt;

&lt;p&gt;First, &lt;strong&gt;latency.&lt;/strong&gt; Every repaint waits for a round trip, which is poor for the kind of investigation you do with your eyes. Remote work makes it heavier still.&lt;/p&gt;

&lt;p&gt;Second, &lt;strong&gt;nothing persists.&lt;/strong&gt; Colours, bookmarks, search terms — close &lt;code&gt;less&lt;/code&gt; and they're gone. The unit of handover becomes a verbal explanation (the same problem behind the resumption point in &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps13-huge-data-inspection-en/" rel="noopener noreferrer"&gt;Part 13&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Third, &lt;strong&gt;everyone queues on one machine.&lt;/strong&gt; Two people scanning tens of gigabytes at once fight over that box's I/O.&lt;/p&gt;

&lt;p&gt;So the conclusion: levelling the environment doesn't mean &lt;strong&gt;levelling the RAM — it means agreeing on a tool that doesn't depend on RAM.&lt;/strong&gt; If a 16 GB laptop opens it, everyone sees the same line numbers, and the work stops pooling on one person.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the four had in common
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Friction&lt;/th&gt;
&lt;th&gt;Where it stalls&lt;/th&gt;
&lt;th&gt;Direct cause&lt;/th&gt;
&lt;th&gt;General-purpose approach&lt;/th&gt;
&lt;th&gt;What's left over&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CI build log&lt;/td&gt;
&lt;td&gt;The browser viewer&lt;/td&gt;
&lt;td&gt;Interleaving, ANSI, progress bars, retries&lt;/td&gt;
&lt;td&gt;Fetch the raw log, clean with &lt;code&gt;ansi2txt&lt;/code&gt; / &lt;code&gt;tr&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Line numbers shift with each pass; still huge afterwards&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Carrying production home&lt;/td&gt;
&lt;td&gt;Transfer and review&lt;/td&gt;
&lt;td&gt;Size and confidentiality at once&lt;/td&gt;
&lt;td&gt;Compressed transfer, &lt;code&gt;grep&lt;/code&gt; to narrow, masking&lt;/td&gt;
&lt;td&gt;The filter is only knowable afterwards; masking is irreversible&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reading across WSL&lt;/td&gt;
&lt;td&gt;The OS boundary&lt;/td&gt;
&lt;td&gt;One round trip per access&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;cp&lt;/code&gt; to the native filesystem first&lt;/td&gt;
&lt;td&gt;Double the disk, plus copy time; no tool on the Windows side&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Team environment gap&lt;/td&gt;
&lt;td&gt;The reader's machine&lt;/td&gt;
&lt;td&gt;The ceiling is set by RAM&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;ssh&lt;/code&gt; to a shared box, read with &lt;code&gt;less&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Latency; nothing persists; everyone on one machine&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;All four push you into the same choice: &lt;strong&gt;transform the log before reading it, or travel to where the log lives.&lt;/strong&gt; Transform it and the line numbers change. Travel to it and nothing you learn persists.&lt;/p&gt;

&lt;p&gt;Which makes the requirements three.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;How much you can read isn't set by RAM.&lt;/strong&gt; Satisfy this and section 4's gap simply stops existing — and sections 1 and 2 no longer need "shrink it so it fits."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You can read the original as it is.&lt;/strong&gt; Opened without splitting, masking, or cleanup, so section 2's irreversible decisions can be postponed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It completes locally.&lt;/strong&gt; Section 2's review question changes from "where do we send it" to "where do we keep it," and section 3 reduces to where the file sits.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Back to the 480,000 lines. That log was never too long to read. &lt;strong&gt;It was a log you had to transform before reading — which cost you the only coordinate it had.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The tool I use
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://uvp.y42u.net/en/" rel="noopener noreferrer"&gt;UwView&lt;/a&gt; (free), which I develop, displays, scrolls, and searches huge text &lt;strong&gt;from the moment it opens&lt;/strong&gt;. It doesn't load the whole file into memory, so files larger than RAM open fine. The index builds in the background, and line numbers appear when it finishes.&lt;/p&gt;

&lt;p&gt;Of the three requirements above, the free version covers the first two outright.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;How much you can read isn't set by RAM.&lt;/strong&gt; A 16 GB laptop opens files of tens of gigabytes (47.73 GB / about 890 million lines measured on the free version; one specific setup). The precondition for section 4's "it opened on my machine" disappears.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It never writes to the original, and never splits or extracts.&lt;/strong&gt; Section 1's cleanup intermediates aren't needed, so &lt;strong&gt;the line numbers still match the CI UI.&lt;/strong&gt; Section 2's "just narrow it for now" can wait.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The same tool runs on Windows, macOS, and Linux.&lt;/strong&gt; That makes section 3's "which side holds the tool" a choice. Read a Windows-side log with a Windows-side tool and no round trips cross the boundary.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;All processing is local.&lt;/strong&gt; The file is never transmitted anywhere. In section 2's review, that is sometimes the precondition rather than a feature.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Encoding switches while the file stays open&lt;/strong&gt; (UTF-8 / Shift-JIS(CP932) / EUC-JP / UTF-16, auto-detected). No &lt;code&gt;iconv&lt;/code&gt; intermediate (&lt;a href="https://uvp.y42u.net/en/blog/uwview-ps03-japanese-encoding-traps-en/" rel="noopener noreferrer"&gt;Part 3&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Highlighting colours lines rather than removing them.&lt;/strong&gt; Section 1's interleaving can be untangled by eye, colouring per job name (&lt;a href="https://uvp.y42u.net/en/blog/uwview-ps05-debug-log-practices-en/" rel="noopener noreferrer"&gt;Part 5&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The third requirement — and every case where you reopen the same file — is &lt;a href="https://uvp.y42u.net/en/pro-en/" rel="noopener noreferrer"&gt;UwView Pro&lt;/a&gt;'s territory.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It saves the index and the compression&lt;/strong&gt;: from the second open onward the file comes back &lt;strong&gt;instantly, with line numbers&lt;/strong&gt; (0.02–0.07 s measured on a 47.73 GB text file; one setup, results vary). The dump you carried home in section 2 gets reopened the day you write the report, the day review sends it back, and the day something similar recurs — &lt;strong&gt;the same tens of gigabytes, across days.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;~1/9 storage, still searchable&lt;/strong&gt;: aimed straight at section 2's "it won't fit on my SSD." Search goes through the compressed cache, so there's no &lt;code&gt;zgrep&lt;/code&gt;-style decompress-as-you-scan on every query (&lt;a href="https://uvp.y42u.net/en/blog/uwview-ps08-compressed-archive-search-en/" rel="noopener noreferrer"&gt;Part 8&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Drill-down search&lt;/strong&gt;: narrow a result by another term, then another, and &lt;strong&gt;the original line numbers survive to the last stage.&lt;/strong&gt; Against section 2's "the filter is only knowable afterwards," this answers by &lt;strong&gt;narrowing the view instead of narrowing the file&lt;/strong&gt; (&lt;a href="https://uvp.y42u.net/en/blog/uvp-drilldown-search-en/" rel="noopener noreferrer"&gt;implementation write-up&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;±N is independent per stage&lt;/strong&gt;: ±1 while narrowing, ±20 on the stage you read. Section 1's "discover &lt;code&gt;-B 20&lt;/code&gt; was too small, then run &lt;code&gt;grep&lt;/code&gt; again" disappears (free version is fixed ±1; variable ±N is Pro).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sequence search&lt;/strong&gt;: match only where &lt;code&gt;w1 → w2 → w3&lt;/code&gt; appear &lt;strong&gt;in that order&lt;/strong&gt;. In section 1's log, where a retry recorded the same failure three times, &lt;strong&gt;the ordering of the first occurrence becomes the query&lt;/strong&gt; (&lt;a href="https://uvp.y42u.net/en/blog/uvp-sequence-search-en/" rel="noopener noreferrer"&gt;implementation write-up&lt;/a&gt;). One honest note: each stage scans the body from the previous stage's position, so &lt;strong&gt;it takes about as long as a full-text search.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Search terms and colour rules can be saved and handed over&lt;/strong&gt;: aimed at section 4's "close &lt;code&gt;less&lt;/code&gt; and it's gone" (&lt;a href="https://uvp.y42u.net/en/blog/uwview-archive-session-restore-workflow-en/" rel="noopener noreferrer"&gt;archive plus session restore&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The honest limits
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;UwView is a viewer. It is not CI log infrastructure and not a log-aggregation service.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It doesn't strip section 1's ANSI escapes or fold progress bars. If preprocessing is needed, &lt;code&gt;sed&lt;/code&gt; and &lt;code&gt;tr&lt;/code&gt; are your job.&lt;/li&gt;
&lt;li&gt;It doesn't fetch logs from a CI API either. You still need the CI's own CLI or equivalent.&lt;/li&gt;
&lt;li&gt;It doesn't mask anything. Handling personal data follows your organisation's policy.&lt;/li&gt;
&lt;li&gt;Section 3's I/O penalty cannot be removed by a tool. It's solved by &lt;strong&gt;placement&lt;/strong&gt; — which side of the boundary the file sits on, and which side's tool reads it.&lt;/li&gt;
&lt;li&gt;No live tailing, no alerting, no automated cross-log correlation.&lt;/li&gt;
&lt;li&gt;Text only. Binary dumps and core dumps are out of scope.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For completeness: non-destructive diff editing (Edit Upgrade) exists as a separate licence, but &lt;strong&gt;all four items above are read-only work.&lt;/strong&gt; What you need here is the View side.&lt;/p&gt;

&lt;p&gt;And if a huge log is eating your disk and you want it &lt;strong&gt;compressed for storage while staying searchable at speed, give &lt;a href="https://uvp.y42u.net/en/pro-en/" rel="noopener noreferrer"&gt;UwView Pro&lt;/a&gt; a look&lt;/strong&gt; — persistent index, compressed-cache search, and ~1/9 storage make both reopening and searching a step faster (all OS, one-time or monthly).&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Part 1: four go-to tools that sink under huge files: &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps01-huge-file-tool-limits-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uwview-ps01-huge-file-tool-limits-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 3: four character-encoding traps and how to isolate them: &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps03-japanese-encoding-traps-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uwview-ps03-japanese-encoding-traps-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 5: four practices for living with development logs: &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps05-debug-log-practices-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uwview-ps05-debug-log-practices-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 8: keeping logs compressed and still searchable: &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps08-compressed-archive-search-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uwview-ps08-compressed-archive-search-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 11: reading legacy encodings in 2026: &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps11-legacy-encoding-euc-utf16-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uwview-ps11-legacy-encoding-euc-utf16-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 13: four techniques for inspecting huge data: &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps13-huge-data-inspection-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uwview-ps13-huge-data-inspection-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 15: four limits of command-line craft: &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps15-cli-craft-limits-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uwview-ps15-cli-craft-limits-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 16: four principles for preserving, excerpting, and proving integrity: &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps16-log-as-evidence-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uwview-ps16-log-as-evidence-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Drill-down search — narrowing a result by another term: &lt;a href="https://uvp.y42u.net/en/blog/uvp-drilldown-search-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uvp-drilldown-search-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Sequence search — finding only what appears in that order: &lt;a href="https://uvp.y42u.net/en/blog/uvp-sequence-search-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uvp-sequence-search-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Archive plus session restore, as a working flow: &lt;a href="https://uvp.y42u.net/en/blog/uwview-archive-session-restore-workflow-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uwview-archive-session-restore-workflow-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Source code (GitHub): &lt;a href="https://github.com/amru195704/UwView" rel="noopener noreferrer"&gt;https://github.com/amru195704/UwView&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;From the developer&lt;/strong&gt;: a full list of my apps, Kindle books, and open-source work is on &lt;a href="https://github.com/amru195704" rel="noopener noreferrer"&gt;GitHub: amru195704&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A note&lt;/strong&gt;&lt;br&gt;
This article is provided for reference and makes no guarantee of accuracy or completeness. Moving production data, masking personal information, and retaining logs must follow your organisation's policy and applicable law. Line counts, sizes, and filenames are illustrative and do not describe any real case. The behaviour of &lt;code&gt;sed&lt;/code&gt;, &lt;code&gt;tr&lt;/code&gt;, &lt;code&gt;grep&lt;/code&gt;, &lt;code&gt;gzip&lt;/code&gt;, and &lt;code&gt;cp&lt;/code&gt; varies by implementation (GNU/BSD/busybox), version, and build options, and CI service CLIs and retention periods change with the vendor's specifications. WSL file-access performance varies substantially with version (WSL1/WSL2), Windows build, mount settings, and whether antivirus is active, so the description here explains a tendency rather than guaranteeing any figure. Check option names and defaults against your own &lt;code&gt;man&lt;/code&gt; pages and the official documentation. Measured figures come from one specific setup and are not a guarantee of the same result. If you spot an error, a comment is welcome and I'll check and correct it.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>devops</category>
      <category>cicd</category>
      <category>wsl</category>
      <category>logging</category>
    </item>
    <item>
      <title>Logs as Evidence — Four Principles for Preserving, Excerpting, and Proving Integrity</title>
      <dc:creator>y4u</dc:creator>
      <pubDate>Mon, 14 Sep 2026 02:56:13 +0000</pubDate>
      <link>https://dev.to/amru195704/logs-as-evidence-four-principles-for-preserving-excerpting-and-proving-integrity-fhb</link>
      <guid>https://dev.to/amru195704/logs-as-evidence-four-principles-for-preserving-excerpting-and-proving-integrity-fhb</guid>
      <description>&lt;p&gt;The reboot cost thirty-eight minutes of log.&lt;/p&gt;

&lt;p&gt;Fixing it was right. &lt;strong&gt;What should you have taken with you first?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Loss has a shape. There are four of them.&lt;/p&gt;

&lt;p&gt;Earlier parts of this series were about &lt;em&gt;how to read&lt;/em&gt;. This one is about what comes before that — keeping the thing you'll read in a form you can later show to someone else. Reboots, rotation, the excerpt you paste into a report, and the question of how you demonstrate that a file wasn't altered. None of it is glamorous. All of it, done wrong, &lt;strong&gt;removes the investigation itself from the table.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Up front: UwView never writes to the original. It displays and searches from the moment it opens, and it never splits or extracts, so a preserved log stays one file, unmodified. Pro's drill-down search stacks the narrowing and &lt;strong&gt;keeps the original line numbers all the way to the last stage&lt;/strong&gt;, so you can cite a passage in the form a report needs — "line N of the original." Everything runs on your own machine; the file is never sent anywhere (measured on one setup; results vary — details at the end)
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;This article assumes the preserved log is already in your hands, or is about to be. Containment, notification, and any judgement about legal admissibility belong to your organisation's policy and the relevant authorities.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  1. Before "a reboot fixed it" — sorting what dies from what survives
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Situation
&lt;/h3&gt;

&lt;p&gt;Two in the morning. The service stops responding. You don't know why, but a reboot brings it back.&lt;/p&gt;

&lt;p&gt;The next morning someone asks what caused it. You can't say. You go looking for the logs, and &lt;strong&gt;the window you care about isn't there&lt;/strong&gt; — or it's there with a few minutes missing from the middle.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why it happens
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;A reboot is a destructive operation.&lt;/strong&gt; Not uniformly, though — what dies and what survives divides cleanly.&lt;/p&gt;

&lt;p&gt;Almost certainly gone:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The logging buffer inside the process.&lt;/strong&gt; Most logging libraries don't &lt;code&gt;write&lt;/code&gt; per line; they batch. On a hard kill (power loss, a panic, a forced stop signal), whatever hadn't reached the disk is gone. That's &lt;strong&gt;the last few seconds to few tens of seconds&lt;/strong&gt; — which is to say, the part nearest the cause.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The kernel ring buffer.&lt;/strong&gt; What &lt;code&gt;dmesg&lt;/code&gt; reads gets replaced on reboot unless you're persisting it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;In-memory scratch space.&lt;/strong&gt; Directories backed by tmpfs empty out. If journald is &lt;code&gt;Storage=volatile&lt;/code&gt; — including the common case where &lt;code&gt;auto&lt;/code&gt; finds no &lt;code&gt;/var/log/journal&lt;/code&gt; — the journal also lives in the in-memory runtime area and &lt;strong&gt;disappears with the reboot&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Volatile state.&lt;/strong&gt; Socket tables, the process tree, open file descriptors, files that were deleted but are still held open. Not logs, but frequently more eloquent than logs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Surviving: whatever finished landing in &lt;code&gt;/var/log/*&lt;/code&gt;, a &lt;code&gt;Storage=persistent&lt;/code&gt; journal, already-rotated archives.&lt;/p&gt;

&lt;p&gt;So what a reboot takes is precisely &lt;strong&gt;the side nearest the cause&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Working with general-purpose tools, and where it stops
&lt;/h3&gt;

&lt;p&gt;There are things you can do in under a minute, before the reboot.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Have one destination directory ready (e.g. preserve-20260910T031500Z)&lt;/span&gt;
&lt;span class="nv"&gt;D&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;preserve-20260910T031500Z

dmesg &lt;span class="nt"&gt;-T&lt;/span&gt;                       &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$D&lt;/span&gt;&lt;span class="s2"&gt;/dmesg.txt"&lt;/span&gt;
journalctl &lt;span class="nt"&gt;-b&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; short-precise &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$D&lt;/span&gt;&lt;span class="s2"&gt;/journal-thisboot.txt"&lt;/span&gt;
ps auxww                       &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$D&lt;/span&gt;&lt;span class="s2"&gt;/ps.txt"&lt;/span&gt;
ss &lt;span class="nt"&gt;-tanp&lt;/span&gt;                       &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$D&lt;/span&gt;&lt;span class="s2"&gt;/ss.txt"&lt;/span&gt;
&lt;span class="nb"&gt;cp&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; /var/log/app/current.log &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$D&lt;/span&gt;&lt;span class="s2"&gt;/"&lt;/span&gt;          &lt;span class="c"&gt;# copy — not move, not a slice&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your system has a command that lists open file descriptors, capture that output too. And if you have to stop the application, &lt;strong&gt;try the clean stop first&lt;/strong&gt; — given a termination signal, most implementations flush what's left before they exit. A forced stop is the last resort, for when that doesn't work.&lt;/p&gt;

&lt;p&gt;The point is that it's &lt;code&gt;cp&lt;/code&gt; and not &lt;code&gt;mv&lt;/code&gt;, and that nothing is being truncated with &lt;code&gt;tail&lt;/code&gt;. &lt;strong&gt;At this stage, never shrink anything to a size you can read.&lt;/strong&gt; You can redo the shrinking as many times as you like later. You cannot undo it once you've asked for "the part before that."&lt;/p&gt;

&lt;p&gt;Three limits.&lt;/p&gt;

&lt;p&gt;First, &lt;strong&gt;taking everything produces something you can't open.&lt;/strong&gt; A &lt;code&gt;journalctl -b&lt;/code&gt; that runs to tens of gigabytes is not unusual. Preserved, yes; readable, no — which walks straight back into &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps01-huge-file-tool-limits-en/" rel="noopener noreferrer"&gt;Part 1&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Second, &lt;strong&gt;where it lands.&lt;/strong&gt; On the same disk, you eat the free space of a box that's already in trouble. On another disk, you spend the minutes you least want to spend.&lt;/p&gt;

&lt;p&gt;Third — and in practice this is the one that decides the outcome — &lt;strong&gt;whether you can remember the procedure at 2 a.m.&lt;/strong&gt; A procedure that depends on the memory of someone whose hands are shaking is not a procedure. Which is why, as in &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps10-oncall-night-preparation-en/" rel="noopener noreferrer"&gt;Part 10&lt;/a&gt;, this belongs in a script: one &lt;code&gt;preserve.sh&lt;/code&gt;, somewhere on your path.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. The rotation gap — the moment evidence disappears is already on the schedule
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Situation
&lt;/h3&gt;

&lt;p&gt;The incident was at 03:24. The next day you go to read the log.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;app.log&lt;/code&gt; exists, but it starts at 03:25. The 03:24 lines should be in &lt;code&gt;app.log.1.gz&lt;/code&gt;. You open it and &lt;strong&gt;those minutes are simply not there&lt;/strong&gt; — or, with &lt;code&gt;rotate 8&lt;/code&gt;, the file that held them was deleted days ago.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why it happens
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Rotation swaps a file out&lt;/strong&gt;, and every swap has a seam. The size of the seam depends on the method.&lt;/p&gt;

&lt;p&gt;Two &lt;code&gt;logrotate&lt;/code&gt; modes, treated separately:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;create&lt;/code&gt; (the default).&lt;/strong&gt; The old file is &lt;code&gt;mv&lt;/code&gt;'d, a new one is created, and &lt;code&gt;postrotate&lt;/code&gt; tells the application to reopen (typically by sending it a HUP signal). If the reopen fails, &lt;strong&gt;the application keeps writing to the moved inode.&lt;/strong&gt; The directory shows an empty &lt;code&gt;app.log&lt;/code&gt; while the writes pour into &lt;code&gt;app.log.1&lt;/code&gt; — a classic "logging just stopped" incident.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;copytruncate&lt;/code&gt;.&lt;/strong&gt; The contents are copied, then the original is truncated. Nothing has to be told anything, which is why people like it. But &lt;strong&gt;lines written between the copy and the truncate land in neither file.&lt;/strong&gt; Copying several gigabytes takes seconds. At a few thousand lines per second, that's thousands to tens of thousands of lines.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And the deletion schedule is written down in the config. &lt;code&gt;rotate 8&lt;/code&gt; with &lt;code&gt;daily&lt;/code&gt; means eight days; &lt;code&gt;maxage 30&lt;/code&gt; means thirty. Being told "we retain three years" while the machine says eight days is a gap that shows up in real audits — the &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps04-log-retention-decision-en/" rel="noopener noreferrer"&gt;Part 4&lt;/a&gt; decision about delete, keep, or compress, arriving as a consequence.&lt;/p&gt;

&lt;h3&gt;
  
  
  Working with general-purpose tools, and where it stops
&lt;/h3&gt;

&lt;p&gt;First, see what will happen before it happens.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;logrotate &lt;span class="nt"&gt;-d&lt;/span&gt; app.conf                    &lt;span class="c"&gt;# dry run: report, don't act&lt;/span&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-rn&lt;/span&gt; &lt;span class="s1"&gt;'rotate\|maxage\|copytruncate\|create'&lt;/span&gt; logrotate.d/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read the config paths as whatever your distribution uses — usually a &lt;code&gt;logrotate.d&lt;/code&gt; directory plus the top-level config file above it. When it last ran is recorded in &lt;code&gt;logrotate&lt;/code&gt;'s own state file (&lt;code&gt;logrotate/status&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Then put the copy before the swap.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;/&lt;span class="n"&gt;var&lt;/span&gt;/&lt;span class="n"&gt;log&lt;/span&gt;/&lt;span class="n"&gt;app&lt;/span&gt;/*.&lt;span class="n"&gt;log&lt;/span&gt; {
    &lt;span class="n"&gt;daily&lt;/span&gt;
    &lt;span class="n"&gt;rotate&lt;/span&gt; &lt;span class="m"&gt;8&lt;/span&gt;
    &lt;span class="n"&gt;dateext&lt;/span&gt;
    &lt;span class="n"&gt;dateformat&lt;/span&gt; -%&lt;span class="n"&gt;Y&lt;/span&gt;%&lt;span class="n"&gt;m&lt;/span&gt;%&lt;span class="n"&gt;d&lt;/span&gt;
    &lt;span class="n"&gt;delaycompress&lt;/span&gt;
    &lt;span class="n"&gt;sharedscripts&lt;/span&gt;
    &lt;span class="n"&gt;prerotate&lt;/span&gt;
        &lt;span class="n"&gt;cp&lt;/span&gt; -&lt;span class="n"&gt;a&lt;/span&gt; /&lt;span class="n"&gt;var&lt;/span&gt;/&lt;span class="n"&gt;log&lt;/span&gt;/&lt;span class="n"&gt;app&lt;/span&gt;/&lt;span class="n"&gt;app&lt;/span&gt;.&lt;span class="n"&gt;log&lt;/span&gt; /&lt;span class="n"&gt;archive&lt;/span&gt;/&lt;span class="n"&gt;app&lt;/span&gt;-&lt;span class="m"&gt;20260910&lt;/span&gt;&lt;span class="n"&gt;T031500Z&lt;/span&gt;.&lt;span class="n"&gt;log&lt;/span&gt;
    &lt;span class="n"&gt;endscript&lt;/span&gt;
    &lt;span class="n"&gt;postrotate&lt;/span&gt;
        &lt;span class="c"&gt;# tell the app to reopen (a service reload, or a HUP signal)
&lt;/span&gt;    &lt;span class="n"&gt;endscript&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In practice the timestamp in the staged filename comes from expanding a date command; the example above hard-codes one.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;dateext&lt;/code&gt; looks like a cosmetic setting and isn't. Under the numbered scheme, &lt;code&gt;app.log.1&lt;/code&gt; shifts to &lt;code&gt;app.log.2&lt;/code&gt; and &lt;strong&gt;the same name refers to different contents over time&lt;/strong&gt;, so "which day is this &lt;code&gt;app.log.3&lt;/code&gt; from" becomes unanswerable. Put the date in the name and the name becomes a coordinate.&lt;/p&gt;

&lt;p&gt;Three limits.&lt;/p&gt;

&lt;p&gt;First, &lt;strong&gt;the &lt;code&gt;prerotate&lt;/code&gt; copy lengthens the rotation itself.&lt;/strong&gt; Copying 50 GB takes minutes, during which disk I/O saturates and production slows. Land that on top of the nightly batch window and your preservation has manufactured a second incident.&lt;/p&gt;

&lt;p&gt;Second, &lt;strong&gt;you've doubled the space.&lt;/strong&gt; The instinct is to compress, but searching compressed logs with &lt;code&gt;zgrep&lt;/code&gt; is slow — &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps08-compressed-archive-search-en/" rel="noopener noreferrer"&gt;Part 8&lt;/a&gt; covers why: it's a sequential scan with decompression on the way through.&lt;/p&gt;

&lt;p&gt;Third, and heaviest once you think of the file as evidence: &lt;strong&gt;the moment you &lt;code&gt;cp&lt;/code&gt;, it is a different file.&lt;/strong&gt; &lt;code&gt;cp -a&lt;/code&gt; preserves mtime and atime, but the destination inode is new and its &lt;code&gt;ctime&lt;/code&gt; is now. And if you offer mtime as proof that "this was written at 03:24," &lt;code&gt;touch&lt;/code&gt; sets mtime to anything you like. &lt;strong&gt;File attributes prove nothing on their own.&lt;/strong&gt; Which leads to section 4.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. The "±10 lines" you paste into a report — excerpting without touching the original
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Situation
&lt;/h3&gt;

&lt;p&gt;You've found the line. "Put the relevant log in the report." Ten lines either side is enough.&lt;/p&gt;

&lt;p&gt;You cut it with &lt;code&gt;sed -n&lt;/code&gt; and paste. In review: "what was it doing just before that?" Back to &lt;code&gt;sed&lt;/code&gt;. Then: "start twenty lines earlier." Back to &lt;code&gt;sed&lt;/code&gt;. By the third round you've lost track of which fragment came from which line number.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why it happens
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Excerpting breaks three things at once.&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The coordinate.&lt;/strong&gt; Line 1 of the excerpt is line 1, not line 12,034,541.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The timing of the width decision.&lt;/strong&gt; How much context you need is knowable only after reading. Excerpting demands the number first.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Identity.&lt;/strong&gt; The moment you cut, it isn't the original. The hash won't match, and the fragment alone can't demonstrate that it came from the original at all.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The third one bites when the report leaves the building. "Lines 12,034,541–12,034,561 of the original" is a claim &lt;strong&gt;a reader without the original cannot check.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Working with general-purpose tools, and where it stops
&lt;/h3&gt;

&lt;p&gt;You can keep the coordinate.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Original line numbers survive, with context&lt;/span&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nt"&gt;-C&lt;/span&gt; 10 &lt;span class="s1"&gt;'OutOfMemoryError'&lt;/span&gt; huge.log &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; slice.txt

&lt;span class="c"&gt;# When you already know the line&lt;/span&gt;
&lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'NR&amp;gt;=12034531 &amp;amp;&amp;amp; NR&amp;lt;=12034551 {printf "%d: %s\n", NR, $0}'&lt;/span&gt; huge.log

&lt;span class="c"&gt;# Record where you looked rather than what was there&lt;/span&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s1"&gt;'OutOfMemoryError'&lt;/span&gt; huge.log | &lt;span class="nb"&gt;cut&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;: &lt;span class="nt"&gt;-f1&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; hit-lines.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first is the practical winner: original line numbers, plus context.&lt;/p&gt;

&lt;p&gt;The limits stay.&lt;/p&gt;

&lt;p&gt;First, &lt;strong&gt;&lt;code&gt;-C&lt;/code&gt; has to be chosen before you type it.&lt;/strong&gt; You learn that ten wasn't enough only after reading, and &lt;code&gt;-C 20&lt;/code&gt; reads the file from the top again. Under the I/O-bound ceiling from &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps15-cli-craft-limits-en/" rel="noopener noreferrer"&gt;Part 15&lt;/a&gt;, those re-reads accumulate one round trip at a time.&lt;/p&gt;

&lt;p&gt;Second, &lt;strong&gt;lots of hits make it useless.&lt;/strong&gt; A hundred hits at 21 lines each is 2,100 lines interleaved with &lt;code&gt;--&lt;/code&gt; separators. Not something you paste into a report. Cap it with &lt;code&gt;-m 1&lt;/code&gt; and you've lost the fact that there were others.&lt;/p&gt;

&lt;p&gt;Third, &lt;strong&gt;prefixing the line number changes the text.&lt;/strong&gt; &lt;code&gt;12034541: [ERROR] ...&lt;/code&gt; reads well, but that string doesn't exist in the original — searching for it finds nothing. You bought the coordinate by contaminating the line.&lt;/p&gt;

&lt;p&gt;And underneath all three: &lt;strong&gt;a fragment becomes evidence only in reference to an original that still exists.&lt;/strong&gt; What the report actually needs isn't the fragment, it's the correspondence — &lt;em&gt;there is an original, and this is line N of it.&lt;/em&gt; Holding that correspondence together is the next section.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Demonstrating "this wasn't altered" — what a checksum covers
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Situation
&lt;/h3&gt;

&lt;p&gt;You hashed the preserved log.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sha256sum &lt;/span&gt;evidence.log &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; evidence.log.sha256
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That felt like the end of it. Then an auditor asks &lt;strong&gt;when, and by whom&lt;/strong&gt;, that hash was computed — and there's no answer in the file.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why it happens
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;A checksum proves that two byte sequences are the same.&lt;/strong&gt; That is the entire claim. It does not establish:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;When it was taken.&lt;/strong&gt; The mtime of the &lt;code&gt;.sha256&lt;/code&gt; is writable. So is any date you typed inside it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Who took it.&lt;/strong&gt; A hash has no author.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;That the hash itself is authentic.&lt;/strong&gt; If &lt;code&gt;evidence.log&lt;/code&gt; and &lt;code&gt;evidence.log.sha256&lt;/code&gt; sit in the same directory, anyone who can rewrite the log can rewrite the hash. &lt;strong&gt;A hash stored beside the file is a lock with the key left in it.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;That the contents are true.&lt;/strong&gt; If the application logged a lie, the hash matches the lie perfectly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So &lt;code&gt;sha256sum&lt;/code&gt; on its own says exactly one thing: &lt;em&gt;nothing has broken, in my hands, since the moment I ran this.&lt;/em&gt; Tamper resistance doesn't come from the strength of the hash function. It comes from &lt;strong&gt;where you put the hash.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Working with general-purpose tools, and where it stops
&lt;/h3&gt;

&lt;p&gt;Two moves: separate the storage, and add time and authorship.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1) Take them together — a manifest, not one file at a time&lt;/span&gt;
find /archive &lt;span class="nt"&gt;-type&lt;/span&gt; f &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s1"&gt;'*.log*'&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt; | xargs &lt;span class="nb"&gt;sha256sum&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; manifest-20260910T031500Z.sha256

&lt;span class="c"&gt;# 2) Verification is one line&lt;/span&gt;
&lt;span class="nb"&gt;sha256sum&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; manifest-20260910T031500Z.sha256

&lt;span class="c"&gt;# 3) For compressed logs, hash the *contents*&lt;/span&gt;
&lt;span class="nb"&gt;gzip&lt;/span&gt; &lt;span class="nt"&gt;-dc&lt;/span&gt; app.log.gz | &lt;span class="nb"&gt;sha256sum&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Where filenames may contain spaces, use the null-terminated options of &lt;code&gt;find&lt;/code&gt; and &lt;code&gt;xargs&lt;/code&gt;. And to add the &lt;strong&gt;"who"&lt;/strong&gt;, attach a detached signature to the manifest (GnuPG, or whatever key management your organisation already runs). A hash has no author, so this is the field nothing else fills in.&lt;/p&gt;

&lt;p&gt;The third is the one people miss. &lt;strong&gt;gzip writes the original filename and mtime into its header&lt;/strong&gt;, so compressing identical content twice produces two different &lt;code&gt;.gz&lt;/code&gt; hashes. Compression level and implementation (gzip / pigz / a given zlib) change it too. Record only the &lt;code&gt;.gz&lt;/code&gt; hash in your ledger and, the first time anyone recompresses, you get a mismatch that means nothing and a scare that costs a day. Record &lt;strong&gt;the hash of the contents.&lt;/strong&gt; (&lt;code&gt;gzip -n&lt;/code&gt; drops the name and mtime and stabilises the &lt;code&gt;.gz&lt;/code&gt; side, but hashing the contents is the safer habit.)&lt;/p&gt;

&lt;p&gt;Separation can be bought at whatever price you can afford: ship it to another host, write it to append-only storage, commit it somewhere with real history, print it and seal it, or attach a third-party timestamp (RFC 3161). What they share is one property — &lt;strong&gt;the authority to rewrite the log and the authority to rewrite the hash are held by different people, in different places.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Two limits.&lt;/p&gt;

&lt;p&gt;First, &lt;strong&gt;all of it requires ongoing operation.&lt;/strong&gt; Doing it once and finding six months later that nobody kept it up is the most common ending.&lt;/p&gt;

&lt;p&gt;Second, and this one is structural: &lt;strong&gt;hash late and you can say correspondingly little.&lt;/strong&gt; A hash taken three days after the incident attests only to the three-days-later-onward state. Which puts the right moment for hashing in the same place as section 1's "before the reboot." Preservation and integrity aren't two jobs. They're &lt;strong&gt;two motions inside the same sixty seconds.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What all four had in common
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Principle&lt;/th&gt;
&lt;th&gt;When the evidence is lost&lt;/th&gt;
&lt;th&gt;Direct cause&lt;/th&gt;
&lt;th&gt;General-purpose response&lt;/th&gt;
&lt;th&gt;What remains&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Preserve&lt;/td&gt;
&lt;td&gt;Reboot, hard kill&lt;/td&gt;
&lt;td&gt;Unflushed buffers and volatile storage&lt;/td&gt;
&lt;td&gt;Dump before rebooting; &lt;code&gt;cp&lt;/code&gt;, never &lt;code&gt;mv&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Too big to read afterwards; nobody remembers the steps at 2 a.m.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stage&lt;/td&gt;
&lt;td&gt;The rotation seam&lt;/td&gt;
&lt;td&gt;The &lt;code&gt;copytruncate&lt;/code&gt; gap; the &lt;code&gt;rotate N&lt;/code&gt; deadline&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;logrotate -d&lt;/code&gt; first; &lt;code&gt;cp&lt;/code&gt; in &lt;code&gt;prerotate&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Double the I/O and space; &lt;code&gt;cp&lt;/code&gt; changes the inode, so attributes prove nothing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Excerpt&lt;/td&gt;
&lt;td&gt;The moment it's pasted into a report&lt;/td&gt;
&lt;td&gt;Coordinate, width, and identity lost together&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;grep -n -C&lt;/code&gt;; &lt;code&gt;awk&lt;/code&gt; with &lt;code&gt;NR&lt;/code&gt; prefixed&lt;/td&gt;
&lt;td&gt;Width chosen up front; a fragment alone can't be verified&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prove integrity&lt;/td&gt;
&lt;td&gt;When the hash wasn't stored separately&lt;/td&gt;
&lt;td&gt;A hash only asserts byte equality&lt;/td&gt;
&lt;td&gt;Manifest + separated storage + signature&lt;/td&gt;
&lt;td&gt;Taken too late; &lt;code&gt;.gz&lt;/code&gt; hashes shift on recompression&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The four look like four unrelated jobs, owned by different people at different hours. The reason the right-hand column rhymes is that &lt;strong&gt;all four lose something other than what they touched, at the instant they touch the original.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;mv&lt;/code&gt; loses the location. &lt;code&gt;truncate&lt;/code&gt; loses the seam. Excerpting loses the coordinate. Recompressing loses the hash. Pushed far enough, there's only one principle: &lt;strong&gt;handle the original as the original, and take what you need without changing it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Three conditions follow.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Never write to it, split it, or cut pieces out of it.&lt;/strong&gt; A tool that has to shrink a file before it can show it to you is the wrong tool here — the shrink triggers all three of section 3's losses at once.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Narrow while keeping the original coordinate.&lt;/strong&gt; The number has to survive to the end, in the form a report needs: "line 12,034,541." Stacking a second filter must not renumber anything.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Assume you'll reopen the same file many times.&lt;/strong&gt; A preserved log is not read once. It's read the day you write the report, the day review sends it back, and the day the auditor asks. &lt;strong&gt;The same tens of gigabytes, reopened across days.&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Back to the thirty-eight minutes. The reboot didn't really take them. What took them was &lt;strong&gt;not having decided, beforehand, what the sixty seconds before a reboot are for.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The tool I use
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://uvp.y42u.net/en/" rel="noopener noreferrer"&gt;UwView&lt;/a&gt; (free), which I develop, displays, scrolls, and searches huge text &lt;strong&gt;from the moment it opens&lt;/strong&gt;. It doesn't load the whole file into memory, so files larger than RAM open fine. The index is built in the background and line numbers appear when it finishes.&lt;/p&gt;

&lt;p&gt;Of the three conditions above, the free version covers the first outright.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It never writes to the original.&lt;/strong&gt; Nothing is split, nothing is extracted, so the log you &lt;code&gt;cp&lt;/code&gt;'d in section 1 stays one file, unmodified. Because "shrink it to read it" stops being necessary, section 3's three losses stop having an occasion to happen.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Highlighting colours lines rather than removing them.&lt;/strong&gt; You sort by colour instead of excluding, so "the thing I needed was inside what I filtered out" doesn't arise.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;All processing is local.&lt;/strong&gt; The file is never transmitted anywhere. On preserved logs, where whether data may leave the building is itself the question, that's often a precondition rather than a feature.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Encoding switches while the file stays open&lt;/strong&gt; (UTF-8 / Shift-JIS(CP932) / EUC-JP / UTF-16, auto-detected). No &lt;code&gt;iconv&lt;/code&gt; in the pipe, so no second copy on disk (&lt;a href="https://uvp.y42u.net/en/blog/uwview-ps03-japanese-encoding-traps-en/" rel="noopener noreferrer"&gt;Part 3&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Conditions two and three are what &lt;a href="https://uvp.y42u.net/en/pro-en/" rel="noopener noreferrer"&gt;UwView Pro&lt;/a&gt; adds.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Drill-down search&lt;/strong&gt;: narrow a result by another term, then another. Tabs carry &lt;code&gt;term (count)&lt;/code&gt;, and &lt;strong&gt;the original line numbers survive to the last stage&lt;/strong&gt; — so section 3's "the coordinate dies at stage two" doesn't happen, and the excerpt you paste can carry "line N of the original." A right-click history lists, in order, which term matched at which line (&lt;a href="https://uvp.y42u.net/en/blog/uvp-drilldown-search-en/" rel="noopener noreferrer"&gt;implementation write-up&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;±N is independent per stage&lt;/strong&gt;: ±1 while narrowing, ±20 on the stage you actually read — &lt;strong&gt;the width is changeable after the fact.&lt;/strong&gt; Section 3's "discover that &lt;code&gt;-C 10&lt;/code&gt; was too small, then run &lt;code&gt;grep&lt;/code&gt; again" disappears (free version is fixed ±1; variable ±N is Pro).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sequence search&lt;/strong&gt;: match only where &lt;code&gt;w1 → w2 → w3&lt;/code&gt; appear &lt;strong&gt;in that order&lt;/strong&gt;. Section 1's "clean stop attempted → failed → hard kill" becomes the query itself (&lt;a href="https://uvp.y42u.net/en/blog/uvp-sequence-search-en/" rel="noopener noreferrer"&gt;implementation write-up&lt;/a&gt;). One honest note: each stage scans the body from the previous stage's position, so &lt;strong&gt;it takes about as long as a full-text search&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It saves the index and the compression&lt;/strong&gt;: from the second open onward the file opens &lt;strong&gt;instantly, with line numbers&lt;/strong&gt; (0.02–0.07 s measured on a 47.73 GB text file; one setup, results vary). That lands directly on the third condition — a preserved log reopened across days.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;~1/9 storage, still searchable&lt;/strong&gt;: aimed straight at section 2's doubled disk usage. Search goes through the compressed cache, so there's no &lt;code&gt;zgrep&lt;/code&gt;-style decompress-as-you-scan on every query (&lt;a href="https://uvp.y42u.net/en/blog/uwview-ps08-compressed-archive-search-en/" rel="noopener noreferrer"&gt;Part 8&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There's one feature that touches section 4. The sidecar Pro builds (&lt;code&gt;.uwvz&lt;/code&gt;) carries an XxHash3 table per compressed block and &lt;strong&gt;verifies on every decompression&lt;/strong&gt;. It also checks the offset table for sanity on open (monotonic, final offset equal to the real file length), so bit rot in storage or a copy that was cut short won't slip past you unnoticed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This is not tamper detection, though.&lt;/strong&gt; XxHash3 is a non-cryptographic hash chosen for speed; it isn't designed to resist deliberate rewriting. What section 4 needs is &lt;code&gt;sha256sum&lt;/code&gt; and separated storage. This check exists to &lt;strong&gt;catch accidents&lt;/strong&gt; — bit rot, a bad transfer, a truncated file. Don't conflate the two.&lt;/p&gt;

&lt;h3&gt;
  
  
  The honest limits
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;UwView is a viewer. It is not a SIEM and not a forensic suite.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It doesn't take section 1's dumps. Writing &lt;code&gt;preserve.sh&lt;/code&gt; is your job.&lt;/li&gt;
&lt;li&gt;It doesn't configure &lt;code&gt;logrotate&lt;/code&gt; and doesn't run section 2's staging.&lt;/li&gt;
&lt;li&gt;It doesn't compute hashes, doesn't sign, and doesn't record chain-of-custody (who opened what, when). &lt;strong&gt;It is not a place to keep an evidence ledger.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;No automated cross-log correlation, no threat-intel matching, no alerting, no report generation.&lt;/li&gt;
&lt;li&gt;Disk images and memory dumps are out of scope entirely.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One more constraint, stated plainly because it matters to people handling evidence: &lt;strong&gt;Pro's sidecar (&lt;code&gt;.uwvz&lt;/code&gt;) is created as a new file next to the original.&lt;/strong&gt; The original itself doesn't change by a single byte, but if your procedure is that &lt;strong&gt;nothing may be added to the preservation directory&lt;/strong&gt;, copy the original into a separate working area before opening it. (The sidecar carries the original's length and last-modified time as its validation key, so if the original ever changes, the sidecar invalidates itself.)&lt;/p&gt;

&lt;p&gt;What this tool covers is the step before all of that: &lt;strong&gt;reading a preserved raw log as it is, locally, with the original coordinates intact, using your own eyes.&lt;/strong&gt; If your scale calls for correlation and alerting, that's a different product's job.&lt;/p&gt;

&lt;p&gt;For completeness: non-destructive diff editing (Edit Upgrade) exists as a separate licence, but &lt;strong&gt;every one of the four principles above is read-only work.&lt;/strong&gt; What you need here is the View side.&lt;/p&gt;

&lt;p&gt;And if a huge log is eating your disk and you want it &lt;strong&gt;compressed for storage while staying searchable at speed, give &lt;a href="https://uvp.y42u.net/en/pro-en/" rel="noopener noreferrer"&gt;UwView Pro&lt;/a&gt; a look&lt;/strong&gt; — persistent index, compressed-cache search, and ~1/9 storage make both reopening and searching a step faster (all OS, one-time or monthly).&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Part 1: four go-to tools that sink under huge files: &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps01-huge-file-tool-limits-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uwview-ps01-huge-file-tool-limits-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 3: four character-encoding traps and how to isolate them: &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps03-japanese-encoding-traps-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uwview-ps03-japanese-encoding-traps-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 4: deciding between delete, keep, and compress: &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps04-log-retention-decision-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uwview-ps04-log-retention-decision-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 6: picking traces out of tens of millions of auth-log lines: &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps06-intrusion-triage-auth-logs-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uwview-ps06-intrusion-triage-auth-logs-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 7: four ways to read a timestamp as a weapon: &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps07-timestamp-driven-triage-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uwview-ps07-timestamp-driven-triage-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 8: keeping logs compressed and still searchable: &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps08-compressed-archive-search-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uwview-ps08-compressed-archive-search-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 10: four things to set up for the you of 2 a.m.: &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps10-oncall-night-preparation-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uwview-ps10-oncall-night-preparation-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 13: four techniques for inspecting huge data: &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps13-huge-data-inspection-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uwview-ps13-huge-data-inspection-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 14: the traces are in the raw log: &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps14-attack-traces-raw-logs-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uwview-ps14-attack-traces-raw-logs-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 15: four limits of command-line craft: &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps15-cli-craft-limits-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uwview-ps15-cli-craft-limits-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Drill-down search — narrowing a result by another term: &lt;a href="https://uvp.y42u.net/en/blog/uvp-drilldown-search-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uvp-drilldown-search-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Sequence search — finding only what appears in that order: &lt;a href="https://uvp.y42u.net/en/blog/uvp-sequence-search-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uvp-sequence-search-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Why search results moved into a separate window: &lt;a href="https://uvp.y42u.net/en/blog/uwview-filter-popup-jump-save-context-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uwview-filter-popup-jump-save-context-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Archive plus session restore, as a working flow: &lt;a href="https://uvp.y42u.net/en/blog/uwview-archive-session-restore-workflow-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uwview-archive-session-restore-workflow-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Source code (GitHub): &lt;a href="https://github.com/amru195704/UwView" rel="noopener noreferrer"&gt;https://github.com/amru195704/UwView&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;From the developer&lt;/strong&gt;: a full list of my apps, Kindle books, and open-source work is on &lt;a href="https://github.com/amru195704" rel="noopener noreferrer"&gt;GitHub: amru195704&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A note&lt;/strong&gt;&lt;br&gt;
This article is provided for reference and makes no guarantee of accuracy or completeness. Actual incident response, evidence preservation, and audit work must follow your organisation's policy and applicable law and regulatory guidance. Nothing here is advice about legal admissibility. Times, line numbers, and filenames are illustrative and do not describe any real case. The behaviour of &lt;code&gt;logrotate&lt;/code&gt;, &lt;code&gt;journalctl&lt;/code&gt;, &lt;code&gt;sha256sum&lt;/code&gt;, &lt;code&gt;gzip&lt;/code&gt;, and &lt;code&gt;grep&lt;/code&gt; varies by implementation (GNU/BSD/busybox), version, build options, and distribution defaults — check option names and defaults against your own &lt;code&gt;man&lt;/code&gt; pages. Measured figures come from one specific setup and are not a guarantee of the same result. Disk type, filesystem, fragmentation, encryption, page-cache state, and concurrent load all change the outcome substantially. If you spot an error, a comment is welcome and I'll check and correct it.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>security</category>
      <category>devops</category>
      <category>sre</category>
      <category>logging</category>
    </item>
    <item>
      <title>"less Is Enough" — Until It Isn't: Four Limits of Command-Line Craft</title>
      <dc:creator>y4u</dc:creator>
      <pubDate>Fri, 11 Sep 2026 09:56:39 +0000</pubDate>
      <link>https://dev.to/amru195704/less-is-enough-until-it-isnt-four-limits-of-command-line-craft-5e30</link>
      <guid>https://dev.to/amru195704/less-is-enough-until-it-isnt-four-limits-of-command-line-craft-5e30</guid>
      <description>&lt;p&gt;You press &lt;code&gt;G&lt;/code&gt;. Ninety seconds pass.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;less&lt;/code&gt; was supposed to be the light tool. &lt;strong&gt;So what was being read during those ninety seconds?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The answer isn't about the quality of the tool. It's about one thing: where the line breaks are.&lt;/p&gt;

&lt;p&gt;Every part of this series has been about where general-purpose tools stop. This one goes at the four that are trusted most — &lt;code&gt;less&lt;/code&gt;, Vim, pipe craft, and &lt;code&gt;grep&lt;/code&gt;. None of them is broken. They're all excellent. And past a certain size, all four hit the same wall. &lt;strong&gt;The goal here is to draw that wall as a line you can see.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Up front: UwView scrolls to the end of a huge file from the moment it opens; the index builds in the background and line numbers appear when it finishes. Pro saves the index and the compression, so a 47.73 GB log you've opened once reopens &lt;strong&gt;instantly, with line numbers&lt;/strong&gt;, from the second open onward (0.02–0.07 s measured on one setup; results vary — details at the end)
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Except where noted, every timing and transfer rate below is &lt;strong&gt;environment-dependent&lt;/strong&gt;. The numbers are worked examples and measurements meant to show the shape of the problem, not a promise about your machine.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  1. &lt;code&gt;less&lt;/code&gt; was enough — until line jumps and Japanese search
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Situation
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;less huge.log&lt;/code&gt; opens instantly. So far, so good.&lt;/p&gt;

&lt;p&gt;It falls apart on the next keystroke. &lt;code&gt;G&lt;/code&gt; to jump to the end: it stalls. &lt;code&gt;-N&lt;/code&gt; for line numbers: it stalls harder. &lt;code&gt;12000000g&lt;/code&gt; to reach a specific line: stalls again. And in a Japanese log, typing &lt;code&gt;/エラー&lt;/code&gt; returns &lt;strong&gt;nothing at all&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;"&lt;code&gt;less&lt;/code&gt; is enough" was a sound judgement. On a few megabytes, nothing is lighter.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why it happens
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;less&lt;/code&gt; is fast because it doesn't read, and slow the moment you ask something it can't answer without reading.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;less&lt;/code&gt; treats a file as a sequence of lines, but at open time it knows exactly one thing: the first screenful. Where the file's lines begin and end is determined by newline bytes, and &lt;strong&gt;you cannot know where those are without reading them&lt;/strong&gt;. There is no way to answer "at which byte does line 12,000,000 start?" by seeking. So:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;G&lt;/code&gt; (go to end) = read every byte to the end, counting newlines&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-N&lt;/code&gt; (show line numbers) = count newlines up to the current position&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;12000000g&lt;/code&gt; (jump to a line) = find the 12,000,000th newline from the top&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All three are &lt;strong&gt;the same operation: read everything, count newlines&lt;/strong&gt;. On a 50 GB file, that's 50 GB read. The ninety seconds is what that costs. &lt;code&gt;less&lt;/code&gt; isn't being inefficient.&lt;/p&gt;

&lt;p&gt;The Japanese wall is a different problem. &lt;strong&gt;&lt;code&gt;less&lt;/code&gt; does not transcode.&lt;/strong&gt; It only decides whether bytes are displayable (&lt;code&gt;LESSCHARSET&lt;/code&gt; configures that decision, not a conversion). If your terminal is UTF-8 and the file is CP932, the UTF-8 bytes you typed for &lt;code&gt;/エラー&lt;/code&gt; simply do not appear in the file's CP932 bytes. &lt;strong&gt;The string isn't absent; it exists as a different byte sequence&lt;/strong&gt; — the topic of &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps03-japanese-encoding-traps-en/" rel="noopener noreferrer"&gt;Part 3&lt;/a&gt; and &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps11-legacy-encoding-euc-utf16-en/" rel="noopener noreferrer"&gt;Part 11&lt;/a&gt;, showing up here as a pager keystroke.&lt;/p&gt;

&lt;h3&gt;
  
  
  What general-purpose tools do, and where they stop
&lt;/h3&gt;

&lt;p&gt;There are standard moves.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;less &lt;span class="nt"&gt;-n&lt;/span&gt; huge.log            &lt;span class="c"&gt;# stop computing line numbers → G gets light (no line numbers)&lt;/span&gt;
&lt;span class="nv"&gt;LESSCHARSET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;utf-8 less huge.log
iconv &lt;span class="nt"&gt;-f&lt;/span&gt; CP932 &lt;span class="nt"&gt;-t&lt;/span&gt; UTF-8 huge.log | less    &lt;span class="c"&gt;# convert, then pipe in&lt;/span&gt;
&lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; 200M huge.log | less               &lt;span class="c"&gt;# just look at the tail&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first one works. It is also a trade: &lt;code&gt;-n&lt;/code&gt; buys speed by giving up line numbers, so &lt;strong&gt;you cannot have both&lt;/strong&gt;. On any job where the incident report needs "line 12,034,551," you end up back on &lt;code&gt;-N&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The third is the one that quietly costs the most. The moment you write &lt;code&gt;iconv | less&lt;/code&gt;, what &lt;code&gt;less&lt;/code&gt; receives is &lt;strong&gt;standard input, which cannot be seeked&lt;/strong&gt;. You can still scroll back — because &lt;code&gt;less&lt;/code&gt; is &lt;strong&gt;writing what flowed past into a temp file&lt;/strong&gt;. A second 50 GB copy grows in &lt;code&gt;/tmp&lt;/code&gt;. And to move forward you still have to push every byte through the converter, so &lt;code&gt;G&lt;/code&gt; becomes "read everything, converting as you go." The mapping to the original line numbers survives (converting doesn't change the line count), but displaying those numbers costs exactly what it cost before.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;tail -c&lt;/code&gt; is a genuine escape hatch, but &lt;strong&gt;the moment you slice, it stops being the original&lt;/strong&gt;: the first line may begin mid-character and render as garbage, and the line numbering restarts at 1.&lt;/p&gt;

&lt;p&gt;Three limits. One, &lt;strong&gt;"see the end," "show line numbers," and "jump to a line" all demand a full read.&lt;/strong&gt; Two, &lt;strong&gt;the result of that full read is discarded the instant you quit&lt;/strong&gt; — open it again tomorrow and it's another ninety seconds. Three, &lt;strong&gt;there is no way to resolve an encoding mismatch without inserting a conversion&lt;/strong&gt;, and inserting one costs you both seeking and disk.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. &lt;code&gt;:e huge.log&lt;/code&gt; freezes — what Vim can configure, and what it can't
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Situation
&lt;/h3&gt;

&lt;p&gt;You type &lt;code&gt;vim huge.log&lt;/code&gt;, hit Enter, and nothing comes back. For a stretch, &lt;code&gt;Ctrl-C&lt;/code&gt; doesn't even land. Eventually there's a swap-file warning, or a message about memory.&lt;/p&gt;

&lt;p&gt;The reason people reach for Vim over &lt;code&gt;less&lt;/code&gt; is usually: search, read the surrounding lines in place, and mark something. The goal is investigation, not editing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why it happens
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;A Vim buffer is designed to hold the entire file as an array of lines.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That isn't a flaw; it's what an editor has to be. Inserting, deleting, and replacing any line requires holding the sequence as a structure. So &lt;code&gt;:e&lt;/code&gt; reads the whole file, and while reading, builds several other things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A swapfile (&lt;code&gt;.swp&lt;/code&gt;)&lt;/strong&gt; — another copy on disk, for crash recovery&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Undo history&lt;/strong&gt; — and another copy on disk if &lt;code&gt;undofile&lt;/code&gt; is on&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Syntax highlighting state&lt;/strong&gt; — deciding whether a line is inside a comment means scanning back through earlier lines; this is what makes very long lines and very large files collapse&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fold computation&lt;/strong&gt; — &lt;code&gt;foldmethod=syntax&lt;/code&gt; walks everything&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So &lt;code&gt;:e huge.log&lt;/code&gt; starts &lt;strong&gt;reading 50 GB, writing something like 50 GB back to disk, and running a parser over every line&lt;/strong&gt;, all at once. Ninety seconds isn't in the running.&lt;/p&gt;

&lt;h3&gt;
  
  
  What general-purpose tools do, and where they stop
&lt;/h3&gt;

&lt;p&gt;Vim can be told to do considerably less.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vim &lt;span class="nt"&gt;-u&lt;/span&gt; NONE &lt;span class="nt"&gt;-N&lt;/span&gt; huge.log     &lt;span class="c"&gt;# no vimrc, no plugins&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight viml"&gt;&lt;code&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nb"&gt;syntax&lt;/span&gt; off
&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="k"&gt;noswapfile&lt;/span&gt;
&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="nb"&gt;noundofile&lt;/span&gt;
&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="nb"&gt;nofoldenable&lt;/span&gt;
&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="nb"&gt;lazyredraw&lt;/span&gt;
&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="nb"&gt;synmaxcol&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="m"&gt;200&lt;/span&gt;          " give &lt;span class="k"&gt;up&lt;/span&gt; highlighting past column &lt;span class="m"&gt;200&lt;/span&gt;
&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="nb"&gt;viminfo&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;LargeFile&lt;/code&gt;-style plugins essentially apply exactly this automatically. The effect is real and clearly noticeable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One thing does not shrink: the number of bytes read.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every setting above makes the work &lt;em&gt;after&lt;/em&gt; reading cheaper; none of them touches the premise that &lt;code&gt;:e&lt;/code&gt; reads all of it. Vim has no partial-load model — no way to keep most of the file on disk and page in only the region you're looking at. Which means &lt;strong&gt;opening a 48 GB file in Vim on a 16 GB machine is not a configuration problem. It's a structural one.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So in practice, people slice.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s1"&gt;'12000000,12000500p'&lt;/span&gt; huge.log &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; slice.txt &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; vim slice.txt
&lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'NR&amp;gt;=12000000 &amp;amp;&amp;amp; NR&amp;lt;=12000500 {print NR": "$0}'&lt;/span&gt; huge.log &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; slice.txt   &lt;span class="c"&gt;# keeps line numbers&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three limits. One, &lt;strong&gt;you must choose the range before you can look at it.&lt;/strong&gt; You find out 500 lines wasn't enough only after opening, and each retry sends &lt;code&gt;sed&lt;/code&gt; back to read from the top of the file. Two, &lt;strong&gt;a plain slice loses the original line numbers&lt;/strong&gt; — the second form keeps them via &lt;code&gt;NR&lt;/code&gt;, but now the numbers are text inside the line, so you can't paste it straight into a report. Three, &lt;strong&gt;&lt;code&gt;slice.txt&lt;/code&gt; files accumulate.&lt;/strong&gt; By the end of an investigation there are twenty unrelated fragments in &lt;code&gt;/tmp&lt;/code&gt;, and you can no longer say which range each one was.&lt;/p&gt;

&lt;p&gt;None of this is an argument against Vim. &lt;strong&gt;If the job is editing, Vim is the answer.&lt;/strong&gt; The problem is opening Vim when you only wanted to look, and paying the full cost of an editor for it.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Pipe craft vs. a viewer — where the line actually is
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Situation
&lt;/h3&gt;

&lt;p&gt;One line of &lt;code&gt;tail&lt;/code&gt;, &lt;code&gt;head&lt;/code&gt;, &lt;code&gt;sed&lt;/code&gt;, &lt;code&gt;awk&lt;/code&gt;, &lt;code&gt;sort&lt;/code&gt;, and &lt;code&gt;uniq&lt;/code&gt; handles most things. It genuinely has, for years.&lt;/p&gt;

&lt;p&gt;It snags in one specific place: &lt;strong&gt;when you don't yet know what to look for.&lt;/strong&gt; You have the error message. You don't have the condition that produces it. So you can't decide what to hand &lt;code&gt;grep&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You &lt;code&gt;head -100&lt;/code&gt; and stare. You cut a column with &lt;code&gt;awk&lt;/code&gt;. You look at a distribution with &lt;code&gt;sort | uniq -c&lt;/code&gt;. Then back again. Ten round trips. Twenty.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why it happens
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;A pipe demands that you decide the question first.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To write &lt;code&gt;awk 'NR&amp;gt;=a &amp;amp;&amp;amp; NR&amp;lt;=b'&lt;/code&gt; you must already know a and b. To write &lt;code&gt;grep 'X'&lt;/code&gt; you must already know X. But the first half of an investigation is precisely the part where &lt;strong&gt;a, b, and X are decided by looking.&lt;/strong&gt; You're asked for a question at the stage where you don't have one yet.&lt;/p&gt;

&lt;p&gt;The structure of a pipeline has a few more consequences:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Intermediate state doesn't survive.&lt;/strong&gt; After &lt;code&gt;grep A | grep B | grep C&lt;/code&gt;, how many lines A matched and how many B matched is gone. You see the final count without knowing which condition did the work.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Line numbers die after stage one.&lt;/strong&gt; &lt;code&gt;grep -n&lt;/code&gt; counts within the block that reached it, not in the original file.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Every retry starts from the beginning.&lt;/strong&gt; Change one character and the pipeline re-reads the file from the top.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The flip side is equally clear. &lt;strong&gt;A pipeline is unbeatable at repeating the same question.&lt;/strong&gt; A one-liner goes into cron, into CI, and over &lt;code&gt;ssh&lt;/code&gt; unchanged. When the log lives on a remote server you can't pull down, a pipeline is the only option (a constraint that connects directly to the "where do you keep it" question from &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps04-log-retention-decision-en/" rel="noopener noreferrer"&gt;Part 4&lt;/a&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  What general-purpose tools do, and where they stop
&lt;/h3&gt;

&lt;p&gt;The line, in one sentence:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;If the shape of the answer is fixed, use a pipe. If you're still looking for the shape of the answer, use a viewer.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Concretely:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Nature of the work&lt;/th&gt;
&lt;th&gt;Better fit&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Cutting columns, computing, routine transformation&lt;/td&gt;
&lt;td&gt;Pipe&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Running it daily, repeatedly, automatically&lt;/td&gt;
&lt;td&gt;Pipe&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Staying inside a remote server&lt;/td&gt;
&lt;td&gt;Pipe&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Exploring what to look for&lt;/td&gt;
&lt;td&gt;Viewer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Counting what's common inside a narrowed result&lt;/td&gt;
&lt;td&gt;Either (see below)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reading around a hit to make a judgement&lt;/td&gt;
&lt;td&gt;Viewer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Narrowing while changing the condition ten times&lt;/td&gt;
&lt;td&gt;Viewer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Citing a line with its position in the original&lt;/td&gt;
&lt;td&gt;Viewer&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In practice you use both, usually as "narrow with a pipe, then read in a viewer." There's a trap in that flow, though: &lt;strong&gt;narrowing already cost you one full read&lt;/strong&gt;, and it's easy not to notice. And if you drop the narrowed result into another file, &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps09-read-raw-structured-data-en/" rel="noopener noreferrer"&gt;Part 9&lt;/a&gt; applies — that file is no longer the original.&lt;/p&gt;

&lt;p&gt;Two limits. One, &lt;strong&gt;full reads pile up in proportion to the number of exploratory round trips.&lt;/strong&gt; Ninety seconds each, twenty trips, half an hour. Two, &lt;strong&gt;the history of those trips dies with the terminal.&lt;/strong&gt; Tomorrow morning starts with remembering which conditions you already ruled out.&lt;/p&gt;

&lt;p&gt;To be honest about it: this is not a case for replacing pipes with a viewer. &lt;strong&gt;Work that needs no interaction belongs in a pipe.&lt;/strong&gt; What gets replaced is only the interactive first half.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. &lt;code&gt;grep&lt;/code&gt; isn't slow — you're I/O-bound
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Situation
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;grep&lt;/code&gt; felt slow, so you switched to &lt;code&gt;rg&lt;/code&gt; (ripgrep). &lt;strong&gt;Almost no difference.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Or the reverse: you ran the same command twice and the second run was suspiciously fast. Convinced you'd made a mistake, you measured again.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why it happens
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The bottleneck isn't the CPU. It's the disk.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Fixed-string search is very cheap on a modern CPU — Boyer-Moore-family algorithms with SIMD, running close to memory bandwidth. Which means &lt;strong&gt;the CPU is sitting there waiting for the disk to deliver bytes.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The floor is a division problem:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Medium&lt;/th&gt;
&lt;th&gt;Rough sequential read&lt;/th&gt;
&lt;th&gt;Just to read 50 GB (theoretical floor)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;HDD&lt;/td&gt;
&lt;td&gt;100–200 MB/s&lt;/td&gt;
&lt;td&gt;~4–8 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SATA SSD&lt;/td&gt;
&lt;td&gt;~500 MB/s&lt;/td&gt;
&lt;td&gt;~100 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NVMe SSD&lt;/td&gt;
&lt;td&gt;2–7 GB/s&lt;/td&gt;
&lt;td&gt;~7–25 s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;This table is &lt;strong&gt;calculated from typical sequential-read figures, not measured&lt;/strong&gt;. Real numbers move with the filesystem, fragmentation, encryption, competing processes, and whether the file is on the far side of NFS/SMB.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Searching 50 GB on an HDD &lt;strong&gt;cannot beat four minutes&lt;/strong&gt;, no matter how clever the search program is. &lt;code&gt;grep&lt;/code&gt; and &lt;code&gt;rg&lt;/code&gt; performing the same is exactly what you'd expect. The difference shows up only where the work is CPU-bound: complex regular expressions, large numbers of small files (&lt;code&gt;rg&lt;/code&gt; parallelises across them), matching that involves Unicode normalisation.&lt;/p&gt;

&lt;p&gt;The fast second run has the same explanation. Whatever the first run pulled in landed in the kernel's page cache, so &lt;strong&gt;the second read came from RAM, not disk&lt;/strong&gt; — dramatically faster, up to the amount RAM can hold. Which is why &lt;strong&gt;a benchmark number means nothing unless it says whether the cache was warm.&lt;/strong&gt; That's the reason every measurement in this series carries its conditions.&lt;/p&gt;

&lt;h3&gt;
  
  
  What general-purpose tools do, and where they stop
&lt;/h3&gt;

&lt;p&gt;First, find out which side is the bottleneck.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;time grep&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s1"&gt;'ERROR'&lt;/span&gt; huge.log
&lt;span class="c"&gt;# real 4m12s / user 0m21s / sys 0m38s  → the CPU is idle = I/O-bound&lt;/span&gt;

&lt;span class="c"&gt;# measure the sequential ceiling on this machine (bypassing the cache)&lt;/span&gt;
&lt;span class="nb"&gt;dd &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;huge.log &lt;span class="nv"&gt;of&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/dev/null &lt;span class="nv"&gt;bs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1M &lt;span class="nv"&gt;count&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;20000 &lt;span class="nv"&gt;iflag&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;direct

&lt;span class="c"&gt;# watch I/O wait while it runs&lt;/span&gt;
iostat &lt;span class="nt"&gt;-x&lt;/span&gt; 1        &lt;span class="c"&gt;# %util pinned at 100 means saturated&lt;/span&gt;
vmstat 1           &lt;span class="c"&gt;# a large wa column says the same&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;user + sys&lt;/code&gt; is tiny next to &lt;code&gt;real&lt;/code&gt;, the CPU is waiting. Swapping the search program at that point is &lt;strong&gt;buying a faster car to sit in the same traffic jam.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Once you know you're I/O-bound, there are exactly three things you can do:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Put it on faster media&lt;/strong&gt; (HDD → SSD — reliable, but you often don't get to choose where the file lives)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read in parallel&lt;/strong&gt; (works across many files; on one huge file it plateaus depending on the medium)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Read fewer bytes&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The third is the real one, and it splits in two. &lt;strong&gt;An index&lt;/strong&gt; — record where things are, so next time you don't read everything. And &lt;strong&gt;compression&lt;/strong&gt; — if I/O is the bottleneck, reading a smaller compressed byte stream and expanding it on the CPU can be faster than reading the raw file, because the CPU is the resource you have spare. That arithmetic is why "search it while it stays compressed" in &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps08-compressed-archive-search-en/" rel="noopener noreferrer"&gt;Part 8&lt;/a&gt; makes sense.&lt;/p&gt;

&lt;p&gt;One limit, and it's a heavy one. &lt;strong&gt;Neither &lt;code&gt;grep&lt;/code&gt; nor &lt;code&gt;rg&lt;/code&gt; carries anything forward.&lt;/strong&gt; The structure of the 50 GB you just spent four minutes reading vanishes when the command exits. Change the condition and it's four minutes again. The page cache helps only as far as RAM reaches, and 50 GB doesn't fit.&lt;/p&gt;




&lt;h2&gt;
  
  
  What all four had in common
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;What breaks it&lt;/th&gt;
&lt;th&gt;Direct cause&lt;/th&gt;
&lt;th&gt;The workaround&lt;/th&gt;
&lt;th&gt;What's left over&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;less&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;G&lt;/code&gt;, &lt;code&gt;-N&lt;/code&gt;, line jumps, Japanese search&lt;/td&gt;
&lt;td&gt;Line boundaries can't be known without reading&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;-n&lt;/code&gt;, or insert &lt;code&gt;iconv&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Speed and line numbers are mutually exclusive; converting costs seeking and disk&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vim&lt;/td&gt;
&lt;td&gt;The moment you &lt;code&gt;:e&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;The buffer assumes the whole file&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;syntax off&lt;/code&gt;, &lt;code&gt;noswapfile&lt;/code&gt;, slicing&lt;/td&gt;
&lt;td&gt;Bytes read doesn't drop; slicing destroys the original coordinates&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pipes&lt;/td&gt;
&lt;td&gt;The moment you start exploring&lt;/td&gt;
&lt;td&gt;Demands the question up front&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;tee&lt;/code&gt; the intermediate stages&lt;/td&gt;
&lt;td&gt;Full read per round trip; per-stage counts and history don't survive&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;grep&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The moment the file exceeds RAM&lt;/td&gt;
&lt;td&gt;I/O-bound; the CPU is waiting&lt;/td&gt;
&lt;td&gt;Faster media, parallelism&lt;/td&gt;
&lt;td&gt;The scan isn't carried forward&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Four tools, four different design philosophies, four different failure modes. The rightmost column is the same shape in all four rows because &lt;strong&gt;all four rest on the same premise: read the whole file, every time.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;On a small file that premise is free. A 0.1-second full read is indistinguishable from no read at all, so the design is correct. &lt;strong&gt;It only breaks when that 0.1 second becomes ninety.&lt;/strong&gt; Nothing about the operation got harder. It just stopped being worth it. This series keeps arriving at the same sentence, but this time the tools themselves are right — which makes the cause harder to see. You assume you're holding it wrong and go looking for a setting.&lt;/p&gt;

&lt;p&gt;Three things are needed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Don't throw away the line boundaries you already computed.&lt;/strong&gt; The ninety seconds in &lt;code&gt;less&lt;/code&gt;, the &lt;code&gt;:e&lt;/code&gt;, the pipeline round trips — all of them are counting the same newlines over again. Build it once, save it, and the second time there's nothing to count.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read fewer bytes.&lt;/strong&gt; Under an I/O bottleneck, this is the only real speedup. Searching without decompressing means less to read.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep the original's coordinates while you narrow.&lt;/strong&gt; A slice, an &lt;code&gt;iconv&lt;/code&gt;, a &lt;code&gt;sed -n&lt;/code&gt; — each one stops being the original the moment it passes through. On any job that ends in "line number, please," that's what bites last.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Back to the ninety seconds. That wasn't &lt;code&gt;less&lt;/code&gt; being slow. &lt;strong&gt;It was newlines being counted&lt;/strong&gt; — and the count was thrown away the moment you pressed &lt;code&gt;q&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The tool I use
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://uvp.y42u.net/en/" rel="noopener noreferrer"&gt;UwView&lt;/a&gt; (free), which I develop, displays, scrolls, and searches huge text &lt;strong&gt;from the moment it opens&lt;/strong&gt;. It doesn't load the file into memory, so files larger than RAM open fine. The index builds in the background and &lt;strong&gt;line numbers appear when it completes&lt;/strong&gt; (most viewers show only the head until indexing finishes). Nothing is split and nothing is extracted, so the original stays one file, unmodified. Section 1's &lt;code&gt;G&lt;/code&gt; — moving to the end — works from the first second.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Switch encodings without reopening&lt;/strong&gt;: UTF-8 / Shift-JIS (CP932) / EUC-JP / UTF-16 are detected automatically, and when detection is wrong you switch in place without rebuilding the index. Section 1's "inserting &lt;code&gt;iconv&lt;/code&gt; costs you seeking and disk" doesn't arise.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No editing engine&lt;/strong&gt;: the swapfile, undo history, and syntax parsing that make Vim's &lt;code&gt;:e&lt;/code&gt; expensive simply don't exist here. Section 2's list of settings is unnecessary. (For work that genuinely needs editing, Edit Upgrade is a separate licence.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Context width is changed afterwards&lt;/strong&gt;: you don't have to decide how many surrounding lines you need before opening. Section 2's "re-run &lt;code&gt;sed&lt;/code&gt; after discovering 500 lines wasn't enough" disappears. (Free gives ±1 line; adjustable ±N is Pro.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Search results open in a separate popup&lt;/strong&gt; you can jump from into the original (&lt;a href="https://uvp.y42u.net/en/blog/uwview-filter-popup-jump-save-context-en/" rel="noopener noreferrer"&gt;write-up&lt;/a&gt;) — the replacement for section 3's return trip to the terminal.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And &lt;a href="https://uvp.y42u.net/en/pro-en/" rel="noopener noreferrer"&gt;UwView Pro&lt;/a&gt; is where those three requirements live.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It saves the index and the compression&lt;/strong&gt;: a file you've opened once reopens &lt;strong&gt;instantly, with line numbers&lt;/strong&gt;, from the second open onward (0.02–0.07 s measured on a 47.73 GB text file; one setup, results vary). Section 1's "ninety seconds discarded on &lt;code&gt;q&lt;/code&gt;" and section 4's "the scan isn't carried forward" are the same problem, solved by the same feature.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Search through the compressed cache&lt;/strong&gt;: exactly section 4's arithmetic — under an I/O bottleneck, reading fewer bytes is the real speedup. Storage at roughly 1/9 while still searchable helps both the disk and the wait (&lt;a href="https://uvp.y42u.net/en/blog/uwview-ps08-compressed-archive-search-en/" rel="noopener noreferrer"&gt;keeping logs compressed and still searchable&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Drill-down search&lt;/strong&gt;: narrow a result by another term, then another. Tabs carry &lt;code&gt;term (count)&lt;/code&gt;, which is section 3's "per-stage counts don't survive." Stages after the first search only the previous window, so there's no wait, and backing out is one click on an earlier tab. &lt;strong&gt;The original line numbers are preserved all the way down&lt;/strong&gt; (&lt;a href="https://uvp.y42u.net/en/blog/uvp-drilldown-search-en/" rel="noopener noreferrer"&gt;write-up&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resume tomorrow&lt;/strong&gt;: tabs and conditions persist, so section 3's "close the terminal and the history goes with it" doesn't happen (&lt;a href="https://uvp.y42u.net/en/blog/uwview-archive-session-restore-workflow-en/" rel="noopener noreferrer"&gt;archive × session restore workflow&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One row from section 3's table has crossed the line: &lt;strong&gt;frequency tallying.&lt;/strong&gt; As of v1.5.0, drill-down search includes &lt;strong&gt;Tally&lt;/strong&gt; — the equivalent of &lt;code&gt;grep ... | grep -oE '...' | sort | uniq -c | sort -rn&lt;/code&gt;. Apply a regular expression to the hits, take the first capture group as the key, and get a ranked frequency table on top of whatever stage you've narrowed to, with the hit count, the number of distinct values, the elapsed time, and CSV export (&lt;a href="https://uvp.y42u.net/en/blog/uvp-tally-uniq-c-drilldown-en/" rel="noopener noreferrer"&gt;uniq -c, in a GUI&lt;/a&gt;). It's one step past section 3's complaint that per-stage counts don't survive.&lt;/p&gt;

&lt;p&gt;It stops at a frequency ranking, though — &lt;strong&gt;no sums, no averages, no charts.&lt;/strong&gt; That's an analysis tool's job.&lt;/p&gt;

&lt;p&gt;With that said, honestly: &lt;strong&gt;UwView is not a replacement for a shell.&lt;/strong&gt; It won't cut arbitrary columns and compute on them, won't join files, and won't run on a schedule in cron or CI. When the work has to stay inside a remote server, a pipeline is the only answer. Section 3's line moved by exactly one row, and otherwise stands: &lt;strong&gt;this tool is mainly the right-hand column — exploring, context, narrowing, citing.&lt;/strong&gt; Routine transformation and automation still belong to &lt;code&gt;awk&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;One more honest note. &lt;strong&gt;A complete first index takes real time.&lt;/strong&gt; Section 4's I/O bottleneck applies to UwView the same as to anything else; reading 50 GB once doesn't become free. The difference is whether that result is saved and reused. &lt;strong&gt;If you only ever open the file once, &lt;code&gt;less&lt;/code&gt; is enough&lt;/strong&gt; — that judgement is still correct today. It stops being correct on the day you start reopening the same file.&lt;/p&gt;

&lt;p&gt;And if a huge log is eating your disk and you want it &lt;strong&gt;compressed for storage while staying searchable at speed, give &lt;a href="https://uvp.y42u.net/en/pro-en/" rel="noopener noreferrer"&gt;UwView Pro&lt;/a&gt; a look&lt;/strong&gt; — persistent index, compressed-cache search, and ~1/9 storage make both reopening and searching a step faster (all OS, one-time or monthly).&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Part 1: four go-to tools that sink under huge files: &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps01-huge-file-tool-limits-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uwview-ps01-huge-file-tool-limits-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 3: four encoding traps and how to isolate them: &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps03-japanese-encoding-traps-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uwview-ps03-japanese-encoding-traps-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 4: deciding between delete, keep, and compress: &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps04-log-retention-decision-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uwview-ps04-log-retention-decision-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 8: keeping logs compressed and still searchable: &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps08-compressed-archive-search-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uwview-ps08-compressed-archive-search-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 9: reading structured data raw: &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps09-read-raw-structured-data-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uwview-ps09-read-raw-structured-data-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 10: four things to set up for the you at 2 a.m.: &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps10-oncall-night-preparation-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uwview-ps10-oncall-night-preparation-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 11: reading legacy encodings in 2026: &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps11-legacy-encoding-euc-utf16-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uwview-ps11-legacy-encoding-euc-utf16-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 14: the traces are in the raw log: &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps14-attack-traces-raw-logs-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uwview-ps14-attack-traces-raw-logs-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;3GB/10GB/48GB × HDD/SSD/internal SSD — a nine-cell benchmark: &lt;a href="https://uvp.y42u.net/en/blog/uwview-pro-benchmark-3sizes-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uwview-pro-benchmark-3sizes-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;An honest comparison with klogg (Ver1.1.1, complete edition): &lt;a href="https://uvp.y42u.net/en/blog/uwview-klogg-feature-comparison-v111-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uwview-klogg-feature-comparison-v111-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Drill-down search — narrowing a result by another term: &lt;a href="https://uvp.y42u.net/en/blog/uvp-drilldown-search-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uvp-drilldown-search-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;uniq -c joins the GUI (Tally in drill-down search): &lt;a href="https://uvp.y42u.net/en/blog/uvp-tally-uniq-c-drilldown-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uvp-tally-uniq-c-drilldown-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Why search results open in a separate window: &lt;a href="https://uvp.y42u.net/en/blog/uwview-filter-popup-jump-save-context-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uwview-filter-popup-jump-save-context-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Archive × session restore workflow: &lt;a href="https://uvp.y42u.net/en/blog/uwview-archive-session-restore-workflow-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uwview-archive-session-restore-workflow-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Source code (GitHub): &lt;a href="https://github.com/amru195704/UwView" rel="noopener noreferrer"&gt;https://github.com/amru195704/UwView&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;From the developer&lt;/strong&gt;: a full list of my apps, Kindle books, and open-source work is on &lt;a href="https://github.com/amru195704" rel="noopener noreferrer"&gt;GitHub: amru195704&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A note&lt;/strong&gt;&lt;br&gt;
This article is provided for reference and makes no guarantee of accuracy or completeness. The behaviour of &lt;code&gt;less&lt;/code&gt;, Vim, &lt;code&gt;grep&lt;/code&gt;, and &lt;code&gt;awk&lt;/code&gt; varies by implementation (GNU/BSD/busybox), version, build options, and locale settings — always confirm option names and effects against your own &lt;code&gt;man&lt;/code&gt; pages. Except where explicitly marked as measured, the transfer rates and durations here are calculated from typical performance figures, not observed. Figures marked as measured come from one specific setup and are not a guarantee of the same result. Disk type, filesystem, fragmentation, encryption, page-cache state, and competing processes all move the numbers substantially. If you spot an error, a comment is welcome and I'll check and correct it.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>linux</category>
      <category>commandline</category>
      <category>devops</category>
      <category>performance</category>
    </item>
    <item>
      <title>The Traces Are in the Raw Log — Four Views on Lateral Movement, SQLi, and After-the-Fact Investigation</title>
      <dc:creator>y4u</dc:creator>
      <pubDate>Fri, 11 Sep 2026 09:54:55 +0000</pubDate>
      <link>https://dev.to/amru195704/the-traces-are-in-the-raw-log-four-views-on-lateral-movement-sqli-and-after-the-fact-22b</link>
      <guid>https://dev.to/amru195704/the-traces-are-in-the-raw-log-four-views-on-lateral-movement-sqli-and-after-the-fact-22b</guid>
      <description>&lt;p&gt;The encryption took eleven minutes.&lt;/p&gt;

&lt;p&gt;What matters is the six days before it. &lt;strong&gt;Which log do you read first, and in what order?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The trace is never in one line. It's in the arrangement.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://uvp.y42u.net/en/blog/uwview-ps06-intrusion-triage-auth-logs-en/" rel="noopener noreferrer"&gt;Part 6&lt;/a&gt; covered what to look at in the hours right after a suspicion is raised. This one is about what comes after: &lt;strong&gt;showing, in a defensible way, what already happened.&lt;/strong&gt; Tracing lateral movement, investigating a ransomware event after the fact, finding the SQLi that got past the WAF, and inventorying personal data. Different goals, different people — and all four operating under the same constraints: &lt;strong&gt;don't touch the original, don't let the data leave, and be able to cite your evidence.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Up front: UwView Pro's sequence search matches only where &lt;code&gt;w1 → w2 → w3&lt;/code&gt; appear in that order, so an attack pattern like &lt;code&gt;auth success → privilege escalation → connection to another host&lt;/code&gt; becomes the query itself. Drill-down search stacks the narrowing and shows the count at each stage, so you can change a condition and see what it did. Everything runs on your own machine — the file is never sent anywhere (measurements are from one setup and vary; details at the end)
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;This article assumes you already have preserved logs in hand and are deciding how to read them. Containment, preservation, notification, and reporting procedures themselves belong to your organisation's policy and the relevant authorities.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  1. Lateral movement — the identifier changes hands mid-trail
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Situation
&lt;/h3&gt;

&lt;p&gt;You know patient zero. The question from above is always the same: &lt;strong&gt;"How far did it spread?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;On disk you have the VPN gateway log, the authentication server log, the jump host's &lt;code&gt;sshd&lt;/code&gt; log, and the file server's audit log. Four products, four formats. One attacker's behaviour, split across four files.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why it happens
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Lateral movement isn't an anomaly inside one machine. It exists only in the space between logs.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Worse, &lt;strong&gt;the identifier you're chasing changes hands.&lt;/strong&gt; A VPN session ID gets you in; an authentication ticket is issued behind it; that ticket opens an SMB session to a share. Three different strings, and there is no column that joins them. What joins them is a fact: &lt;strong&gt;at the same moment, from the same source, the next identifier came up.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So the test isn't a term — it's an &lt;strong&gt;order&lt;/strong&gt;. &lt;code&gt;VPN success → ticket issued → connection to another host → share enumeration&lt;/code&gt;. In that order, it's lateral movement. Out of order — share enumeration &lt;em&gt;before&lt;/em&gt; the ticket was issued — it's someone doing their job. The clock skew between servers that &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps07-timestamp-driven-triage-en/" rel="noopener noreferrer"&gt;part 7&lt;/a&gt; covered can flip that conclusion outright.&lt;/p&gt;

&lt;h3&gt;
  
  
  What general tools do, and where they stop
&lt;/h3&gt;

&lt;p&gt;You collect across files, then order by time.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-h&lt;/span&gt; &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s1"&gt;'S-1a9f3c'&lt;/span&gt; vpn.log auth.log sshd.log fileserver.log   &lt;span class="c"&gt;# the ID you know&lt;/span&gt;
&lt;span class="c"&gt;# merge the window across files into one timeline (field positions vary)&lt;/span&gt;
&lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="nt"&gt;-k1&lt;/span&gt;,2 &amp;lt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s1"&gt;'2026-08-2[0-6]'&lt;/span&gt; vpn.log&lt;span class="o"&gt;)&lt;/span&gt; &amp;lt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s1"&gt;'2026-08-2[0-6]'&lt;/span&gt; auth.log&lt;span class="o"&gt;)&lt;/span&gt; | less
&lt;span class="c"&gt;# harvest identifiers that came up inside that window&lt;/span&gt;
&lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'$1 &amp;gt;= "2026-08-24T02:10" &amp;amp;&amp;amp; $1 &amp;lt;= "2026-08-24T02:40"'&lt;/span&gt; auth.log | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="s1"&gt;'TGT=[A-Za-z0-9]*'&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The third command is the real work. &lt;strong&gt;Search the known ID → read the window → harvest a new ID → search that.&lt;/strong&gt; One round trip per hop in the chain.&lt;/p&gt;

&lt;p&gt;Three limits. First, &lt;strong&gt;every round trip re-reads the originals from the top.&lt;/strong&gt; Five hops means five passes, and the hypotheses that don't pan out add more. At tens of seconds each that's tolerable in isolation; an after-the-fact scope determination isn't finished until the chain is exhausted.&lt;/p&gt;

&lt;p&gt;Second, &lt;strong&gt;you can't watch four files at once.&lt;/strong&gt; &lt;code&gt;sort -m&lt;/code&gt; gives you something readable, but the moment you merge, you lose which line came from which file. What you actually wanted was four files open, moving through the same window in all of them.&lt;/p&gt;

&lt;p&gt;Third, &lt;strong&gt;&lt;code&gt;grep&lt;/code&gt; cannot express order.&lt;/strong&gt; &lt;code&gt;grep -E 'VPN|TGT|SMB'&lt;/code&gt; returns lines containing any of the three; it never looks at the arrangement. You can write a state machine in &lt;code&gt;awk&lt;/code&gt;, but on a trail where the identifier keeps changing, that means &lt;strong&gt;a throwaway script for every hunch&lt;/strong&gt; — which is not the pace this work runs at.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Ransomware, after the fact — the interesting part is just before
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Situation
&lt;/h3&gt;

&lt;p&gt;The encryption is done. Restore is running from backup. In parallel, a second assignment lands: &lt;strong&gt;"When did they get in? What did they take?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Production is isolated. What you have is a preserved copy of the file access and audit logs. Tens of gigabytes, most of it still &lt;code&gt;.gz&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why it happens
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The encryption phase is short, and the records that matter are in the quiet days before it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Encryption produces an enormous volume of writes, so on a timeline it's a dense black band. Only a small slice of the incident is blacked out. What you want to read is &lt;strong&gt;before that&lt;/strong&gt; — the exploration, the bulk reads, the archive being assembled, the outbound transfer. That part looks almost exactly like a normal week.&lt;/p&gt;

&lt;p&gt;And after-the-fact work carries two constraints that outage triage doesn't. One is &lt;strong&gt;preservation&lt;/strong&gt;: no operation that writes to or rewrites the original. The other is &lt;strong&gt;where the data may live&lt;/strong&gt;. Uploading logs from a compromised system to an external analysis service is often off the table for contractual or policy reasons. &lt;strong&gt;Running entirely on the local machine is frequently a requirement&lt;/strong&gt;, not a preference.&lt;/p&gt;

&lt;h3&gt;
  
  
  What general tools do, and where they stop
&lt;/h3&gt;

&lt;p&gt;Search without unpacking to disk.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;zgrep &lt;span class="nt"&gt;-h&lt;/span&gt; &lt;span class="s1"&gt;'READ\|COPY'&lt;/span&gt; audit-2026-08-&lt;span class="k"&gt;*&lt;/span&gt;.log.gz | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-100&lt;/span&gt;
&lt;span class="c"&gt;# narrow to the 24 hours before encryption began&lt;/span&gt;
zcat audit-2026-08-24.log.gz | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'$1 &amp;gt;= "2026-08-24T00:00" &amp;amp;&amp;amp; $1 &amp;lt;= "2026-08-25T03:12"'&lt;/span&gt; | less
zcat audit-&lt;span class="k"&gt;*&lt;/span&gt;.gz | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'{print $5}'&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt; | &lt;span class="nb"&gt;uniq&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-rn&lt;/span&gt; | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-20&lt;/span&gt;   &lt;span class="c"&gt;# who touched the most&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;zgrep&lt;/code&gt; is the right instinct: nothing is written to disk, which is also the correct posture for preservation.&lt;/p&gt;

&lt;p&gt;Three limits. First, &lt;strong&gt;every change of mind decompresses again.&lt;/strong&gt; &lt;code&gt;zgrep&lt;/code&gt; runs the decompression per search, and after-the-fact investigation is nothing but changing your mind. The cost multiplies by the number of hypotheses (&lt;a href="https://uvp.y42u.net/en/blog/uwview-ps08-compressed-archive-search-en/" rel="noopener noreferrer"&gt;part 8&lt;/a&gt; went into this trade in detail).&lt;/p&gt;

&lt;p&gt;Second, &lt;strong&gt;you can't size "just before" in advance.&lt;/strong&gt; Whether 24 hours is enough or you need six days is something you learn by looking. &lt;code&gt;awk&lt;/code&gt;'s range wants the answer before you open, and every revision is another full pass.&lt;/p&gt;

&lt;p&gt;Third, &lt;strong&gt;the test is, again, an order.&lt;/strong&gt; &lt;code&gt;bulk read → archive created → outbound connection&lt;/code&gt; in that sequence is exfiltration. The same three terms in a scrambled order are usually a backup job.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. The SQLi that got past — what a WAF log doesn't hold
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Situation
&lt;/h3&gt;

&lt;p&gt;You have a WAF, so you have a record of what it blocked. What you actually need to examine is &lt;strong&gt;the requests it didn't block&lt;/strong&gt; — and those exist only in the raw access log of the web server.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why it happens
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The attacker's whole job is to not match your pattern.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;URL encoding, double encoding, comments splitting keywords apart (&lt;code&gt;UNION/**/SELECT&lt;/code&gt;), mixed case, injected control characters. The same payload has dozens of appearances in the log. So the regex is never written once — it's written and revised.&lt;/p&gt;

&lt;p&gt;And the moment you write it, the opposite problem arrives: &lt;strong&gt;false positives.&lt;/strong&gt; A site search with &lt;code&gt;or&lt;/code&gt; in the query string. An article title containing &lt;code&gt;union&lt;/code&gt;. &lt;code&gt;select&lt;/code&gt; is an ordinary English word. Most hits are harmless, and confirming that they're harmless means reading each one in context.&lt;/p&gt;

&lt;p&gt;There's also a distinction that matters legally: &lt;strong&gt;an attempt is not a success.&lt;/strong&gt; Whether it worked shows in &lt;strong&gt;another field on the same line&lt;/strong&gt; (status code, response size) and in &lt;strong&gt;the lines that follow&lt;/strong&gt; — an unnaturally regular burst of requests from the same address.&lt;/p&gt;

&lt;h3&gt;
  
  
  What general tools do, and where they stop
&lt;/h3&gt;

&lt;p&gt;Cast wide, then narrow.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-Ei&lt;/span&gt; &lt;span class="s2"&gt;"union[[:space:]/*]+select|'[[:space:]]*or[[:space:]]*'1'?=|sleep&lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="s2"&gt;|benchmark&lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; access.log | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-50&lt;/span&gt;
&lt;span class="c"&gt;# decode first, twice, to catch double encoding&lt;/span&gt;
perl &lt;span class="nt"&gt;-pe&lt;/span&gt; &lt;span class="s1"&gt;'s/%([0-9A-Fa-f]{2})/chr(hex($1))/ge'&lt;/span&gt; access.log | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-Ei&lt;/span&gt; &lt;span class="s1"&gt;'union.*select'&lt;/span&gt; | &lt;span class="nb"&gt;head&lt;/span&gt;
&lt;span class="c"&gt;# did that address get a 200 back?&lt;/span&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s1"&gt;'203.0.113.77'&lt;/span&gt; access.log | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'{print $9, $10}'&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt; | &lt;span class="nb"&gt;uniq&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The decode step works. Multiple encoding layers give way to simply running the substitution more than once.&lt;/p&gt;

&lt;p&gt;Three limits. First, &lt;strong&gt;once decoded, it isn't the original any more.&lt;/strong&gt; What flows down the pipe is transformed text with no line numbers attached to it. When it's time to quote a specific request in a report, you go looking for where it actually was — the same problem &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps09-read-raw-structured-data-en/" rel="noopener noreferrer"&gt;part 9&lt;/a&gt; described as "the moment you transform it, it stops being the original."&lt;/p&gt;

&lt;p&gt;Second, &lt;strong&gt;tuning a regex means re-reading everything, repeatedly.&lt;/strong&gt; Cutting false positives is ten or twenty rounds of adding and removing conditions, each one a full pass over gigabytes. And &lt;strong&gt;the count at each stage isn't kept&lt;/strong&gt;, so you can't tell which condition did the work. A visible drop — 94,979 → 184 → 54 — answers that instantly, but a pipeline throws its intermediate state away.&lt;/p&gt;

&lt;p&gt;Third, &lt;strong&gt;"did it succeed" can't be written as one &lt;code&gt;grep&lt;/code&gt;.&lt;/strong&gt; The status code is on the same line; the follow-on behaviour is on other lines. No single pipeline sees both, so you end up shuttling between the hit list and the scene.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Inventorying personal data — one person is not one key
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Situation
&lt;/h3&gt;

&lt;p&gt;An erasure request arrives. Or there's a suspected breach and you need to identify who was affected. The ask is: &lt;strong&gt;"produce every trace of this person."&lt;/strong&gt; There's a deadline.&lt;/p&gt;

&lt;p&gt;The logs span years, tens of gigabytes, several systems.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why it happens
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;In a log, one human being appears as several unrelated strings.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A member ID, an email address, the email address they used before they changed it, a phone number, a session ID, whatever IP they happened to hold that day. Search any one of them and the lines written with the others stay hidden. And the awkward part: &lt;strong&gt;the keys grow as you work.&lt;/strong&gt; A line found by email contains a session ID you didn't know about; searching that turns up another identifier.&lt;/p&gt;

&lt;p&gt;The nature of the job matches the full inspection in &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps13-huge-data-inspection-en/" rel="noopener noreferrer"&gt;part 13&lt;/a&gt;: &lt;strong&gt;to say "nothing," you have to go through all of it.&lt;/strong&gt; Answering "we found no records" requires being able to state which range you checked under which conditions.&lt;/p&gt;

&lt;p&gt;And one more constraint: the subject matter &lt;em&gt;is&lt;/em&gt; personal data. &lt;strong&gt;Uploading it somewhere to search it is largely off the table.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What general tools do, and where they stop
&lt;/h3&gt;

&lt;p&gt;Build a key list and apply it in one pass.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-h&lt;/span&gt; &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; keys.txt access.log auth.log app.log | &lt;span class="nb"&gt;tee &lt;/span&gt;hits.txt | &lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt;
&lt;span class="c"&gt;# harvest identifiers you didn't know about yet&lt;/span&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'sid=[A-Za-z0-9]{16}|[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+'&lt;/span&gt; hits.txt | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt;
&lt;span class="c"&gt;# keep filename and line number, for the report&lt;/span&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; keys.txt &lt;span class="k"&gt;*&lt;/span&gt;.log &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; evidence.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;grep -F -f&lt;/code&gt; is the workhorse here: dozens of keys, matched as fixed strings, in one pass. And &lt;code&gt;-n -H&lt;/code&gt; in the third command is basic hygiene for anyone who has to write the report.&lt;/p&gt;

&lt;p&gt;Two limits. First, &lt;strong&gt;every new key restarts everything.&lt;/strong&gt; One identifier discovered means appending to &lt;code&gt;keys.txt&lt;/code&gt; and running the whole corpus again — and identifier chains tend to branch rather than terminate.&lt;/p&gt;

&lt;p&gt;Second, &lt;strong&gt;what you looked at doesn't persist.&lt;/strong&gt; Shell history dies with the terminal. Assembling "which conditions, which range" the day before the deadline, from memory, is how people end up redoing the whole thing. &lt;strong&gt;On this job the search conditions are part of the deliverable&lt;/strong&gt;, and general-purpose tools have nowhere to keep them.&lt;/p&gt;




&lt;h2&gt;
  
  
  What all four had in common
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;View&lt;/th&gt;
&lt;th&gt;What you're looking for&lt;/th&gt;
&lt;th&gt;Constraint&lt;/th&gt;
&lt;th&gt;Where general tools stop&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Lateral movement&lt;/td&gt;
&lt;td&gt;A chain of identifiers that changes hands&lt;/td&gt;
&lt;td&gt;Follow four files at once&lt;/td&gt;
&lt;td&gt;Every round trip re-reads; order can't be expressed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ransomware, after the fact&lt;/td&gt;
&lt;td&gt;The window just before encryption&lt;/td&gt;
&lt;td&gt;Preservation; stay local&lt;/td&gt;
&lt;td&gt;Re-decompresses per query; the window can't be sized up front&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SQLi that got past&lt;/td&gt;
&lt;td&gt;Attempts, and whether any succeeded&lt;/td&gt;
&lt;td&gt;Citations need original coordinates&lt;/td&gt;
&lt;td&gt;Decoding drops the coordinates; per-stage counts vanish&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Personal data inventory&lt;/td&gt;
&lt;td&gt;Several keys pointing at one person&lt;/td&gt;
&lt;td&gt;Can't leave the machine; deadline&lt;/td&gt;
&lt;td&gt;Every new key restarts; conditions aren't kept&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The second column has nothing in common. Neither does the third. The fourth lines up anyway, because &lt;strong&gt;all four are asking for the same three operations.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Search by order.&lt;/strong&gt; &lt;code&gt;VPN success → ticket → another host&lt;/code&gt;; &lt;code&gt;bulk read → archive → outbound&lt;/code&gt;. The trace of an attack is in the arrangement of terms, not the set of them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stack the narrowing and see the count at each stage.&lt;/strong&gt; Neither regex tuning nor key expansion finishes in one pass. When the drop is visible, the condition that did the work is obvious.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep the original's coordinates, and stay on the local machine.&lt;/strong&gt; A citation needs a filename and a line number; the data can't leave. Work on the original, not on a transformed copy.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All three are things anyone does without thinking on a small file. At tens of gigabytes, each round trip demands a full re-read, decoding erases the coordinates, and the conditions die with the terminal. &lt;strong&gt;The operations aren't hard; size is what stops them being worth their cost.&lt;/strong&gt; This series keeps arriving at that sentence — after the fact, it just comes with a deadline and a higher price for being wrong attached.&lt;/p&gt;

&lt;p&gt;Back to those eleven minutes. Any tool will find them. What no tool hands you is the six days before.&lt;/p&gt;




&lt;h2&gt;
  
  
  The tool I use
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://uvp.y42u.net/en/" rel="noopener noreferrer"&gt;UwView&lt;/a&gt; (free), which I develop, displays, scrolls, and searches huge text &lt;strong&gt;from the moment it opens&lt;/strong&gt;. The index is built in the background and line numbers appear when it completes. Nothing is split and nothing is extracted, so a preserved log stays one file, unmodified. &lt;strong&gt;It never writes to the original&lt;/strong&gt; — the first principle of forensics. Highlights add colour without removing lines, which is exactly section 3's "set the harmless hits aside without deleting them." &lt;strong&gt;All processing happens on your machine; the file is never transmitted anywhere.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The three operations above are what &lt;a href="https://uvp.y42u.net/en/pro-en/" rel="noopener noreferrer"&gt;UwView Pro&lt;/a&gt; adds.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Sequence search&lt;/strong&gt;: match only where &lt;code&gt;w1 → w2 → w3&lt;/code&gt; appear &lt;strong&gt;in that order&lt;/strong&gt;. Section 1's &lt;code&gt;VPN success → ticket issued → another host&lt;/code&gt; and section 2's &lt;code&gt;bulk read → archive → outbound&lt;/code&gt; become the query itself. A right-click "history" shows &lt;strong&gt;the path that actually matched&lt;/strong&gt; — which line held which term — and you can jump to any of them. One honest note: each stage searches the body starting from the previous stage's position, so &lt;strong&gt;it takes about as long as a full-text search&lt;/strong&gt;. &lt;a href="https://uvp.y42u.net/en/blog/uvp-sequence-search-en/" rel="noopener noreferrer"&gt;Implementation write-up&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Drill-down search&lt;/strong&gt;: narrow a result by another term, then another. Tabs show &lt;code&gt;term (count)&lt;/code&gt;, so section 3's regex tuning and section 4's key expansion run &lt;strong&gt;with the count visible at every stage&lt;/strong&gt;. Stages after the first search only the previous stage's window, so there's no wait, and backing out means clicking the earlier tab. &lt;a href="https://uvp.y42u.net/en/blog/uvp-drilldown-search-en/" rel="noopener noreferrer"&gt;Implementation write-up&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In practice both look like this. The tabs across the top read &lt;code&gt;Tokyo (10,967) → Ariake (7) → 有明 (4)&lt;/code&gt; — &lt;strong&gt;with counts&lt;/strong&gt; — which is section 3's "how much did each stage remove." The right-click &lt;strong&gt;History (drill-down path)&lt;/strong&gt; reads &lt;code&gt;Line 320,574,475: Tokyo → Line 320,574,475: Ariake&lt;/code&gt; — &lt;strong&gt;term and original line number, in order&lt;/strong&gt; — which is section 1's "which term caught this, and where did it come from." (That example is 892 million lines of OSM data; logs behave the same way. Screenshots are in the &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps14-attack-traces-raw-logs-en/" rel="noopener noreferrer"&gt;original article&lt;/a&gt;.)&lt;/p&gt;

&lt;p&gt;Set against a grep pipe, what survives is different.&lt;/p&gt;

&lt;p&gt;| | &lt;code&gt;grep A file \| grep B \| grep C&lt;/code&gt; | Drill-down + history |&lt;br&gt;
|---|---|---|&lt;br&gt;
| Per-stage counts | Gone — only the final result | Tabs carry &lt;code&gt;term (count)&lt;/code&gt; |&lt;br&gt;
| Original line numbers | Lost from stage 2 on (&lt;code&gt;grep -n&lt;/code&gt; counts within the block that reached it) | Still 320,574,475 — and you can jump there |&lt;br&gt;
| Which term caught the row | Unknowable | Listed in History, term and line number, in order |&lt;br&gt;
| Changing a condition | Rewrite the pipe, re-read the file from the top | Click the tab of the stage you want back |&lt;br&gt;
| Stopping for the day | Close the terminal and the conditions and the path go with it | Tabs and history persist; resume there tomorrow |&lt;/p&gt;

&lt;p&gt;Section 4's "what you looked at doesn't persist" is rows 1 and 5 of that table. On work where &lt;strong&gt;the search conditions are part of the deliverable&lt;/strong&gt; — an erasure-request answer, an after-the-fact report — that difference lands directly on the number of days and on how hard the report is to write.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;±N is independent per stage&lt;/strong&gt;: ±1 while narrowing, ±10 on the stage where you actually read — and changing N doesn't re-read the original. That's section 2's "you can't size the window up front."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It saves the index and the compression&lt;/strong&gt;: from the second open onward the file opens &lt;strong&gt;instantly, with line numbers&lt;/strong&gt; (0.02–0.07 s measured on a 47.73 GB text file; one setup, results vary). Compressed-cache search and ~1/9 storage come with it, so retention-mandated logs cost less disk (all OS, one-time or monthly). On section 2's kind of work — the same preserved tens of gigabytes, reopened for days — that difference shows up in the schedule.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One honest set of limits: UwView is &lt;strong&gt;a viewer. It is not a SIEM and not a forensic suite.&lt;/strong&gt; No automated cross-log correlation, no threat-intel matching, no alerting, no report generation. It will not write section 3's regex for you, it does not normalise the timestamps in section 1, and it does not join files automatically — putting them side by side is still your eyes doing the work. Disk images and memory dumps are out of scope entirely. What it covers is the step before all of that: &lt;strong&gt;looking at a preserved raw log, as it is, locally, with your own eyes.&lt;/strong&gt; If your scale calls for correlation and alerting, that's a different product's job.&lt;/p&gt;

&lt;p&gt;For completeness: non-destructive diff editing (Edit Upgrade) exists as a separate licence, but every one of the four views above is &lt;strong&gt;read-only work&lt;/strong&gt; — what you need here is the View side.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Part 1: four go-to tools that sink under huge files: &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps01-huge-file-tool-limits-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uwview-ps01-huge-file-tool-limits-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 2: four ways to trace causality in logs: &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps02-log-causality-tracing-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uwview-ps02-log-causality-tracing-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 4: deciding between delete, keep, and compress: &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps04-log-retention-decision-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uwview-ps04-log-retention-decision-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 6: picking traces out of tens of millions of auth-log lines: &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps06-intrusion-triage-auth-logs-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uwview-ps06-intrusion-triage-auth-logs-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 7: four ways to read a timestamp as a weapon: &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps07-timestamp-driven-triage-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uwview-ps07-timestamp-driven-triage-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 8: keeping logs compressed and still searchable: &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps08-compressed-archive-search-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uwview-ps08-compressed-archive-search-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 9: reading structured data raw: &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps09-read-raw-structured-data-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uwview-ps09-read-raw-structured-data-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 13: four techniques for inspecting huge data: &lt;a href="https://uvp.y42u.net/en/blog/uwview-ps13-huge-data-inspection-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uwview-ps13-huge-data-inspection-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Sequence search — finding only what appears in that order: &lt;a href="https://uvp.y42u.net/en/blog/uvp-sequence-search-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uvp-sequence-search-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Drill-down search — narrowing a result by another term: &lt;a href="https://uvp.y42u.net/en/blog/uvp-drilldown-search-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uvp-drilldown-search-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Chasing 5xx in an nginx access log: &lt;a href="https://uvp.y42u.net/en/blog/uwview-access-log-5xx-workflow-en/" rel="noopener noreferrer"&gt;https://uvp.y42u.net/en/blog/uwview-access-log-5xx-workflow-en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Source code (GitHub): &lt;a href="https://github.com/amru195704/UwView" rel="noopener noreferrer"&gt;https://github.com/amru195704/UwView&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;From the developer&lt;/strong&gt;: a full list of my apps, Kindle books, and open-source work is on &lt;a href="https://github.com/amru195704" rel="noopener noreferrer"&gt;GitHub: amru195704&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A note&lt;/strong&gt;&lt;br&gt;
This article is provided for reference and makes no guarantee of accuracy or completeness. Actual incident response, evidence preservation, and personal-data handling must follow your organisation's policy and applicable law and regulatory guidance. Times, counts, and identifiers shown here are illustrative and do not describe any real incident. All IP addresses are documentation-range examples. Log formats, field positions, and timestamp conventions vary widely by OS, middleware, product, and configuration. Command examples may need adjustment for your environment (GNU/BSD differences, shell, &lt;code&gt;awk&lt;/code&gt; / &lt;code&gt;grep&lt;/code&gt; / &lt;code&gt;zgrep&lt;/code&gt; implementations). Measured figures come from one specific setup and are not a guarantee of the same result. If you spot an error, a comment is welcome and I'll check and correct it.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>security</category>
      <category>logging</category>
      <category>devops</category>
      <category>sre</category>
    </item>
  </channel>
</rss>
