March 8th, 2024

Azure Cosmos DB design patterns – Part 8: Preallocation

Jay Gordon
Senior Program Manager

We’re thrilled to present the eighth edition of our blog series, focusing on design patterns in Azure Cosmos DB for NoSQL applications. Drawing from real-world customer experiences, our goal is to help you navigate the ins and outs of JSON-based NoSQL databases. In this chapter, we’ll dive deeper into common NoSQL patterns, particularly suitable for those new to this type of database. Additionally, we’ll highlight patterns specific to Azure Cosmos DB, demonstrating how to leverage its distinct capabilities, including preallocation, to address complex architectural challenges.

Azure Samples / cosmsos-db-design-patterns

These patterns, previously shared individually with customers, are now being made more widely accessible. We believe it’s the right moment to broadly publish these insights, enhancing their discoverability among users. Thus, we’ve created Azure Cosmos DB Design Patterns — a GitHub repository filled with an array of examples. These samples are meticulously curated to demonstrate the implementation of specific patterns, assisting you in addressing design challenges when utilizing Azure Cosmos DB in your projects.

To help share these, we have created a blog post series on each of them. Each post will focus on a specific design pattern with a corresponding sample application that is featured in this repository. We hope you enjoy it and find this series useful.

Here is a list of the previous posts in this series:

Azure Cosmos DB design pattern: Preallocation

Preallocation in Azure Cosmos DB for NoSQL is a design pattern that involves initially creating an empty structure, which is later populated with data. This approach is particularly helpful in terms of query and logic design simplicity compared to other methods. By pre-allocating, for example, reservation dates for a room in a hotel, the querying process becomes much more straightforward. Instead of having to check for existing reservations and then calculating the available dates, a simple query can be used to find available dates, significantly simplifying the process.

A practical illustration of this can be seen in a hotel reservation system. Without pre-allocation, you would need to create and maintain a separate collection of dates and cross-reference them with reservations to determine availability. In contrast, with pre-allocation, each room would have its own set of dates marked as reserved or available, allowing for easier and more efficient queries.

However, it’s essential to consider the trade-offs of this pattern. Pre-allocating data, especially for considerable time spans or many items, can result in increased storage and Request Unit (RU) usage. This is because you’re storing more information (e.g., all the dates in a year for each room), which can add up in terms of space and cost, particularly in large-scale applications.

For more in-depth examples and a detailed explanation of implementing this pattern, you can refer to Microsoft’s documentation on Azure Cosmos DB design patterns, particularly the preallocation pattern, and the GitHub repository Azure-Samples/cosmos-db-design-patterns, which includes various design pattern samples for building applications with Azure Cosmos DB for NoSQL.

Moreover, the concept of pre-calculated fields, as in pre-allocation, is well-supported in Azure Cosmos DB, thanks to its support for multi-document transactions. This allows for operations like the simultaneous creation of a book document and updating the author’s total book count in an ACID (atomicity, consistency, isolation, durability) transaction, providing consistency and reliability. More details on this can be found in the section on Modeling data in Azure Cosmos DB on Microsoft Learn.

The Scenario:

In the context of Azure Cosmos DB, the preallocation pattern is particularly useful for scenarios that involve managing collections of dates or values associated with a specific item. This pattern simplifies data management and querying by initializing necessary data structures in advance.

A common scenario illustrating the use of preallocation can be found in a hotel booking system. In this system, each hotel includes several rooms, and each room has a set of dates (termed ReservationDates) associated with it. These dates are preallocated for each room and show whether the room is available or reserved for each day. When a customer wants to book a room, the system efficiently checks the preallocated dates for availability, thus streamlining the reservation process.

Another example can be seen in a movie theater’s booking system. For each movie screening, the seats available in the theater are preallocated. Each seat is marked as either available or booked. This setup enables customers to view and select available seats, improving the user experience and reducing the computational load on the system.

However, it’s crucial to consider Azure Cosmos DB’s limitation of 2MB per item. In scenarios where a document might exceed this limit, such as a lengthy list of dates for a hotel room or a large array of seats for a movie screening, it becomes necessary to split the data across multiple items. This strategy ensures adherence to the size constraints while using the efficiency of the preallocation pattern.

