<?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: The_CrackedAhki</title>
    <description>The latest articles on DEV Community by The_CrackedAhki (@bhadmusx).</description>
    <link>https://dev.to/bhadmusx</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%2F4094098%2Fadc3295e-c43f-4f66-94bd-de86ed010a8f.jpg</url>
      <title>DEV Community: The_CrackedAhki</title>
      <link>https://dev.to/bhadmusx</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/bhadmusx"/>
    <language>en</language>
    <item>
      <title>An audit log service that records who changed what and when. Stack: Node.js, Express, MongoDB.</title>
      <dc:creator>The_CrackedAhki</dc:creator>
      <pubDate>Tue, 10 Nov 2026 06:22:34 +0000</pubDate>
      <link>https://dev.to/bhadmusx/an-audit-log-service-that-records-who-changed-what-and-when-stack-nodejs-express-mongodb-1g76</link>
      <guid>https://dev.to/bhadmusx/an-audit-log-service-that-records-who-changed-what-and-when-stack-nodejs-express-mongodb-1g76</guid>
      <description>&lt;h2&gt;
  
  
  How indexes cut my MongoDB query from 100,001 documents to 100
&lt;/h2&gt;

&lt;h2&gt;
  
  
  What I'm building
&lt;/h2&gt;

&lt;p&gt;An Audit-Log service that helps to identify who changed what and when, Stack: Node js, Express, mongodb&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;p&gt;I seeded 100,000 fake events (actors, actions, resources, timestamps spread over 90 days). The query my API runs most:&lt;br&gt;
"Give me the 100 newest events by &lt;code&gt;user_1&lt;/code&gt; between two dates."&lt;br&gt;
The collection had one extra test event I created through the API, so  scans show 100, 001&lt;/p&gt;

&lt;h2&gt;
  
  
  Before: no index
&lt;/h2&gt;

&lt;p&gt;Using Compass's Explain Plan (which runs &lt;code&gt;explain("executionStats")&lt;/code&gt;):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Docs examined&lt;/th&gt;
&lt;th&gt;Returned&lt;/th&gt;
&lt;th&gt;In-memory sort&lt;/th&gt;
&lt;th&gt;Stage&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;100,001&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;COLLSCAN&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;MongoDB opened every document to return 100.&lt;/p&gt;

&lt;h2&gt;
  
  
  After: one compound index
&lt;/h2&gt;

&lt;p&gt;Index: &lt;code&gt;{ actor: 1, timestamp: -1 }&lt;/code&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Docs examined&lt;/th&gt;
&lt;th&gt;Returned&lt;/th&gt;
&lt;th&gt;In-memory sort&lt;/th&gt;
&lt;th&gt;Stage&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;IXSCAN&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Why field order matters (ESR: Equality, Sort, Range)
&lt;/h2&gt;

&lt;p&gt;In a compound index, order fields like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Equality&lt;/strong&gt;: fields matched with an exact value&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sort&lt;/strong&gt;: the field the results are ordered by&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Range&lt;/strong&gt;: fields filtered with &lt;code&gt;$gte&lt;/code&gt;, &lt;code&gt;$lte&lt;/code&gt;, and so on&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When a field is both the sort and the range, as &lt;code&gt;timestamp&lt;/code&gt; is here, it counts as one step and goes last.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt; my query filters &lt;code&gt;actor = "user_1"&lt;/code&gt;, limits &lt;code&gt;timestamp&lt;/code&gt; to a date range, and sorts by &lt;code&gt;timestamp&lt;/code&gt; descending. So the index is &lt;code&gt;{ actor: 1, timestamp: -1 }&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;actor&lt;/code&gt; goes first, so Mongo jumps straight to that actor's section of the index.&lt;/li&gt;
&lt;li&gt;Inside that section the entries are already ordered by &lt;code&gt;timestamp&lt;/code&gt;, so no in-memory sort is needed.&lt;/li&gt;
&lt;li&gt;With &lt;code&gt;limit(100)&lt;/code&gt;, Mongo reads 100 entries and stops. Result: 100 documents examined, down from 100,001.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The problem I ran into
&lt;/h2&gt;

