Azure AI Hub LogoAzure AI Hub

05 - Use the change feed to trigger embedding refresh

Use the change feed to trigger embedding refresh.

Use the change feed to trigger embedding refresh

This is one of the most critical concepts for taking an AI application from "it works on my machine" to "it works perfectly in production."

When a document's text changes, its old vector embedding becomes useless. If you don't update the embedding, your search results will be inaccurate. Here is how we fix that automatically, without writing complex polling scripts.

1. The Core Problem & The Solution

The Problem: An engineer edits a Troubleshooting Guide in your support portal. The text is new, but the vector numbers in the database are still mathematically pointing to the old text. The Solution: Cosmos DB has a built-in feature called the Change Feed. It is a persistent, ordered log of every Insert and Update in your database.

  • Why it's awesome: It's enabled by default. If your app goes offline for an hour, the Change Feed remembers everything. When you come back online, it hands you the exact list of documents that changed.

How to Consume Changes: Push vs. Pull

  • The Push Model (Recommended): The database automatically pushes changes to your code the moment they happen. This is usually built using Azure Functions. It's fully automated and handles scaling for you.
  • The Pull Model: Your code manually "polls" the database asking, "Anything new?" You'd use this only if you want to run a batch job once a night.

2. The Lease Container Concept

For an Azure Function to listen to the Change Feed, it requires a second, separate Cosmos DB container called a Lease Container.

Think of it as a coordinator. If you have 5 instances of your Azure Function running, the Lease container acts as a "bookmark" to ensure Instance A reads Partition 1, Instance B reads Partition 2, and no two instances process the exact same document at the exact same time. It also remembers where you left off if a server crashes (Checkpointing).

3. Saving Money: Selective Embedding Refresh

Calling the OpenAI API costs money and compute time. If a user updates the "status" of a document from Draft to Published, the actual meaning of the text didn't change. You shouldn't waste money regenerating the vector.

The Fix: We use a contentHash pattern. Create a SHA256 Hash of the text content and save it in the document. When a change happens, hash the new text. If the new hash equals the old hash, skip the OpenAI call!

The Optimized Document Structure

Here is exactly how you would structure the JSON document inside Azure Cosmos DB to support this Selective Embedding Refresh pattern:

{
  "id": "doc_user101_item456",
  "userId": "user101",
  "title": "Cosmos DB Architecture Guide",
  "content": "Azure Cosmos DB handles horizontal scaling via partition keys...",
  "status": "Published",
  "lastUpdated": "2026-09-17T23:50:00Z",
  
  "contentHash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
  
  "embedding": [
    0.00231,
    -0.01452,
    0.08912,
    -0.00567
  ]
}
  • status: Changing this value triggers an update in Cosmos DB, but your backend code will immediately see that the contentHash remains unchanged.
  • contentHash: A computed SHA-256 string generated only from the text properties that matter (title + content).
  • embedding: The heavy, expensive array of vector numbers generated by OpenAI.

4. The Complete Real-Time Architecture

When dealing with high volumes of updates, directly calling OpenAI from the Change Feed trigger can cause rate-limiting issues. A brilliant architectural pattern is to use the Change Feed to drop a "ticket" into an Azure Storage Queue, and have a separate background worker safely process that queue at a controlled speed.

Here is the exact step-by-step flow showing how Cosmos DB, the Queue, and OpenAI work together when a document is updated:

Walkthrough of the Data Transformation

  1. At the start (Cosmos DB): The user saves a document containing text content. { "id": "doc-123", "content": "Cosmos DB scales horizontally." }

  2. In the Queue: A temporary line item is created to act as a reminder. "Please generate embedding for doc-123"

  3. The Background Worker steps in: It reads the queue ticket, looks up "doc-123" in Cosmos DB, extracts the text string, and sends only that text string to OpenAI.

  4. Inside OpenAI: OpenAI's sole job is to act as a translator. The model processes the string and returns a vector array. [0.0123, -0.4567, 0.8912]

  5. Back to Cosmos DB: The worker updates the original document, safely packing the OpenAI numbers into it.

    {
      "id": "doc-123",
      "content": "Cosmos DB scales horizontally.",
      "embedding": [0.0123, -0.4567, 0.8912]
    }

5. Python Implementation Example

Here is a clean Python snippet using the standard hashlib library to demonstrate exactly how to generate and compare this contentHash before making an expensive API call.

import hashlib

def compute_content_hash(title, content):
    """Compute SHA-256 hash of the content used for embedding generation."""
    text_content = f"{title} {content}"
    return hashlib.sha256(text_content.encode('utf-8')).hexdigest()

def process_change(new_document, old_document):
    # 1. Calculate the hash of the incoming text
    new_hash = compute_content_hash(new_document.get('title', ''), new_document.get('content', ''))
    
    # 2. Check the Guard Condition
    old_hash = old_document.get('contentHash') if old_document else None
    
    if new_hash == old_hash:
        print("Hash matches! Skipping OpenAI call. Metadata-only update.")
        # Just update status/metadata in DB and keep existing embedding
        return
        
    print("Content changed. Calling OpenAI for new embedding...")
    # 3. Call OpenAI API here
    # new_embedding = call_openai(...)
    
    # 4. Save the new hash and embedding to the document
    new_document['contentHash'] = new_hash
    # new_document['embedding'] = new_embedding
    # container.upsert_item(new_document)

6. Error Handling & Idempotency

Idempotency means "if this code accidentally runs twice, it won't break anything."

  • Change Feeds can sometimes deliver the same change twice during failovers. Fortunately, generating an embedding from the same text twice just gives you the same math numbers, so it's perfectly safe.
  • Watch out for Deletes: The Change Feed might say "Doc 123 updated!" but by the time your worker reads the queue ticket, the user might have deleted Doc 123. You must write a try/except block to catch CosmosResourceNotFoundError so your code doesn't crash.

On this page