In these scenarios, the preallocation pattern in Azure Cosmos DB helps efficient data management, especially in applications where quick access to a range of predefined data is essential. But it also needs careful planning to ensure data structures are still within the platform’s size limitations.

Sample Implementation:

Case Study: Non-preallocation and preallocation design patterns for hotel room reservations

In the world of Azure Cosmos DB for hotel room reservations, let us delve into two contrasting design patterns: non-preallocation and preallocation. Here are the detailed code examples highlighting each approach:

Non-Preallocation Pattern

In this approach, a hotel starts with a specified number of rooms without any pre-set reservations. The querying process involves checking existing reservations and then deciding available dates.

  • Example Hotel Structure:
{
  "id": "hotel_1",
  "EntityType": "hotel",
  "hotelId": "hotel_1",
  "Name": "Microsoft Hotels Inc",
  "City": "Redmond",
  "Address": "1 Microsoft Way",
  "Rating": 0,
  "AvailableRooms": 0,
  "Rooms": [],
  "Reservations": []
}
  • Example Room (Without Preallocation):
{
  "id": "room_0",
  "EntityType": "room",
  "hotelId": "hotel_1",
  "Name": null,
  "Type": null,
  "Status": null,
  "NoBeds": 0,
  "SizeInSqFt": 0,
  "Price": 0,
  "Available": false,
  "Description": null,
  "MaximumGuests": 0,
  "Features": [],
  "RoomImages": [],
  "Reviews": []
}
  • Example Reservation:
{
  "id": "reservation_room_0_20230213",
  "EntityType": "reservation",
  "LeaseId": null,
  "LeasedUntil": null,
  "IsPaid": false,
  "Status": null,
  "StartDate": "2023-02-13T00:00:00",
  "EndDate": "2023-02-14T00:00:00",
  "CheckIn": "0001-01-01T00:00:00",
  "CheckOut": "0001-01-01T00:00:00",
  "Customer": null,
  "hotelId": "hotel_1",
  "RoomId": "room_0",
  "Room": {
    "id": "room_0",
    "EntityType": "room",
    "LeaseId": null,
    "LeasedUntil": null,
    "hotelId": "hotel_1",
    "Name": null,
    "Type": null,
    "Status": null,
    "NoBeds": 0,
    "SizeInSqFt": 0,
    "Price": 0,
    "Available": false,
    "Description": null,
    "MaximumGuests": 0,
    "ReservationDates": [],
    "Features": [],
    "RoomImages": [],
    "Reviews": []
  }
}

Preallocation Pattern

In contrast, the preallocation approach involves setting up reservation dates for each room in advance. This pattern allows for more straightforward and efficient queries for room availability.

  • Code to Preallocate Reservation Dates:
DateTime start = DateTime.Parse(DateTime.Now.ToString("01/01/yyyy"));
DateTime end = DateTime.Parse(DateTime.Now.ToString("12/31/yyyy"));

// Add all the days for the year which can be queried later.
foreach (Room r in h.Rooms)
{
    int count = 0;

    while (start.AddDays(count) < end)
    {
        r.ReservationDates.Add(new ReservationDate { Date = start.AddDays(count), IsReserved = true });
        count++;
    }
}
  • Example Preallocated Room:
{
  "id": "room_0",
  "_etag": "\"de016f0a-0000-0700-0000-64f19c080000\"",
  "EntityType": "room",
  "LeaseId": null,
  "LeasedUntil": null,
  "hotelId": "hotel_1",
  "Name": null,
  "Type": null,
  "Status": null,
  "NoBeds": 0,
  "SizeInSqFt": 0,
  "Price": 0,
  "Available": false,
  "Description": null,
  "MaximumGuests": 0,
  "ReservationDates": [
    {
      "Date": "2023-09-01T00:00:00Z",
      "IsReserved": true
    },
    {
      "Date": "2023-09-02T00:00:00Z",
      "IsReserved": false
    },
    {
      "Date": "2023-09-03T00:00:00Z",
      "IsReserved": false
    },
    // ... more dates ...
  ]
}

