Azure Functions can react to changes in Cosmos DB, but the magic isn’t in the Function itself; it’s in a separate, often overlooked, Azure service called the Cosmos DB Change Feed Processor.

Let’s see this in action. Imagine you have a products collection in Cosmos DB and you want to update a search index whenever a product is added or modified.

Here’s a simplified products document:

{
    "id": "prod-123",
    "name": "Wireless Mouse",
    "price": 25.99,
    "category": "Electronics",
    "lastUpdated": "2023-10-27T10:00:00Z"
}

When this document is inserted or updated, the Cosmos DB Change Feed Processor picks up the change and passes it to an Azure Function.

The Azure Function, triggered by the Cosmos DB change feed, would look something like this (in C#):

using Microsoft.Azure.WebJobs;
using Microsoft.Azure.Cosmos;
using System.Collections.Generic;
using System.Threading.Tasks;

public static class CosmosTriggerFunction
{
    [FunctionName("CosmosDbChangeFeedTrigger")]
    public static async Task Run(
        [CosmosDBTrigger(
            databaseName: "mydatabase",
            collectionName: "products",
            ConnectionStringSetting = "CosmosDBConnection",
            LeaseCollectionName = "leases",
            CreateLeaseCollectionIfNotExists = true)] IReadOnlyList<Document> input)
    {
        if (input != null && input.Count > 0)
        {
            // Process changes
            foreach (var doc in input)
            {
                // For example, send to a search index
                await IndexDocumentAsync(doc);
            }
        }
    }

    private static Task IndexDocumentAsync(Document doc)
    {
        // Placeholder for actual indexing logic
        System.Diagnostics.Debug.WriteLine($"Processing change for document ID: {doc.Id}");
        return Task.CompletedTask;
    }
}

The CosmosDBTrigger attribute is the key here. It tells the Azure Functions host to monitor the products collection. When changes occur, the host invokes this function, passing a batch of changed documents.

The underlying mechanism is the Cosmos DB Change Feed. Every Cosmos DB container has a change feed that logs all creations and updates to documents in the order they occur. The Change Feed Processor library (which the CosmosDBTrigger attribute uses under the hood) is designed to read this feed reliably. It maintains state – specifically, which documents have been processed – in a separate "lease" container. This ensures that if your function app restarts, it resumes processing from where it left off, preventing data loss or duplicate processing.

The LeaseCollectionName and CreateLeaseCollectionIfNotExists = true are crucial. The leases container is where the processor stores its progress. Without it, the processor wouldn’t know what has already been processed. The ConnectionStringSetting points to your Cosmos DB account.

You control the behavior through the trigger configuration:

  • databaseName: The name of your Cosmos DB database.
  • collectionName: The name of the container whose changes you want to monitor.
  • ConnectionStringSetting: The name of the app setting that holds your Cosmos DB connection string.
  • LeaseCollectionName: The name of the container used for storing leases. This container should be provisioned in the same Cosmos DB account.
  • CreateLeaseCollectionIfNotExists: If true, the lease container will be created automatically if it doesn’t exist.
  • FeedPollDelay: (Optional) The delay in milliseconds between polling the change feed for new changes. Default is 5000ms (5 seconds).
  • MaxItemsPerInvocation: (Optional) The maximum number of documents to process in a single function invocation. Default is 100.

The most surprising thing about this setup is how the leases container operates. It’s not just a simple log of progress; it uses distributed locking to ensure that only one instance of your function app (or one partition of a scaled-out function app) is processing a given segment of the change feed at any time. This partitioning and locking are what make the change feed robust and scalable.

The change feed itself has a retention period, typically 24 hours, though this can be extended. If your function app is down for longer than the retention period, you might miss changes unless you implement a strategy to re-read historical data or acknowledge the missed processing window. The change feed processor handles retries and error management, but it’s essential to have robust error handling within your function code to deal with issues like transient network problems or malformed documents.

The next concept you’ll likely encounter is handling different types of operations (create, update, delete) within the change feed, and how to manage the evolution of your data schema over time as your documents change.

Want structured learning?

Take the full Azure-functions course →