<?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>Forem: Gayathri Jetti</title>
    <description>The latest articles on Forem by Gayathri Jetti (@gayathriabcde).</description>
    <link>https://forem.com/gayathriabcde</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3904661%2F3ce4f423-ff5c-4ca3-89dc-0e65b7b78bdc.jpg</url>
      <title>Forem: Gayathri Jetti</title>
      <link>https://forem.com/gayathriabcde</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/gayathriabcde"/>
    <language>en</language>
    <item>
      <title>Video Search with MongoDB</title>
      <dc:creator>Gayathri Jetti</dc:creator>
      <pubDate>Wed, 06 May 2026 07:17:48 +0000</pubDate>
      <link>https://forem.com/gayathriabcde/video-search-with-mongodb-5a1p</link>
      <guid>https://forem.com/gayathriabcde/video-search-with-mongodb-5a1p</guid>
      <description>&lt;p&gt;By: &lt;a class="mentioned-user" href="https://dev.to/gayathriabcde"&gt;@gayathriabcde&lt;/a&gt;,    &lt;a class="mentioned-user" href="https://dev.to/gotlur_parnika_27c3841815"&gt;@gotlur_parnika_27c3841815&lt;/a&gt; , &lt;a class="mentioned-user" href="https://dev.to/jhv_07"&gt;@jhv_07&lt;/a&gt; , and &lt;a class="mentioned-user" href="https://dev.to/gnana_1905"&gt;@gnana_1905&lt;/a&gt; , under the guidance of &lt;a class="mentioned-user" href="https://dev.to/chanda_rajkumar"&gt;@chanda_rajkumar&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;Building a Retrieval-Augmented Generation (RAG) application generally involves the similar steps of: chunking text, embedding it, storing the embeddings in a vector database, and searching based on similarity. This works perfectly for text-based documents, but we can't apply these steps as-is when trying to build a RAG system for videos, specially procedural videos, where each frame in the video is connected to another. &lt;/p&gt;

&lt;p&gt;While trying to build a RAG system specifically for the purpose of making procedural videos very easy to search through, I realized that treating it as a 'bag of chunks' is extremely inefficient. The large amount of data that each frame in the video generates was also another problem we faced. Now, I'll explain how I attempted to use MongoDB's $graphLookup to solve this problem. &lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;The Prerequisite Problem:&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Videos aren't just isolated concepts grouped together, the frames are chronologically sequenced, and the actions performed in previous frames tend to have a action-result relation with consequential frames, based on what is being discussed in the video. For example, if an AI search engine is asked a question like "How do I tighten the 10mm bolt?", a standard vector search will just give the exact, most semantically similar frame, exactly when the bolt is being tightened. But, that doesn't answer the user's question entirely, as the user isn't made aware of the things they have to do before turning the bolt. &lt;/p&gt;

&lt;p&gt;This is where prerequisite steps are necessary. They can be used to guide the user through the entire process to achieve the result and answer the query in a most thorough and educational way. &lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;2. Vector DB vs Graph DB:&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;To do this, the worker needed to analyze the videos and store them as a chronological chain of nodes, a graph instead of a flat list. There are two things we need to do this effectively: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;A Vector Database&lt;/strong&gt; that would perform semantic search on both the audio transcripts and visual descriptions which would quickly find the exact moment the user requires.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;A database that would help traverse backward&lt;/strong&gt; through the links between the frames and give the prerequisite steps. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Traversing through nodes would be easier if I used a Graph Database, but only if we were linking nodes that are far from each other in the video. Since we are primarily focusing on procedural videos right now, and since they're essentially linked lists where nodes point to the nodes right before them, I felt a dedicated graph database, used for highly interconnected data, would be unnecessary. And maintaining two databases, one for semantic search and the other for storing with relationships, and syncing both their states, is extremely difficult. &lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Node Schema:&lt;/strong&gt;
&lt;/h3&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff6sbv5jr04b5t9kkhuc2.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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff6sbv5jr04b5t9kkhuc2.png" alt="Node Schema Visualization" width="800" height="818"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;3. The Solution: MongoDB Atlas&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Since MongoDB Atlas is a document database that natively supports both Vector Search and graph traversal, it allowed me to use a unified approach by modelling video moments as documents with two main components:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;A vector_int8 field&lt;/strong&gt; that stores highly compressed and quantized embeddings. This keeps the storage footprint small while supporting fast semantic search.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;A graph_edges object&lt;/strong&gt; that contains simple &lt;code&gt;prev_node_id&lt;/code&gt; and &lt;code&gt;next_node_id&lt;/code&gt; pointers. These act as the "links" that turn our flat list of frames into a traversable timeline.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;4. The Single-Pipeline logic:&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The main optimization happens in the retrieval API. Instead of fetching a target vector, extracting its ID, and then executing another query to get the prerequisite steps, MongoDB handles everything in a single Aggregation Pipeline.&lt;/p&gt;

&lt;p&gt;Here is how the search is executed:&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Stage 1: The Vector Search&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;First, we use the &lt;code&gt;$vectorSearch&lt;/code&gt; stage to find the "Target Node"—the specific moment that best matches what the user is asking.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$vectorSearch&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;index&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vector_index&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;path&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vector_int8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;queryVector&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;user_query_int8&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;numCandidates&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;limit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;Stage 2: Traversal&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Immediately after finding the target node, we send it directly into &lt;code&gt;$graphLookup&lt;/code&gt;. This stage uses the &lt;code&gt;prev_node_id&lt;/code&gt; field to automatically walk backward in time, fetching the exact prerequisite steps the user needs to see.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$graphLookup&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;from&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;video_nodes&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;startWith&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$graph_edges.prev_node_id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;connectFromField&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;graph_edges.prev_node_id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;connectToField&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;_id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;as&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;prerequisite_steps&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;maxDepth&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;depthField&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;steps_backward&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fytm68y21mn2r1yr3jk4f.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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fytm68y21mn2r1yr3jk4f.png" alt=" " width="800" height="820"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Why This Architecture Wins:&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Single Query Execution:&lt;/strong&gt; MongoDB returns a structured document containing the target answer and its chronological context that is ready to be sent to the frontend, with a single query from the backend.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Configurable Depth:&lt;/strong&gt; We can very easily change the number of steps the user would require by simply modifying the value of the &lt;code&gt;maxDepth&lt;/code&gt; field.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Simple Architecture:&lt;/strong&gt; We get the benefits of a graph structure without having to learn specialized query languages or maintain two separate databases.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;5. Conclusion:&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;To summarize, the regular RAG approach of treating data as a "bag of chunks" fails for procedural videos because it ignores the dependencies between frames. To solve this without creating further problems, I used MongoDB Atlas to create a unified architecture that gives meaningful results efficiently using &lt;code&gt;$graphLookup&lt;/code&gt; and a vector search index. &lt;/p&gt;

&lt;p&gt;If you are building an AI search tool for any kind of chronological or step-by-step data, maintaining that relational link between your nodes is most important. This unified approach keeps the backend simple, avoids unnecessary operational overhead, and ultimately gives the user a much more thorough and educational answer.&lt;/p&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fodvf1hc4ed2s0xy7t25x.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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fodvf1hc4ed2s0xy7t25x.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;A limited local implementation where the video gets processed to understand the insertion of the frame-nodes in MongoDB&lt;/em&gt;&lt;/p&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxh8sblqyqmtvaorfc4ai.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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxh8sblqyqmtvaorfc4ai.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;After the video is processed, searching something will take us to the exact second the answer to the query is being discussed in the video&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>database</category>
      <category>mongodb</category>
      <category>rag</category>
    </item>
  </channel>
</rss>