In the scenario exploring non-preallocation and preallocation design patterns for hotel room reservations using Azure Cosmos DB, we can see two distinctly different approaches in handling reservation data.

Non-Preallocation Pattern

In this design, a hotel starts with rooms that have no predefined reservations. The process to check for reservations involves querying for existing reservations and then deducing the available dates.

  • Hotel Structure Example:
{
  "id": "hotel_1",
  "EntityType": "hotel",
  "hotelId": "hotel_1",
  "Name": "Microsoft Hotels Inc",
  "City": "Redmond",
  "Address": "1 Microsoft Way",
  "Rating": 0,
  "AvailableRooms": 0,
  "Rooms": [],
  "Reservations": []
}
  • Room Example (Without Preallocation):
{
  "id": "room_0",
  "EntityType": "room",
  "hotelId": "hotel_1",
  "Name": null,
  "Type": null,
  "Status": null,
  "NoBeds": 0,
  "SizeInSqFt": 0,
  "Price": 0,
  "Available": false,
  "Description": null,
  "MaximumGuests": 0,
  "Features": [],
  "RoomImages": [],
  "Reviews": []
}
  • Reservation Example:
{
  "id": "reservation_room_0_20230213",
  "EntityType": "reservation",
  "LeaseId": null,
  "LeasedUntil": null,
  "IsPaid": false,
  "Status": null,
  "StartDate": "2023-02-13T00:00:00",
  "EndDate": "2023-02-14T00:00:00",
  "CheckIn": "0001-01-01T00:00:00",
  "CheckOut": "0001-01-01T00:00:00",
  "Customer": null,
  "hotelId": "hotel_1",
  "RoomId": "room_0",
  "Room": {
    "id": "room_0",
    "EntityType": "room",
    "LeaseId": null,
    "LeasedUntil": null,
    "hotelId": "hotel_1",
    "Name": null,
    "Type": null,
    "Status": null,
    "NoBeds": 0,
    "SizeInSqFt": 0,
    "Price": 0,
    "Available": false,
    "Description": null,
    "MaximumGuests": 0,
    "ReservationDates": [],
    "Features": [],
    "RoomImages": [],
    "Reviews": []
  }
}

Preallocation Pattern

In contrast, the preallocation approach pre-defines reservation dates for each room. This simplifies querying for availability, as dates are already marked as reserved or available.

  • Code for Preallocating Reservation Dates:
DateTime start = DateTime.Parse(DateTime.Now.ToString("01/01/yyyy"));
DateTime end = DateTime.Parse(DateTime.Now.ToString("12/31/yyyy"));

// Adding all days of the year for each room.
foreach (Room r in h.Rooms) {
  int count = 0;
  while (start.AddDays(count) < end) {
      r.ReservationDates.Add(new ReservationDate { Date = start.AddDays(count), IsReserved = true });
      count++;
  }
}
  • Preallocated Room Example:
{
  "id": "room_0",
  "_etag": "\"de016f0a-0000-0700-0000-64f19c080000\"",
  "EntityType": "room",
  "LeaseId": null,
  "LeasedUntil": null,
  "hotelId": "hotel_1",
  "Name": null,
  "Type": null,
  "Status": null,
  "NoBeds": 0,
  "SizeInSqFt": 0,
  "Price": 0,
  "Available": false,
  "Description": null,
  "MaximumGuests": 0,
  "ReservationDates": [
    {
      "Date": "2023-09-01T00:00:00Z",
      "IsReserved": true
    },
    {
      "Date": "2023-09-02T00:00:00Z",
      "IsReserved": false
    },
    {
      "Date": "2023-09-03T00:00:00Z",
      "IsReserved": false
    },...
  ]
}

  • Queries:

By pre-allocating the room’s reservation days, you can easily run the following query to find available dates for a particular room or set of rooms:

SELECT
    a.Date,
    a.IsReserved,
    r.hotelId
FROM room r
    JOIN a IN r.ReservationDates
WHERE
    a.Date >= '2023-09-01T00:00:00Z' AND
    a.Date < '2023-09-02T00:00:00Z' AND
    a.IsReserved = false AND
    r.hotelId = 'hotel_1'