&lt;p&gt;While testing, I hit a problem with the time-range query. My compound index was &lt;code&gt;{ actor: 1, timestamp: -1 }&lt;/code&gt;, and a query on &lt;code&gt;timestamp&lt;/code&gt; alone couldn’t use it, because an index only helps queries that use its first field. I had two options: change the query to always include &lt;code&gt;actor&lt;/code&gt;, or create a separate index on &lt;code&gt;timestamp&lt;/code&gt;. I chose the separate index because the audit log needs “everything between these dates” queries across all actors.&lt;/p&gt;

&lt;h2&gt;
  
  
  Per-query results
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Query&lt;/th&gt;
&lt;th&gt;Index used&lt;/th&gt;
&lt;th&gt;Examined (no index)&lt;/th&gt;
&lt;th&gt;Examined (indexed)&lt;/th&gt;
&lt;th&gt;Returned&lt;/th&gt;
&lt;th&gt;Stage (indexed)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;actor&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;actor_1_timestamp_-1&lt;/code&gt; (prefix)&lt;/td&gt;
&lt;td&gt;100,001&lt;/td&gt;
&lt;td&gt;27,188&lt;/td&gt;
&lt;td&gt;27,188&lt;/td&gt;
&lt;td&gt;IXSCAN&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;action&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;action_1_timestamp_-1&lt;/code&gt; (prefix)&lt;/td&gt;
&lt;td&gt;100,001&lt;/td&gt;
&lt;td&gt;33,124&lt;/td&gt;
&lt;td&gt;33,124&lt;/td&gt;
&lt;td&gt;IXSCAN&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;timestamp&lt;/code&gt; range&lt;/td&gt;
&lt;td&gt;&lt;code&gt;timestamp_-1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;100,001&lt;/td&gt;
&lt;td&gt;17,765&lt;/td&gt;
&lt;td&gt;17,765&lt;/td&gt;
&lt;td&gt;IXSCAN&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;actor&lt;/code&gt; + range&lt;/td&gt;
&lt;td&gt;&lt;code&gt;actor_1_timestamp_-1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;100,001&lt;/td&gt;
&lt;td&gt;4,772&lt;/td&gt;
&lt;td&gt;4,772&lt;/td&gt;
&lt;td&gt;IXSCAN&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;actor&lt;/code&gt; + range + sort + limit 100&lt;/td&gt;
&lt;td&gt;&lt;code&gt;actor_1_timestamp_-1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;100,001&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;IXSCAN&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;What is "prefix": Prefix means that  the index can serve for queries that match the first field so for index of &lt;code&gt;actor_1_timestamp_-1&lt;/code&gt; can serve  a query of &lt;code&gt;actor&lt;/code&gt; because of the first field&lt;/p&gt;

&lt;p&gt;What I noticed: for every indexed query, docs examined equals docs returned, so the&lt;br&gt;
index removes the wasted work and only the real work remains. The &lt;code&gt;actor&lt;/code&gt; and&lt;br&gt;
time-range rows still examine thousands of documents because they return thousands.&lt;br&gt;
Only the &lt;code&gt;limit&lt;/code&gt; brings the work down to 100.&lt;/p&gt;

&lt;p&gt;I left execution time out on purpose. The same query ran anywhere from 0 ms to over&lt;br&gt;
200 ms between runs on my local machine, so docs examined is the number I trust. I'll&lt;br&gt;
measure timing properly under load in the next post.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it costs
&lt;/h2&gt;

&lt;p&gt;Indexes make reads faster and writes slower, because every insert updates each one. For a write-heavy audit log that matters.&lt;/p&gt;

&lt;p&gt;Code: &lt;a href="https://github.com/BhadmusX/Audit-Log" rel="noopener noreferrer"&gt;https://github.com/BhadmusX/Audit-Log&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Next
&lt;/h2&gt;

&lt;p&gt;Cursor pagination, then load testing with k6 or autocannon, then batching and a queue.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>mongodb</category>
      <category>node</category>
      <category>beginners</category>
    </item>
    <item>
      <title>The new beginning: grinding like my life depends on it</title>
      <dc:creator>The_CrackedAhki</dc:creator>
      <pubDate>Tue, 25 Aug 2026 11:58:31 +0000</pubDate>
      <link>https://dev.to/bhadmusx/the-new-beginning-grinding-like-my-life-depends-on-it-1m3j</link>
      <guid>https://dev.to/bhadmusx/the-new-beginning-grinding-like-my-life-depends-on-it-1m3j</guid>
      <description>&lt;p&gt;This is my first post on this platform and I intend on using this platform to share my progress and everything I learnt along the way, the ups the downs the failure and the little wins.&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
