Umbraco is Always Loading Content From Database on Azure App Service

Explore a comprehensive workaround to boost Umbraco performance on Azure Linux App Service. Learn to tackle the persistent content loading issue from the database with our expert insights and detailed code solutions

Introduction to the Azure Linux App Service & Umbraco Problem

As the field of web development continues to evolve, prioritizing performance optimization becomes increasingly crucial, particularly for database-intensive content management systems such as Umbraco.

However, a significant performance issue for Umbraco instances running on Azure Linux App Service has been identified.

This problem, deeply discussed in a GitHub #13909 issue, impacts sites with a large volume of content, notably those exceeding 100,000 nodes.

The Core Umbraco App Service Issue with Local Cache

The crux of the issue lies in how Umbraco handles content loading during restarts or deployments in an Azure environment.

Despite adhering to the recommended configurations outlined in the Umbraco documentation for Azure Web Apps, our team has observed that Umbraco persistently loads content from the database instead of utilizing the local cache.

This behavior significantly increases load times and affects overall performance.

The underlying cause can be traced back to the Azure App Service's handling of the MachineName.

Each time a deployment or restart occurs, the MachineName changes, leading to the absence of the *.-lastsynced.txt file, as detailed in Umbraco's LastSyncedFileManager.

Additionally, the local cache files are not preserved due to the behavior of the /tmp/ directory on Azure Linux App Service, as explained in Microsoft's documentation on Path.GetTempPath method.

The Broader Context with Azure Workers

This problem is exacerbated by the nature of web worker migrations in Azure Web Apps, where websites may be moved between different workers.

Variables such as Environment.MachineName change as a result, impacting any code or libraries reliant on these static variables.

The Umbraco documentation highlights this, but the current behavior of Umbraco does not align with the expected outcome in such environments.

Interestingly, the handling of Examine files within Umbraco suggests a possible solution.

These files use a SyncedTempFileSystemDirectoryFactory configuration, allowing local indexing and replication back to the main storage, as per the Examine settings documentation.

Umbraco Performance Impact

This issue profoundly impacts large-scale Umbraco sites, drastically affecting their startup times and overall performance.

It's a critical challenge for developers and content managers working in Azure environments, necessitating an effective workaround until the Umbraco team implements a permanent fix.

In the following sections of this article, we will delve into a comprehensive workaround, including complete code, to mitigate this issue and enhance the performance of Umbraco on Azure Linux App Service.

The code below addresses the specific issue of Umbraco’s handling of cache and database loading in Azure environments.

The purpose is to modify the default behavior of Umbraco's caching system (NuCache) and ensure that the system can properly identify and use the last synced state in a cloud environment where server instances can change frequently.

Stay tuned for the next section to explore the detailed workaround and implementation steps.

AlwaysLoadingContentFromDatabaseWorkaroundComponent

This class is a custom component in Umbraco that addresses the issue of the Last Synced File not being properly recognized in Azure environments.

Key elements include:

  • Initialization: It initializes with dependencies like ILogger, LastSyncedFileManager, and PublishedSnapshotServiceOptions.
  • SetLastSyncedFile Method: This method checks if the local database should be ignored (IgnoreLocalDb). If the expected last synced file does not exist, it locates the most recent -lastsynced.txt file and moves it to the expected file path. This ensures that Umbraco correctly identifies the last synced state even when the Azure App Service instance is moved or restarted.
  • Error Handling: If there's an issue during this process, it logs an error.
public class AlwaysLoadingContentFromDatabaseWorkaroundComponent : IComponent
{
    private readonly ILogger<AlwaysLoadingContentFromDatabaseWorkaroundComponent> _logger;

    private readonly LastSyncedFileManager _lastSyncedFileManager;

    private readonly PublishedSnapshotServiceOptions _publishedSnapshotServiceOptions;

    public AlwaysLoadingContentFromDatabaseWorkaroundComponent(
        ILogger<AlwaysLoadingContentFromDatabaseWorkaroundComponent> logger,
        LastSyncedFileManager lastSyncedFileManager,
        PublishedSnapshotServiceOptions publishedSnapshotServiceOptions)
    {
        _logger = logger;

        _lastSyncedFileManager = lastSyncedFileManager;

        _publishedSnapshotServiceOptions = publishedSnapshotServiceOptions;
    }

    public void Initialize()
    {
        SetLastSyncedFile();
    }

    public void Terminate() { }

    private void SetLastSyncedFile()
    {
        if (_publishedSnapshotServiceOptions.IgnoreLocalDb)
            return;

        try
        {
            var expectedFilePath = _lastSyncedFileManager.DistCacheFilePath;

            if (File.Exists(expectedFilePath))
                return;

            var directoryPath = Path.GetDirectoryName(expectedFilePath);
            var existingFile = Directory.GetFiles(directoryPath, "*-lastsynced.txt")
                .OrderByDescending(filePath => (new FileInfo(filePath)).LastWriteTime)
                .FirstOrDefault();

            if (existingFile.IsSet() && existingFile != expectedFilePath)
            {
                Directory.Move(existingFile, expectedFilePath);

                _logger.LogInformation("The last synced file has been adjusted as a workaround.");
            }
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Unable to set last synced file.");
        }
    }
}

AlwaysLoadingContentFromDatabaseWorkaroundComposer

This is a Composer class in Umbraco, used for dependency injection and initialization of components. 

It ensures that the AlwaysLoadingContentFromDatabaseWorkaroundComponent is registered and initialized.

It's a critical piece for the proper sequence of component initialization.

public class AlwaysLoadingContentFromDatabaseWorkaroundComposer : ComponentComposer<AlwaysLoadingContentFromDatabaseWorkaroundComponent> { }

Final Thoughts on Umbraco Cloud Solutions

Tackling the challenge of optimizing Umbraco on Azure App Service, especially concerning the efficiency of loading content from the database, is a critical step for developers and content managers.

This article has delved into a series of custom solutions and code modifications designed to enhance Umbraco's performance in cloud-based environments.

By addressing the persistent issue of database loading and cache management, we can significantly improve the responsiveness and reliability of Umbraco sites hosted on Azure.

Remember, while these solutions provide a workaround for current limitations, staying updated with the latest developments from the Umbraco community is essential. 

As technology evolves, so do the solutions and best practices for managing content management systems in cloud environments.

🌐 Explore More: Interested in learning about Umbraco, Azure App Service, and other web development insights? Explore our blog for a wealth of information and expert advice.

✉️Get in Touch: If you have questions or need assistance with your Umbraco projects on Azure, don't hesitate to contact us. Our team of experts is always ready to help you navigate through any challenges and optimize your digital solutions.

↑ Top ↑