The preallocation pattern offers a more efficient and straightforward way to query room availability. By initializing reservation dates, the system can easily identify available dates, improving performance and user experience.

Why it Matters.

Using the preallocation design pattern in database management, particularly in systems like Azure Cosmos DB, offers several significant advantages, particularly in scenarios that involve scheduling and reservations, like hotel room bookings or seat allocations in theaters. Here’s why preallocation matters:

  1. Efficiency in Query Processing: Preallocation simplifies the querying process. Instead of computing availability dynamically each time a query is made, preallocation allows for direct lookup of available dates or items. This reduces computational overhead and can lead to quicker response times.
  2. Simplification of Application Logic: Preallocating resources like hotel room dates or theater seats makes the application logic simpler. The system does not need to perform complex calculations to determine availability, reducing the potential for errors and making the codebase more maintainable.
  3. Better Performance and Scalability: By reducing the computational burden during query execution, preallocation can enhance the overall performance of the system. This is beneficial in high-demand environments where systems need to handle many queries efficiently.
  4. Cost Efficiency in Resource Utilization: In a system like Azure Cosmos DB, which uses Request Units (RUs) to measure resource utilization, preallocation can be more cost-effective. It potentially reduces the number of RUs consumed per operation, as the queries are simpler and less resource intensive.
  5. Predictability and Reliability: Preallocating resources offers a level of predictability and reliability to the system. It ensures that the availability of resources is clearly defined upfront, reducing the chances of overbooking or other scheduling conflicts.
  6. Limitation Management: In systems like Azure Cosmos DB, where there is a limit on the size of an item (like 2MB), preallocation necessitates careful planning and management of data. This encourages efficient data modeling and structure, which is essential for optimized database performance.

However, it is essential to note the trade-offs as well. Preallocating large datasets can lead to increased storage requirements, and in systems with limitations on item size (like Azure Cosmos DB’s 2MB limit), it may necessitate breaking down data into multiple items. This requires careful planning and understanding of the system’s constraints.

In conclusion, preallocation is a powerful pattern in database design, particularly beneficial for systems requiring efficient querying of resources like time slots or seats. It offers a balance of efficiency, simplicity, and cost-effectiveness, making it a valuable strategy in various application domains.

Getting Started with Azure Cosmos DB Design Patterns

You can review the sample code by visiting the Preallocation design pattern on GitHub. You can also try this out for yourself by visiting the Azure Cosmos DB Design Patterns GitHub repo and cloning or forking it. Then run locally or from Code Spaces in GitHub. If you are new to Azure Cosmos DB, we have you covered with a free Azure Cosmos DB account for 30 days, no credit card needed. If you want more time, you can extend the free period. You can even upgrade too.

Sign up for your free Azure Cosmos DB account at aka.ms/trycosmosdb.

Explore this and the other design patterns and see how Azure Cosmos DB can enhance your application development and data modeling efforts. Whether you are an experienced developer or just getting started, the free trial allows you to discover the benefits firsthand.

To get started with Azure Cosmos DB Design Patterns, follow these steps:

  1. Visit the GitHub repository and explore the various design patterns and best practices provided.
  2. Clone or download the repository to access the sample code and documentation.
  3. Review the README files and documentation for each design pattern to understand when and how to apply them to your Azure Cosmos DB projects.
  4. Experiment with the sample code and adapt it to your specific use cases.

About Azure Cosmos DB

Azure Cosmos DB is a fully managed and serverless distributed database for modern app development, with SLA-backed speed and availability, automatic and instant scalability, and support for open-source PostgreSQL, MongoDB, and Apache Cassandra. Try Azure Cosmos DB for free here. To stay in the loop on Azure Cosmos DB updates, follow us on Twitter/XYouTube, and LinkedIn.

AI-assisted content. This article was partially created with the help of AI. An author reviewed and revised the content as needed. Learn more

Author

Jay Gordon
Senior Program Manager

Jay Gordon is a Senior Program Manager with Azure Cosmos DB focused on reaching developer communities. Jay is located in Brooklyn, NY.

0 comments

Discussion are closed.