<?xml version="1.0" encoding="utf-8"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/"><channel><language>en</language><title>Blog posts by Mike</title> <link>https://world.optimizely.com/blogs/mike/</link><description></description><ttl>60</ttl><generator>Optimizely World</generator><item> <title>Scheduler Delegation: Using Optimizely DXP Infrastructure for Queued Processing</title>            <link>https://world.optimizely.com/blogs/mike/dates/2026/10/scheduler-delegation-using-optimizely-dxp-infrastructure-for-queued-processing/</link>            <description>&lt;div&gt;
&lt;div&gt;At SYZYGY Techsolutions, we support Optimizely DXP projects at scale, so continuously identifying unique ways to optimize the application is an essential part of how we work. One pattern that has paid off repeatedly is&amp;nbsp;&lt;strong&gt;scheduler delegation&lt;/strong&gt;: moving expensive, recurring or bursty background work off user-facing instances and onto the dedicated scheduler instance, using infrastructure that DXP already provides.&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;This post walks through the problem, the architecture, the reasoning behind it, and the&amp;nbsp;code needed to apply it in your own solution.&lt;/div&gt;
&lt;h2&gt;The problem: expensive work triggered from the request path&lt;/h2&gt;
Imagine thousands of visitors performing a similar action at the same time, spread across several instances of your application. They might be clicking through a car configurator, where every choice of model, colour or engine asks which options can still be combined and which are available. Or they might simply be opening pages, each of which needs the website&#39;s translated texts in their language.&lt;br /&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;Resolving that action takes time. The answer depends on several data sources, each with its own rules: product rules, production planning and supplier stock for the configurator; the CMS, a translation management system and other applications for the texts. Yet the number of distinct questions is limited, and the answer does not need to be exact to the second. The final order confirms availability anyway, and a text that is a minute old is harmless.&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;So you cache the result. That solves the speed, but introduces new problems:&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;1.&amp;nbsp;&lt;strong&gt;Cold starts.&lt;/strong&gt;&amp;nbsp;After a deployment, a restart or an expiry, the cache is empty and the first visitors get nothing or wait.&lt;/div&gt;
&lt;div&gt;2. &lt;strong&gt;Repeated work.&lt;/strong&gt;&amp;nbsp;Many visitors hit the same empty entry at once, and each request calculates the same answer, overloading the underlying systems.&lt;/div&gt;
&lt;div&gt;3.&amp;nbsp;&lt;strong&gt;Instances out of sync.&lt;/strong&gt; Every web instance has its own cache, recalculates on its own schedule, and may show a different answer than its neighbour.&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;Together these produce a cache stampede: the same heavy work done many times over, competing with visitor traffic for CPU, memory and connections to the source systems. Making visitors wait for the calculation only moves the problem onto them. The goal is to keep&amp;nbsp;&lt;strong&gt;the request path fast, whatever the state of the cache&lt;/strong&gt;.&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;Optimizely&#39;s built-in &lt;span style=&quot;font-family: &#39;courier new&#39;, courier, monospace;&quot;&gt;ISynchronizedObjectInstanceCache&lt;/span&gt; is a great fit for data that changes infrequently. Under high load in a multi-instance setup, however, we observed clear limits of its caching model for this kind of workload. Each instance keeps its own in-memory copy, and only invalidations travel between instances. Every instance that recalculates an entry and stores it sends an invalidation to all the others, which drop their copies and recalculate in turn. The result is a stream of cache-update events from every side, the same stampede repeated on every instance, and moments when instances serve different values or overwrite each other&#39;s results.&lt;/div&gt;
&lt;h2&gt;What DXP gives you: a scheduler instance&lt;/h2&gt;
&lt;div&gt;On Preproduction and Production, Optimizely DXP customers can request a dedicated &lt;strong&gt;scheduler instance&lt;/strong&gt;, one per environment, next to the web instances that serve visitors. This instance:&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;does not receive user-facing requests;&lt;/li&gt;
&lt;li&gt;is intended for backend work: scheduled jobs, background tasks and back-office operations performed by content editors;&lt;/li&gt;
&lt;li&gt;is part of the same deployment, so it runs your code, your configuration and your initialization modules.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;In other words, DXP already hands you a place where heavy work runs away from visitor traffic. Most teams use it only for classic scheduled jobs on a fixed timetable. Scheduler delegation extends it to&amp;nbsp;&lt;strong&gt;event-driven, queued work&lt;/strong&gt;: user-facing instances report that something is needed, and the scheduler instance does it.&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;The rest of the pattern relies on platform features that every DXP project already has:&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Capability&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Provided by&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Role in the pattern&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cross-instance messaging&lt;/td&gt;
&lt;td&gt;The CMS event system (&lt;span style=&quot;font-family: &#39;courier new&#39;, courier, monospace;&quot;&gt;IEventRegistry&lt;/span&gt;), which uses Azure Service Bus on DXP&lt;/td&gt;
&lt;td&gt;Carries a small &quot;something is missing&quot; message from any instance to all instances&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scheduler awareness&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: &#39;courier new&#39;, courier, monospace;&quot;&gt;SchedulerOptions.Enabled&lt;/span&gt; (EPiServer.Scheduler), which is only &lt;span style=&quot;font-family: &#39;courier new&#39;, courier, monospace;&quot;&gt;true&lt;/span&gt; on the scheduler instance&lt;/td&gt;
&lt;td&gt;Lets a handler decide whether it should act on a message&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scheduled jobs&lt;/td&gt;
&lt;td&gt;The CMS scheduled job engine (&lt;span style=&quot;font-family: &#39;courier new&#39;, courier, monospace;&quot;&gt;ScheduledJobBase&lt;/span&gt;, &lt;span style=&quot;font-family: &#39;courier new&#39;, courier, monospace;&quot;&gt;[ScheduledPlugIn]&lt;/span&gt;)&lt;/td&gt;
&lt;td&gt;Provides a recurring, restartable execution loop for batch processing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Shared state&lt;/td&gt;
&lt;td&gt;Blob storage (&lt;span style=&quot;font-family: &#39;courier new&#39;, courier, monospace;&quot;&gt;IBlobFactory&lt;/span&gt;), the CMS/Commerce databases, or a persisted synchronized cache&lt;/td&gt;
&lt;td&gt;Carries the finished result back to user-facing instances&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div&gt;&lt;br /&gt;
&lt;div&gt;Because all of this ships with DXP, the pattern runs entirely on infrastructure you already have.&lt;/div&gt;
&lt;h2&gt;Architecture overview&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/link/a6ead68713a0439c98e910ab124199f6.aspx&quot; width=&quot;1233&quot; height=&quot;790&quot; /&gt;&lt;/p&gt;
&lt;div&gt;The flow, step by step:&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;1. &lt;strong&gt;Raise.&lt;/strong&gt; A user-facing instance reads the calculated result from shared storage. If it is missing or stale, it returns what it has (stale data or a fallback) and&amp;nbsp;&lt;strong&gt;raises an event&lt;/strong&gt;&amp;nbsp;that identifies what is needed. Raising is fire-and-forget and must never throw into the request.&lt;/div&gt;
&lt;div&gt;2. &lt;strong&gt;Deliver.&lt;/strong&gt;&amp;nbsp;The event travels over the platform&#39;s event bus and reaches every instance.&lt;/div&gt;
&lt;div&gt;3. &lt;strong&gt;Enqueue.&lt;/strong&gt;&amp;nbsp;Every instance receives the event, but only the scheduler instance acts on it: it places the item into an in-memory queue that keeps one entry per unique input.&lt;/div&gt;
&lt;div&gt;4. &lt;strong&gt;Batch.&lt;/strong&gt;&amp;nbsp;A scheduled job runs at a fixed interval (for example every minute), drains the queue, and processes each unique input once.&lt;/div&gt;
&lt;div&gt;5. &lt;strong&gt;Write back.&lt;/strong&gt;&amp;nbsp;The result is written to shared storage.&lt;/div&gt;
&lt;div&gt;6. &lt;strong&gt;Read.&lt;/strong&gt;&amp;nbsp;User-facing instances pick up the result on their next read, with no direct communication between the scheduler and the web instances.&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;img src=&quot;/link/0e2a878fd9394d50bf63984746603a0e.aspx&quot; width=&quot;1233&quot; height=&quot;764&quot; /&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;From the visitor&#39;s perspective, every request is answered immediately. The expensive work runs later on the scheduler instance, once per unique input.&lt;/div&gt;
&lt;h2&gt;Design decisions and the reasoning behind them&lt;/h2&gt;
&lt;div&gt;The code samples below are simplified C# against the Optimizely CMS 12 APIs. Logging, null checks and dependency injection details are trimmed for readability.&lt;/div&gt;
&lt;h3&gt;1. Events carry intent; data stays at the source&lt;/h3&gt;
&lt;div&gt;The event payload is only an identifier (for example &lt;span style=&quot;font-family: &#39;courier new&#39;, courier, monospace;&quot;&gt;{ model, options }&lt;/span&gt;). It is small, cheap to serialize and well within message size limits. The scheduler fetches whatever data it needs from the source of truth when it processes the item. This avoids stale payloads and keeps the bus lightweight.&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;The payload must be serializable by the event system, and its type must be registered as a known event type:&lt;/div&gt;
&lt;pre&gt;&lt;code&gt;using System.Runtime.Serialization;
using EPiServer.Events;

[DataContract]
[EventsServiceKnownType]
public class RecalculationRequest
{
    [DataMember] public string Model { get; set; } = &quot;&quot;;
    [DataMember] public string Options { get; set; } = &quot;&quot;;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. An in-memory queue on the scheduler is enough&lt;/h3&gt;
&lt;div&gt;A durable queue (a dedicated Service Bus queue, a database table) is the obvious place for pending work. Here the work is &lt;strong&gt;idempotent and re-calculable&lt;/strong&gt;, so a simple in-memory queue on the scheduler instance does the job:&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;If the scheduler restarts and loses its queue, the next request that sees a missing or stale result raises the event again.&lt;/li&gt;
&lt;li&gt;Duplicate events are harmless because the queue keeps one entry per input.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;Self-healing through the request path replaces durability. That removes persistence, retries, dead-lettering and cleanup from the design. This only holds if the work really is idempotent; for non-recoverable work, use a durable queue instead.&lt;/div&gt;
&lt;h3&gt;3. Batching and de-duplication give you load control&lt;/h3&gt;
&lt;div&gt;&lt;img src=&quot;/link/812931f526124590b55800d55924edf8.aspx&quot; width=&quot;1234&quot; height=&quot;617&quot; /&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;Because events are queued and drained on a timer, the cost of the system depends only on the number of &lt;strong&gt;unique inputs&lt;/strong&gt;. Request volume and instance count drop out of the equation: a burst of thousands of misses collapses into a handful of resource (re)generations. The job interval becomes a single dial that trades refresh latency against background load.&lt;/div&gt;
&lt;h3&gt;4. A de-duplicating queue keeps producers fast and memory bounded&lt;/h3&gt;
&lt;div&gt;The queue is written by event handler threads (producers) and drained by the job thread (consumer). Keying it by the inputs means a burst of identical events occupies a single entry, so memory stays bounded during a miss storm. Draining swaps in a fresh dictionary atomically, so producers never wait for the batch. The queue is registered as a singleton, so the event handler and the scheduled job share the same instance:&lt;/div&gt;
&lt;pre&gt;&lt;code&gt;using System.Collections.Concurrent;
using EPiServer.ServiceLocation;

[ServiceConfiguration(Lifecycle = ServiceInstanceScope.Singleton)]
public class RecalculationQueue
{
    private ConcurrentDictionary&amp;lt;(string Model, string Options), RecalculationRequest&amp;gt; _pending = new();

    public void Add(RecalculationRequest request) =&amp;gt;
        _pending.TryAdd((request.Model, request.Options), request);

    public IReadOnlyCollection&amp;lt;RecalculationRequest&amp;gt; Drain()
    {
        // A write racing the swap can be lost; the next miss raises it again.
        var drained = Interlocked.Exchange(ref _pending, new());
        return drained.Values.ToList();
    }

    public bool IsEmpty =&amp;gt; _pending.IsEmpty;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5. One event rail, two routing modes&lt;/h3&gt;
&lt;div&gt;Local development, CI and single-node environments usually run without a scheduler instance. A configuration flag routes the same event differently, so one code path covers both setups:&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;img src=&quot;/link/28e7cbdd7e704261a5ae2f3ed33c25e5.aspx&quot; width=&quot;1236&quot; height=&quot;710&quot; /&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Delegation on&lt;/strong&gt; (Preproduction, Production): only the scheduler instance reacts, and it queues the item.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Delegation off&lt;/strong&gt; (local, simple setups): only the instance that raised the event reacts, and it processes the item immediately on a background thread.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;Both modes share the same read path and the same processing logic. Only the &quot;who handles it, and when&quot; decision changes, and that sits behind a configuration flag. This keeps the pattern easy to test locally and makes it safe to switch off.&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;The routing is encapsulated in an abstract initialization module. Concrete features only supply the event identity and the processing logic (template method pattern):&lt;/div&gt;
&lt;pre&gt;&lt;code&gt;using EPiServer.Events;
using EPiServer.Events.Clients;
using EPiServer.Framework;
using EPiServer.Framework.Initialization;
using EPiServer.Scheduler;
using EPiServer.ServiceLocation;
using Microsoft.Extensions.Options;

public abstract class DelegatingEventModule&amp;lt;TRequest&amp;gt; : IInitializableModule
    where TRequest : class
{
    protected abstract Guid EventId { get; }
    protected abstract Guid RaiserId { get; }
    protected abstract void HandleOnScheduler(TRequest request);
    protected abstract void HandleLocally(TRequest request);

    private bool DelegateToScheduler =&amp;gt;
        ServiceLocator.Current.GetInstance&amp;lt;IOptions&amp;lt;DelegationOptions&amp;gt;&amp;gt;().Value.Enabled;

    private static bool IsScheduler =&amp;gt;
        ServiceLocator.Current.GetInstance&amp;lt;IOptions&amp;lt;SchedulerOptions&amp;gt;&amp;gt;().Value.Enabled;

    public void Initialize(InitializationEngine context)
    {
        var evt = ServiceLocator.Current.GetInstance&amp;lt;IEventRegistry&amp;gt;().Get(EventId);
        evt.Raised += DelegateToScheduler ? OnSchedulerRoute : OnLocalRoute;
    }

    public void Uninitialize(InitializationEngine context)
    {
        var evt = ServiceLocator.Current.GetInstance&amp;lt;IEventRegistry&amp;gt;().Get(EventId);
        evt.Raised -= DelegateToScheduler ? OnSchedulerRoute : OnLocalRoute;
    }

    private void OnSchedulerRoute(object sender, EventNotificationEventArgs e)
    {
        if (!IsScheduler) return;                  // web instances ignore it
        if (e.Param is not TRequest request) return;
        HandleOnScheduler(request);                // typically: queue.Add(request)
    }

    private void OnLocalRoute(object sender, EventNotificationEventArgs e)
    {
        if (e.RaiserId != RaiserId) return;        // only handle what this instance raised
        if (e.Param is not TRequest request) return;
        Task.Run(() =&amp;gt; HandleLocally(request));
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;A concrete module is discovered by the initialization engine through &lt;span style=&quot;font-family: &#39;courier new&#39;, courier, monospace;&quot;&gt;[ModuleDependency]&lt;/span&gt;:&lt;/div&gt;
&lt;pre&gt;&lt;code&gt;[ModuleDependency(typeof(EPiServer.Web.InitializationModule))]
public class ConfiguratorRecalculationModule : DelegatingEventModule&amp;lt;RecalculationRequest&amp;gt;
{
    internal static readonly Guid Id = new(&quot;00000000-0000-0000-0000-000000000001&quot;);
    internal static readonly Guid Raiser = Guid.NewGuid();

    protected override Guid EventId =&amp;gt; Id;
    protected override Guid RaiserId =&amp;gt; Raiser;

    protected override void HandleOnScheduler(RecalculationRequest request) =&amp;gt;
        ServiceLocator.Current.GetInstance&amp;lt;RecalculationQueue&amp;gt;().Add(request);

    protected override void HandleLocally(RecalculationRequest request) =&amp;gt;
        ServiceLocator.Current.GetInstance&amp;lt;ConfiguratorCalculator&amp;gt;().Recalculate(request);
}&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;Four details matter here:&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;span style=&quot;font-family: &#39;courier new&#39;, courier, monospace;&quot;&gt;IsScheduler&lt;/span&gt; reads &lt;span style=&quot;font-family: &#39;courier new&#39;, courier, monospace;&quot;&gt;SchedulerOptions.Enabled&lt;/span&gt;, which the platform sets per instance. On DXP the scheduler is enabled only on the scheduler instance, so the check follows the real topology.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: &#39;courier new&#39;, courier, monospace;&quot;&gt;DelegationOptions&lt;/span&gt; is your own options class, bound from &lt;span style=&quot;font-family: &#39;courier new&#39;, courier, monospace;&quot;&gt;appsettings.json&lt;/span&gt;. It is the configuration flag that switches between the two routing modes.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: &#39;courier new&#39;, courier, monospace;&quot;&gt;RaiserId&lt;/span&gt; is generated once per process (&lt;span style=&quot;font-family: &#39;courier new&#39;, courier, monospace;&quot;&gt;Guid.NewGuid()&lt;/span&gt; in a static field). It lets an instance recognize its own events when delegation is off.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: &#39;courier new&#39;, courier, monospace;&quot;&gt;EventId&lt;/span&gt; is a fixed GUID. Every instance must use the same value to subscribe to the same event.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;6. The read path is defensive&lt;/h3&gt;
&lt;div&gt;Reading the result and requesting a recalculation live in one service on the web instances. It follows a simple rule, &lt;strong&gt;stale data beats no data&lt;/strong&gt;, and a failure to raise the event never reaches the visitor:&lt;/div&gt;
&lt;pre&gt;&lt;code&gt;using EPiServer.Events.Clients;

public class ConfiguratorResultService(
    IEventRegistry eventRegistry,
    IResultStorage storage,
    ILogger&amp;lt;ConfiguratorResultService&amp;gt; logger)
{
    private static readonly TimeSpan Ttl = TimeSpan.FromMinutes(2);

    public ConfiguratorResult GetResult(RecalculationRequest request)
    {
        var entry = storage.TryRead(request);

        if (entry is null || entry.CalculatedAt &amp;lt; DateTime.UtcNow - Ttl)
        {
            RequestRecalculation(request);
        }

        return entry?.Value ?? ConfiguratorResult.Fallback;
    }

    private void RequestRecalculation(RecalculationRequest request)
    {
        try
        {
            eventRegistry.Get(ConfiguratorRecalculationModule.Id)
                ?.Raise(ConfiguratorRecalculationModule.Raiser, request);
        }
        catch (Exception ex)
        {
            // A failed raise only delays the refresh; the visitor still gets an answer.
            logger.LogWarning(ex, &quot;Recalculation request for {Model} could not be raised&quot;, request.Model);
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;7. The scheduled job is the batch processor&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;using EPiServer.PlugIn;
using EPiServer.Scheduler;

[ScheduledPlugIn(
    DisplayName = &quot;Recalculate configurator results on demand&quot;,
    GUID = &quot;00000000-0000-0000-0000-000000000002&quot;,
    IntervalType = ScheduledIntervalType.Minutes,
    IntervalLength = 1,
    DefaultEnabled = true,
    Restartable = true)]
public class RecalculateOnDemandJob : ScheduledJobBase
{
    private readonly RecalculationQueue _queue;
    private readonly ConfiguratorCalculator _calculator;
    private readonly ILogger&amp;lt;RecalculateOnDemandJob&amp;gt; _logger;
    private bool _stopRequested;

    public RecalculateOnDemandJob(
        RecalculationQueue queue,
        ConfiguratorCalculator calculator,
        ILogger&amp;lt;RecalculateOnDemandJob&amp;gt; logger)
    {
        _queue = queue;
        _calculator = calculator;
        _logger = logger;
        IsStoppable = true;
    }

    public override void Stop() =&amp;gt; _stopRequested = true;

    public override string Execute()
    {
        if (_queue.IsEmpty) return &quot;Nothing to recalculate&quot;;

        var batch = _queue.Drain();

        var done = 0;
        foreach (var request in batch)
        {
            if (_stopRequested) break;
            try
            {
                _calculator.Recalculate(request);   // calculate and write to shared storage
                done++;
            }
            catch (Exception ex)
            {
                // One failing item must not abandon the batch; the next miss re-raises it.
                _logger.LogError(ex, &quot;Recalculation failed for {Model}&quot;, request.Model);
            }
        }

        return $&quot;Recalculated {done} of {batch.Count} items&quot;;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;&lt;span style=&quot;font-family: &#39;courier new&#39;, courier, monospace;&quot;&gt;Restartable = true&lt;/span&gt; makes the scheduler start the job again if the instance shuts down while it is running, and &lt;span style=&quot;font-family: &#39;courier new&#39;, courier, monospace;&quot;&gt;IsStoppable&lt;/span&gt; together with &lt;span style=&quot;font-family: &#39;courier new&#39;, courier, monospace;&quot;&gt;Stop()&lt;/span&gt; lets an operator end a long batch from the admin interface. Items dropped by a stop are simply requested again by the next miss.&lt;/div&gt;
&lt;h2&gt;Results in production&lt;/h2&gt;
&lt;div&gt;In one production implementation, the read path serves about 1,022,000 requests per day (absolute figures are scaled for confidentiality; the ratios are real):&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;1,000,000 hits&lt;/strong&gt; (about 98%), answered from a fresh entry;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;22,000 stale reads&lt;/strong&gt; (about 2%), answered immediately with the previous result while the scheduler refreshes the entry;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;0 misses. &lt;/strong&gt;Misses only occur before the storage is first populated. After that, visitors receive the previous value of an expired entry until it is refreshed.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;Before the pattern, each of those 22,000 stale reads would have been a miss: a visitor waiting for a synchronous recalculation, with several instances often recalculating the same entry at once. Stale reads now absorb that work, and the recalculation runs once per unique input on the scheduler instance. The exact numbers depend on traffic, the feature and the chosen TTL, but the shape stays the same: misses disappear and stale reads take their place.&lt;/div&gt;
&lt;h2&gt;What enables this architecture&lt;/h2&gt;
&lt;div&gt;The building blocks are the platform capabilities listed earlier. Three properties make them fit together:&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Every instance runs the same deployment.&lt;/strong&gt; Initialization modules subscribe to the event on all instances, and a single platform setting decides which one acts.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The control path and the data path are separate.&lt;/strong&gt; Events travel over the bus, results travel through shared storage, and the scheduler and web instances never call each other. Either path can be replaced independently.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;There is a single writer.&lt;/strong&gt; Only the scheduler instance calculates and stores results, so every web instance reads the same value. Eventual consistency is confined to one place: the time until the next job tick.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;The return path is a free choice&lt;/h2&gt;
&lt;div&gt;Any storage that every instance can read works as the return path. Blob storage is the simplest starting point and handles large results well. The CMS or Commerce database suits small structured results but adds load to a shared database. A synchronized cache gives the lowest read latency, provided the scheduler instance stays its only writer; an eviction then simply raises a new recalculation request. Whichever you choose, keep a short-lived local cache on the web instances so that shared storage is read once per interval rather than on every request.&lt;/div&gt;
&lt;h2&gt;Trade-offs and failure modes&lt;/h2&gt;
&lt;div&gt;Restarts, duplicate events, failing items and environments without a scheduler are covered by the design decisions above. Three trade-offs remain:&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Concern&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Behavior&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Mitigation&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Eventual consistency&lt;/td&gt;
&lt;td&gt;A first-time miss is answered with a fallback until the next job tick&lt;/td&gt;
&lt;td&gt;Choose an interval that matches business tolerance; pre-warm after deployments&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Event delivery is best-effort&lt;/td&gt;
&lt;td&gt;A message may be missed in rare cases&lt;/td&gt;
&lt;td&gt;The next miss or stale read raises it again; the TTL bounds the impact&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Shared scheduler capacity&lt;/td&gt;
&lt;td&gt;The scheduler instance also runs every other scheduled job and the editors&#39; back-office work, so a long batch competes with them&lt;/td&gt;
&lt;td&gt;Keep per-item work bounded, watch batch duration, and move truly heavy work to a dedicated service&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;br /&gt;
&lt;div&gt;Observability is worth building from day one: log every miss, every raise, and the batch size per tick. The ratio between events received and unique inputs processed is a direct measure of how much work the pattern is saving.&lt;/div&gt;
&lt;h2&gt;When to use it&lt;/h2&gt;
&lt;div&gt;The problem section already describes the shape that fits: a limited set of repeated questions, slow answers from several sources, tolerance for slightly outdated results, and work that can be recalculated at any time. Besides configurators and translated texts, the same shape appears in accessory compatibility per model, dealer and store details per region, and filter options per product category.&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;Two further conditions are easy to overlook:&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The answer contains no financial values, personal data or access decisions.&lt;/strong&gt; Such data must be accurate and current on every request.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Visitors ask only a fraction of all possible questions.&lt;/strong&gt; If every answer is needed anyway, a plain scheduled job that calculates everything is simpler.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;Cases that look similar but fail one of these conditions:&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Business case&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Why it does NOT fit&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prices, discounts, taxes, delivery costs&lt;/td&gt;
&lt;td&gt;Financial values shown to customers must be accurate at all times. An outdated value creates legal and reputational risk.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stock or slot reservation at checkout&lt;/td&gt;
&lt;td&gt;Visitors compete for the same item, which needs a synchronous, locked check.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Personal recommendations or account data&lt;/td&gt;
&lt;td&gt;Inputs are unique per visitor, so de-duplication has nothing to merge, and personal data should stay out of shared storage.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Navigation or content filtered by permissions&lt;/td&gt;
&lt;td&gt;Access decisions must be current. A stale answer can expose content.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sitemaps and product feeds&lt;/td&gt;
&lt;td&gt;Every answer is needed and read by machines on a timetable. A plain scheduled job is simpler.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Image renditions&lt;/td&gt;
&lt;td&gt;Serving the original as a fallback defeats the purpose, and the number of variants is large. Resize on demand at the CDN.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;Getting started checklist&lt;/h2&gt;
&lt;div&gt;1. Request a scheduler instance for Preproduction and Production through Optimizely support.&lt;/div&gt;
&lt;div&gt;2. Identify one expensive, re-calculable operation currently done on the request path.&lt;/div&gt;
&lt;div&gt;3. Define a small event payload and a stable event identifier.&lt;/div&gt;
&lt;div&gt;4. Implement the read path: raise on miss or stale, never throw, always return something.&lt;/div&gt;
&lt;div&gt;5. Implement the delegating module with both routing modes behind a configuration flag.&lt;/div&gt;
&lt;div&gt;6. Add the de-duplicating queue and the scheduled batch job.&lt;/div&gt;
&lt;div&gt;7. Pick the return path and a TTL that match your consistency needs.&lt;/div&gt;
&lt;div&gt;8. Add logging and metrics for misses, raises and batch size.&lt;/div&gt;
&lt;div&gt;9. Disable delegation locally; enable it in Preproduction and Production.&lt;/div&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;div&gt;Scheduler delegation turns an under-used part of DXP into a small background processing system. Web instances report what they need over the event bus, the scheduler instance collects those requests and processes them in batches, and shared storage hands the results back. The request path stays fast, each heavy calculation runs once per unique input, and everything runs on infrastructure DXP already provides.&lt;/div&gt;
&lt;/div&gt;</description>            <guid>https://world.optimizely.com/blogs/mike/dates/2026/10/scheduler-delegation-using-optimizely-dxp-infrastructure-for-queued-processing/</guid>            <pubDate>Tue, 06 Oct 2026 08:46:00 GMT</pubDate>           <category>Blog post</category></item><item> <title>Remote Debugging in Optimizely DXP: What Is Actually Possible? </title>            <link>https://world.optimizely.com/blogs/mike/dates/2026/4/remote-debugging-in-optimizely-dxp-what-is-actually-possible-/</link>            <description>&lt;div class=&quot;OutlineElement Ltr SCXW248700128 BCX8&quot;&gt;
&lt;h3 class=&quot;Paragraph SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;TextRun SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;Introduction&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p class=&quot;Paragraph SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;TextRun SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;At SYZYGY &lt;/span&gt;&lt;span class=&quot;NormalTextRun SpellingErrorV2Themed SCXW248700128 BCX8&quot;&gt;Techsolutions&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;, we support Optimizely DXP projects at scale, so continuously&amp;nbsp;&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;identifying&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;&amp;nbsp;the right tools and approaches for analyzing complex issues is an essential part of how we work.&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;EOP Selected SCXW248700128 BCX8&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;OutlineElement Ltr SCXW248700128 BCX8&quot;&gt;
&lt;p class=&quot;Paragraph SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;TextRun SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;At some point, most developers&amp;nbsp;&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;encounter&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt; issues that are complex&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;to reproduce locally. Differences between local setup and other environments, variations in operating systems, and discrepancies in configuration or data all contribute to this gap.&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;TextRun SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;While logs and telemetry provide valuable signals, they are inherently indirect. They rely on post&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;NoBreakHyphenBlob BlobObject DragDrop SCXW248700128 BCX8&quot;&gt;‑&lt;/span&gt;&lt;span class=&quot;TextRun SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;hoc interpretation and selective instrumentation, which can make it difficult to fully understand execution flow and runtime behavior in a managed platform like Optimizely DXP.&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;EOP Selected SCXW248700128 BCX8&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;OutlineElement Ltr SCXW248700128 BCX8&quot;&gt;
&lt;p class=&quot;Paragraph SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;TextRun SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;This led to a question:&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p class=&quot;Paragraph SCXW248700128 BCX8&quot; style=&quot;text-align: left; padding-left: 20pt;&quot;&gt;&lt;span class=&quot;TextRun SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;&lt;strong&gt;Can you remotely debug an application running in Optimizely DXP?&lt;/strong&gt;&lt;/span&gt;&lt;/span&gt;&lt;strong&gt;&lt;span class=&quot;EOP Selected SCXW248700128 BCX8&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p class=&quot;Paragraph SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;TextRun SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;After going through official documentation, community discussions, and existing guides, I realized that the answer&amp;nbsp;&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;isn&amp;rsquo;t&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;&amp;nbsp;clearly documented. And&amp;nbsp;&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;that&amp;rsquo;s&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;&amp;nbsp;not accidental.&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;EOP Selected SCXW248700128 BCX8&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;OutlineElement Ltr SCXW248700128 BCX8&quot;&gt;
&lt;p class=&quot;Paragraph SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;TextRun SCXW248700128 BCX8&quot;&gt;&lt;strong&gt;&lt;span class=&quot;NormalTextRun ContextualSpellingAndGrammarErrorV2Themed SCXW248700128 BCX8&quot;&gt;TL;DR&lt;/span&gt;&lt;/strong&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;&lt;strong&gt;:&lt;/strong&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;TextRun SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;Based on this investigation, true remote debugging inside Optimizely DXP does not appear to be a supported workflow&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;TextRun SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;TextRun SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;&amp;nbsp;This aligns with the nature of DXP as a managed Platform-as-a-Service (PaaS), where direct access to infrastructure and debugging capabilities is limited.&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;EOP Selected SCXW248700128 BCX8&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;OutlineElement Ltr SCXW248700128 BCX8&quot;&gt;
&lt;p class=&quot;Paragraph SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;TextRun SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;Still, the question&amp;nbsp;&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;remains&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;&amp;nbsp;relevant. For developers working with complex behavior,&amp;nbsp;&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;it&amp;rsquo;s&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;&amp;nbsp;useful to understand&amp;nbsp;&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;the boundaries&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;.&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;TextRun SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;To get a clear picture, I approached this as a structured exploration. Instead of focusing on DXP directly, the investigation starts from the underlying platform it builds upon &amp;ndash; Azure App Services (Linux) &amp;ndash; and moves upward from there. This makes it possible to map what is available, how it behaves, and how those capabilities surface within a DXP environment&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;OutlineElement Ltr SCXW248700128 BCX8&quot;&gt;&lt;hr /&gt;
&lt;h3 class=&quot;Paragraph SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;TextRun SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;Why Remote Debugging Matters &amp;ndash; and Why&amp;nbsp;&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;It&amp;rsquo;s&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt; So Hard in DXP&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;/div&gt;
&lt;div class=&quot;OutlineElement Ltr SCXW248700128 BCX8&quot;&gt;
&lt;p class=&quot;Paragraph SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;TextRun SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;Remote debugging is one of the most powerful &amp;ndash; yet often overlooked &amp;ndash; tools in a developer&amp;rsquo;s toolkit. Being able to attach a debugger to a live environment allows you to inspect variables, step through code paths, and diagnose elusive bugs that only manifest in production-like conditions. In traditional Azure App Service setups, remote debugging with tools like &lt;strong&gt;Visual Studio&lt;/strong&gt;&amp;nbsp;&lt;/span&gt;&lt;span class=&quot;NormalTextRun ContextualSpellingAndGrammarErrorV2Themed SCXW248700128 BCX8&quot;&gt;or&amp;nbsp;&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;&lt;strong&gt;JetBrains Rider&lt;/strong&gt;&amp;nbsp;is not only possible but well-documented.&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;EOP Selected SCXW248700128 BCX8&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;OutlineElement Ltr SCXW248700128 BCX8&quot;&gt;
&lt;p class=&quot;Paragraph SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;TextRun SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;NormalTextRun ContextualSpellingAndGrammarErrorV2Themed SCXW248700128 BCX8&quot;&gt;Optimizely&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;&amp;nbsp;DXP is a managed Platform-as-a-Service (PaaS) which prioritizes multiple layers of abstraction and a strong emphasis on security, compliance, and performance over infrastructure access.&amp;nbsp;&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;In practice, DXP limits direct access to underlying app services, VM internals, and debugging endpoints.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p class=&quot;Paragraph SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;TextRun SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;So what options are actually available &amp;ndash; and how far can you realistically go with them?&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;EOP Selected SCXW248700128 BCX8&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;hr /&gt;
&lt;h3 class=&quot;OutlineElement Ltr SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;TextRun SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;Remote Debugging Options &amp;ndash; Azure Web App (Linux) + .NET&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;div class=&quot;OutlineElement Ltr SCXW248700128 BCX8&quot;&gt;
&lt;p class=&quot;Paragraph SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;TextRun SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;The Optimizely ecosystem runs on Linux containers, which means that tools like &lt;/span&gt;&lt;a title=&quot;Official Microsoft Documentation&quot; href=&quot;https://learn.microsoft.com/en-us/visualstudio/debugger/debug-live-azure-applications?view=vs-2022&quot;&gt;Snapshot Debugger&lt;/a&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;&amp;nbsp;or&amp;nbsp;&lt;a title=&quot;Official Microsoft Documentation&quot; href=&quot;https://learn.microsoft.com/en-us/visualstudio/debugger/remote-debugging-azure-app-service?view=vs-2022&quot;&gt;Remote debugging on Azure App Services (Windows)&lt;/a&gt;&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt; are simply not available. The alternative&amp;nbsp;&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;we&#39;re&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt; left with is an &lt;a title=&quot;Official Microsoft Documentation&quot; href=&quot;https://learn.microsoft.com/en-us/visualstudio/debugger/remote-debugging-dotnet-core-linux-with-ssh?view=vs-2022&quot;&gt;SSH debugger&lt;/a&gt;&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;. The referenced article explains how it works &amp;ndash; but since we aimed to explore every&amp;nbsp;&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;viable&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;option&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;,&amp;nbsp;&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;it&amp;rsquo;s&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;&amp;nbsp;worth noting that both JetBrains Rider and Visual Studio support SSH-based debugging. They use slightly different configurations, but the idea is the same &amp;ndash;&amp;nbsp;&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;establish&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;&amp;nbsp;a tunnel over a specific&amp;nbsp;&lt;/span&gt;&lt;span class=&quot;NormalTextRun ContextualSpellingAndGrammarErrorV2Themed SCXW248700128 BCX8&quot;&gt;port, and&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt; communicate bidirectionally about the execution process. Ideally, the deployed application should be built in Debug configuration (&lt;em&gt;&quot;&lt;/em&gt;&lt;/span&gt;&lt;/span&gt;&lt;em&gt;dotnet build --configuration Debug&quot;&lt;/em&gt;&lt;span class=&quot;TextRun SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt; and &quot;&lt;/span&gt;&lt;/span&gt;&lt;em&gt;dotnet publish --configuration Debug&quot;&lt;/em&gt;&lt;span class=&quot;TextRun SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;), so that symbol files &lt;em&gt;(&quot;&lt;/em&gt;&lt;/span&gt;&lt;/span&gt;&lt;em&gt;.pdb&quot;&lt;/em&gt;&lt;span class=&quot;TextRun SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;) are included &amp;ndash; enabling the debugger to accurately map execution to source code.&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;EOP Selected SCXW248700128 BCX8&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;OutlineElement Ltr SCXW248700128 BCX8&quot;&gt;
&lt;p class=&quot;Paragraph SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;TextRun SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;As an Optimizely Power User, you typically have access to three environments via the Azure Portal: Integration, Preproduction, and Production (sometimes also ADE1).&amp;nbsp;&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;In my case, only the Integration environment exposed enough of the Web App&amp;rsquo;s configuration to investigate this directly. Since Preproduction and Production did not expose the Web App resource without Optimizely Support involvement, all debugging attempts focused on the Integration environment.&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;EOP Selected SCXW248700128 BCX8&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;OutlineElement Ltr SCXW248700128 BCX8&quot;&gt;
&lt;p class=&quot;Paragraph SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;TextRun SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;While &lt;a title=&quot;Official Microsoft Documentation&quot; href=&quot;https://learn.microsoft.com/en-us/azure/app-service/resources-kudu&quot;&gt;Kudu&lt;/a&gt; allows basic SSH access to the instance, enabling remote debugging requires &lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;establishing&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;&amp;nbsp;an SSH tunnel from the local environment. Ideally, the tunnel should be managed by Azure and connect directly to the Optimizely DXP environment &amp;ndash; avoiding&amp;nbsp;&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;firewall&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt; issues or potential security risks. Fortunately, Azure provides an out-of-the-box solution for this: &lt;a title=&quot;Official Microsoft Documentation&quot; href=&quot;https://learn.microsoft.com/en-us/azure/app-service/configure-linux-open-ssh-session?pivots=container-linux&quot;&gt;Open as SSH session to a container in Azure App&amp;nbsp;Service&lt;/a&gt;&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;. The documentation clearly outlines the necessary steps. Just ensure the &lt;a title=&quot;Official Microsoft Documentation&quot; href=&quot;https://learn.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest&quot;&gt;Azure CLI is&amp;nbsp;installed&lt;/a&gt; &lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;and that&amp;nbsp;&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;you&#39;ve&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt; run &quot;&lt;/span&gt;&lt;/span&gt;&lt;em&gt;az login&quot;&lt;/em&gt;&lt;span class=&quot;TextRun SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt; beforehand. Once you run &quot;&lt;/span&gt;&lt;/span&gt;&lt;em&gt;az webapp create-remote-connection --subscription &amp;lt;subscription-id&amp;gt; --resource-group &amp;lt;resource-group-name&amp;gt; -n &amp;lt;app-name&amp;gt;&quot;&lt;/em&gt;&lt;span class=&quot;TextRun SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;, the SSH tunnel to your instance will be active. Then, using your preferred SSH client, you can connect and gain shell access to the server &amp;ndash; just as described in the documentation.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;OutlineElement Ltr SCXW248700128 BCX8&quot;&gt;&lt;hr /&gt;&lt;/div&gt;
&lt;div class=&quot;OutlineElement Ltr SCXW248700128 BCX8&quot;&gt;
&lt;h3 class=&quot;Paragraph SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;TextRun SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;Where the approach started to break down&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;EOP Selected SCXW248700128 BCX8&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/h3&gt;
&lt;/div&gt;
&lt;div class=&quot;OutlineElement Ltr SCXW248700128 BCX8&quot;&gt;
&lt;p class=&quot;Paragraph SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;TextRun SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;Encouraged by the successful SSH connection, I opened JetBrains Rider and&amp;nbsp;&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;attempted&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;&amp;nbsp;to &amp;ldquo;Attach to Remote Process&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;&amp;rdquo;.&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;&amp;nbsp;After entering the local port of the SSH tunnel, I was prompted &amp;ndash; as expected &amp;ndash; with a message like &amp;ldquo;Your server&amp;nbsp;&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;doesn&amp;rsquo;t&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;&amp;nbsp;have the necessary files for debugging. Would you like to install them&lt;/span&gt;&lt;span class=&quot;NormalTextRun ContextualSpellingAndGrammarErrorV2Themed SCXW248700128 BCX8&quot;&gt;?&amp;rdquo;.&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;&amp;nbsp;I clicked &amp;ldquo;Yes&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;&amp;rdquo;.&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;&amp;nbsp;The debugger tools downloaded successfully &amp;ndash; but the installation failed after just 1-2 MB were transferred to the server. No clear exception was shown; Rider simply displayed the same prompt again. Multiple retries led nowhere, so I started investigating the issue more closely. It turned out Rider was&amp;nbsp;&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;attempting&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt; to download and install the debugging tools by copying them over the SSH tunnel. The Kudu logs referenced a &quot;&lt;/span&gt;&lt;/span&gt;&lt;em&gt;WebSocketException&quot;&amp;nbsp;&lt;/em&gt;&lt;span class=&quot;TextRun SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;(&lt;em&gt;The remote party closed the WebSocket connection without completing the close &lt;/em&gt;&lt;/span&gt;&lt;span class=&quot;NormalTextRun ContextualSpellingAndGrammarErrorV2Themed SCXW248700128 BCX8&quot;&gt;&lt;em&gt;handshake.&lt;/em&gt;&lt;/span&gt;&lt;span class=&quot;NormalTextRun ContextualSpellingAndGrammarErrorV2Themed SCXW248700128 BCX8&quot;&gt;), but&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt; provided little else of value. I did confirm that Rider was uploading ZIP archives to the expected directory (&lt;em&gt;&quot;&lt;/em&gt;&lt;/span&gt;&lt;/span&gt;&lt;em&gt;~/.local/share/JetBrains/RiderRemoteDebugger/...&quot;&lt;/em&gt;&lt;span class=&quot;TextRun SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;), but only partially &amp;ndash; the file sizes were well below the 100MB+ expected for full debugger tools. Naturally, trying to unzip a partially downloaded archive failed. At that point, it became clear we had two options: either upload the debugger archive via &lt;em&gt;&quot;&lt;/em&gt;&lt;/span&gt;&lt;/span&gt;&lt;em&gt;scp&quot;&lt;/em&gt;&lt;span class=&quot;TextRun SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;NormalTextRun ContextualSpellingAndGrammarErrorV2Themed SCXW248700128 BCX8&quot;&gt;, or&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt; download and install it directly from within the instance. Using &lt;em&gt;&quot;&lt;/em&gt;&lt;/span&gt;&lt;/span&gt;&lt;em&gt;wget&quot;&lt;/em&gt;&amp;nbsp;&lt;span class=&quot;TextRun SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;was straightforward &amp;ndash; and it worked without issues.&amp;nbsp;&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;I installed the Linux debugger tools matching the container architecture&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;&amp;nbsp;(d&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;etails&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;&amp;nbsp;&lt;a title=&quot;Official JetBrains Documentation&quot; href=&quot;https://www.jetbrains.com/help/rider/SSH_Remote_Debugging.html#-q06re1_143&quot;&gt;here&lt;/a&gt;) into the same directory where Rider had previously failed. The exact path may vary by Rider version, but&amp;nbsp;&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;it&#39;s&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt; typically something like &lt;em&gt;&quot;&lt;/em&gt;&lt;/span&gt;&lt;/span&gt;&lt;em&gt;~/.local/share/JetBrains/RiderRemoteDebugger/...&quot;&lt;/em&gt;&lt;span class=&quot;TextRun SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;. After installing &lt;em&gt;&quot;&lt;/em&gt;&lt;/span&gt;&lt;/span&gt;&lt;em&gt;unzip&quot;&lt;/em&gt; &lt;span class=&quot;TextRun SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;(&lt;em&gt;&quot;&lt;/em&gt;&lt;/span&gt;&lt;/span&gt;&lt;em&gt;apt-get update &amp;amp;&amp;amp; apt-get install -y unzip&quot;&lt;/em&gt;&lt;span class=&quot;TextRun SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;) and extracting the archive, I reached the&amp;nbsp;&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;final step&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;: Rider was now able to list the running processes on the instance &amp;ndash; including my active .NET process.&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;EOP Selected SCXW248700128 BCX8&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;OutlineElement Ltr SCXW248700128 BCX8&quot;&gt;&lt;hr /&gt;
&lt;h3 class=&quot;Paragraph SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;TextRun SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;Next attempt &amp;ndash; a&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;nd&amp;nbsp;&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;one more&amp;nbsp;&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;discovery&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;EOP Selected SCXW248700128 BCX8&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/h3&gt;
&lt;/div&gt;
&lt;div class=&quot;OutlineElement Ltr SCXW248700128 BCX8&quot;&gt;
&lt;p class=&quot;Paragraph SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;TextRun SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;I clicked &lt;strong&gt;Attach&lt;/strong&gt;&amp;nbsp;again, however the same pattern reappeared: the connection was briefly&amp;nbsp;&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;established&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;, then&amp;nbsp;&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;immediately&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt; closed. Kudu logs once again showed a &lt;em&gt;&quot;&lt;/em&gt;&lt;/span&gt;&lt;/span&gt;&lt;em&gt;WebSocketException&quot;&lt;/em&gt;&lt;span class=&quot;TextRun SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;, prompting me to investigate potential issues with the SSH connection itself. I decided to retry the file upload using &lt;em&gt;&quot;&lt;/em&gt;&lt;/span&gt;&lt;/span&gt;&lt;em&gt;scp&quot;&lt;/em&gt;&amp;nbsp;&lt;span class=&quot;TextRun SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;myself &amp;ndash; this time with verbose logging &amp;ndash; to better understand what was happening. I started small: uploading an empty &lt;em&gt;&quot;&lt;/em&gt;&lt;/span&gt;&lt;/span&gt;&lt;em&gt;test.txt&quot;&lt;/em&gt;&lt;span class=&quot;TextRun SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt; worked fine. A slightly larger file with a few lines of text &amp;ndash; also successful. But when &lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;attempting&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;&amp;nbsp;to upload a larger file, I&amp;nbsp;&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;encountered&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt; a familiar error: &lt;strong&gt;broken pipe&lt;/strong&gt;. This pointed toward a &lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;likely culprit&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;: some form of bandwidth limitation.&amp;nbsp;&lt;/span&gt;&lt;span class=&quot;NormalTextRun ContextualSpellingAndGrammarErrorV2Themed SCXW248700128 BCX8&quot;&gt;So&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt; I tried uploading the file with bandwidth throttling enabled:&lt;em&gt; &quot;&lt;/em&gt;&lt;/span&gt;&lt;/span&gt;&lt;em&gt;scp&amp;nbsp;-vvv&amp;nbsp;-o MACs=hmac-sha1 -P &amp;lt;port&amp;gt; -l 8192 &quot;debugger-file.zip&quot;&amp;nbsp;root@127.0.0.1:~/.local/share/JetBrains/RiderRemoteDebugger/...&quot;&amp;nbsp;&lt;/em&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;OutlineElement Ltr SCXW248700128 BCX8&quot;&gt;
&lt;p class=&quot;Paragraph SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;TextRun SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;That worked. My colleague and I reproduced the behavior on multiple machines, which made a purely client-side explanation less likely.&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;&amp;nbsp;The remaining question was: how can we enforce the same throttling for JetBrains Rider&amp;rsquo;s automatic upload process?&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;EOP Selected SCXW248700128 BCX8&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;OutlineElement Ltr SCXW248700128 BCX8&quot;&gt;
&lt;p class=&quot;Paragraph SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;TextRun SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;And&amp;nbsp;&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;here&amp;rsquo;s&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;&amp;nbsp;the tricky part: the SSH connection is managed by Azure, leaving little room for server-side customization. I briefly considered&amp;nbsp;&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;terminating&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;&amp;nbsp;the existing SSH server and launching a custom one with modified settings &amp;ndash; but OpenSSH&amp;nbsp;&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;doesn&amp;rsquo;t&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;&amp;nbsp;offer the necessary configuration flexibility, and interfering with the managed Azure/Optimizely infrastructure&amp;nbsp;&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;wasn&amp;rsquo;t&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;a viable&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;option&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;. My next attempt involved using &lt;em&gt;&quot;&lt;/em&gt;&lt;/span&gt;&lt;/span&gt;&lt;em&gt;tc&quot;&lt;/em&gt;&amp;nbsp;&lt;span class=&quot;TextRun SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;(traffic control) on the server to limit bandwidth, matching the constraint&amp;nbsp;&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;I&amp;rsquo;d&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt; used with &lt;em&gt;&quot;&lt;/em&gt;&lt;/span&gt;&lt;/span&gt;&lt;em&gt;scp&quot;&lt;/em&gt;&lt;span class=&quot;TextRun SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;. However, even the&amp;nbsp;&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;initial&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt; setup command &amp;ndash; &lt;em&gt;&quot;&lt;/em&gt;&lt;/span&gt;&lt;/span&gt;&lt;em&gt;tc&amp;nbsp;qdisc&amp;nbsp;add dev $IFACE root handle 1:&amp;nbsp;htb default 30&quot;&lt;/em&gt;&lt;span class=&quot;TextRun SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt; &amp;ndash; failed with a &lt;strong&gt;Permission &lt;/strong&gt;&lt;/span&gt;&lt;span class=&quot;NormalTextRun ContextualSpellingAndGrammarErrorV2Themed SCXW248700128 BCX8&quot;&gt;&lt;strong&gt;denied&lt;/strong&gt;&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt; error. A quick check using &lt;em&gt;&quot;&lt;/em&gt;&lt;/span&gt;&lt;/span&gt;&lt;em&gt;capsh&amp;nbsp;--print | grep&amp;nbsp;cap_net_admin&quot;&lt;/em&gt;&lt;span class=&quot;TextRun SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;&amp;nbsp;confirmed it: I&amp;nbsp;&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;didn&amp;rsquo;t&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;&amp;nbsp;have the required capabilities &amp;ndash; and that was the end of that path.&amp;nbsp;&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;Realizing that tools like trickle or other ad hoc bandwidth limiters would&amp;nbsp;&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;likely interfere&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;&amp;nbsp;with the existing setup, I shifted focus&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;&amp;nbsp;to throttling the connection from the client side. On Windows, I experimented with built-in QoS policies to throttle traffic &amp;ndash; but it&amp;nbsp;&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;didn&amp;rsquo;t&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;&amp;nbsp;help. Rider continued to disconnect shortly after starting the debugger, and the issue&amp;nbsp;&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;remained&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;EOP Selected SCXW248700128 BCX8&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;OutlineElement Ltr SCXW248700128 BCX8&quot;&gt;&lt;hr /&gt;&lt;/div&gt;
&lt;div class=&quot;OutlineElement Ltr SCXW248700128 BCX8&quot;&gt;
&lt;h3 class=&quot;Paragraph SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;TextRun SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;Eliminating&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;&amp;nbsp;concerns, or what else was tried&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;EOP Selected SCXW248700128 BCX8&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/h3&gt;
&lt;/div&gt;
&lt;div class=&quot;OutlineElement Ltr SCXW248700128 BCX8&quot;&gt;
&lt;p class=&quot;Paragraph SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;TextRun SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;1.&amp;nbsp;&lt;strong&gt;SFTP&lt;/strong&gt;. Some resources mention SFTP as a requirement for remote debugging, raising concerns about whether&amp;nbsp;&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;it&amp;rsquo;s&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt; enabled in the Optimizely setup. It is. You can confirm this either in the Azure Portal or by running &lt;em&gt;&quot;&lt;/em&gt;&lt;/span&gt;&lt;/span&gt;&lt;em&gt;grep sftp /etc/ssh/sshd_config&quot;&lt;/em&gt;&lt;span class=&quot;TextRun SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;. Any output&amp;nbsp;&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;indicates&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;it&amp;rsquo;s&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;&amp;nbsp;active &amp;ndash; and&amp;nbsp;&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;that&amp;rsquo;s&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;&amp;nbsp;sufficient.&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;EOP Selected SCXW248700128 BCX8&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;OutlineElement Ltr SCXW248700128 BCX8&quot;&gt;
&lt;p class=&quot;Paragraph SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;TextRun SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;2. &lt;strong&gt;Release vs. Debug&amp;nbsp;&lt;/strong&gt;&lt;/span&gt;&lt;span class=&quot;NormalTextRun ContextualSpellingAndGrammarErrorV2Themed SCXW248700128 BCX8&quot;&gt;&lt;strong&gt;configurations.&lt;/strong&gt;&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;&amp;nbsp;I tested both build modes to rule out any mismatch &amp;ndash; same result in both cases. The issues persisted regardless of configuration.&amp;nbsp;&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;That may have been the next problem if the SSH issue had been resolved, but I did not get far enough to prove it&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;EOP Selected SCXW248700128 BCX8&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;OutlineElement Ltr SCXW248700128 BCX8&quot;&gt;
&lt;p class=&quot;Paragraph SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;TextRun SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;3. &lt;strong&gt;Azure&amp;rsquo;s &amp;ldquo;Remote Debugging Enabled&amp;rdquo;&amp;nbsp;&lt;/strong&gt;&lt;/span&gt;&lt;span class=&quot;NormalTextRun ContextualSpellingAndGrammarErrorV2Themed SCXW248700128 BCX8&quot;&gt;&lt;strong&gt;flag.&lt;/strong&gt;&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;I did not find this useful for the .NET on Linux scenario tested here&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;&amp;nbsp;In this setup,&amp;nbsp;&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;it was not applicable here and also interfered with SSH access, as both rely on port&amp;nbsp;&lt;/span&gt;&lt;span class=&quot;NormalTextRun ContextualSpellingAndGrammarErrorV2Themed SCXW248700128 BCX8&quot;&gt;2222&lt;/span&gt;&lt;span class=&quot;NormalTextRun ContextualSpellingAndGrammarErrorV2Themed SCXW248700128 BCX8&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt; Still, if you want to experiment, you can toggle it using the following commands: &lt;em&gt;&quot;&lt;/em&gt;&lt;/span&gt;&lt;/span&gt;&lt;em&gt;az webapp config set --resource-group &amp;lt;resource-group&amp;gt; -n &amp;lt;webapp-name&amp;gt; --remote-debugging-enabled=true&quot; &lt;/em&gt;&lt;span class=&quot;TextRun SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;and to roll-back&lt;em&gt; &quot;&lt;/em&gt;&lt;/span&gt;&lt;/span&gt;&lt;em&gt;az webapp config set --resource-group &amp;lt;resource-group&amp;gt; -n &amp;lt;webapp-name&amp;gt; --remote-debugging-enabled=false&quot;&lt;/em&gt;&lt;span class=&quot;TextRun SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;. Even if not useful for this scenario, the output of these commands is worth exploring &amp;ndash; Azure exposes a surprisingly rich ARM definition of your Web App.&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;EOP Selected SCXW248700128 BCX8&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;OutlineElement Ltr SCXW248700128 BCX8&quot;&gt;
&lt;p class=&quot;Paragraph SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;TextRun SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;4. &lt;strong&gt;What about Visual&amp;nbsp;&lt;/strong&gt;&lt;/span&gt;&lt;span class=&quot;NormalTextRun ContextualSpellingAndGrammarErrorV2Themed SCXW248700128 BCX8&quot;&gt;&lt;strong&gt;Studio?&lt;/strong&gt;&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;&amp;nbsp;As expected, I&amp;nbsp;&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;gave it a try&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;&amp;nbsp;&amp;ndash; but the results were even worse. Visual Studio refused the SSH connection entirely and exited with the&amp;nbsp;&lt;/span&gt;&lt;span class=&quot;NormalTextRun ContextualSpellingAndGrammarErrorV2Themed SCXW248700128 BCX8&quot;&gt;message:&amp;nbsp;&lt;/span&gt;&lt;em&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;&quot;Connectivity Failure: Please make sure host name and port number are correct&lt;/span&gt;&lt;/em&gt;&lt;span class=&quot;NormalTextRun ContextualSpellingAndGrammarErrorV2Themed SCXW248700128 BCX8&quot;&gt;&lt;em&gt;.&quot;&lt;/em&gt;&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;&amp;nbsp;No&amp;nbsp;&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;additional&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;&amp;nbsp;context was provided, leaving me guessing what might&amp;rsquo;ve gone wrong&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;.&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;Throughout this process, I&amp;nbsp;&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;monitored&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;&amp;nbsp;Kudu logs in parallel. While some error entries did appear, they&amp;nbsp;&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;weren&amp;rsquo;t&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt; particularly helpful &amp;ndash; mostly the same &lt;em&gt;&quot;&lt;/em&gt;&lt;/span&gt;&lt;/span&gt;&lt;em&gt;WebSocketException&quot;&amp;nbsp;&lt;/em&gt;&lt;span class=&quot;TextRun SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;patterns I saw when using Rider. One thing was clear: Visual Studio recognized the SSH tunnel. When another connection was already active, it would wait and only fail once the other connection was released &amp;ndash; confirming awareness of the underlying channel, even if it&amp;nbsp;&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;couldn&amp;rsquo;t&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;&amp;nbsp;use it properly.&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;EOP SCXW248700128 BCX8&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;OutlineElement Ltr SCXW248700128 BCX8&quot;&gt;
&lt;p class=&quot;Paragraph SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;TextRun SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;5. &lt;strong&gt;Limit bandwidth from Rider&amp;nbsp;&lt;/strong&gt;&lt;/span&gt;&lt;span class=&quot;NormalTextRun ContextualSpellingAndGrammarErrorV2Themed SCXW248700128 BCX8&quot;&gt;&lt;strong&gt;directly?&lt;/strong&gt;&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;&amp;nbsp;I checked&amp;nbsp;&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;Rider&amp;rsquo;s&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;&amp;nbsp;available&amp;nbsp;&lt;/span&gt;&lt;span class=&quot;NormalTextRun ContextualSpellingAndGrammarErrorV2Themed SCXW248700128 BCX8&quot;&gt;settings, but&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;&amp;nbsp;found no built-in way to throttle the SSH transfer rate.&amp;nbsp;&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;It&amp;rsquo;s&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;&amp;nbsp;possible that using a proxy or a more advanced traffic-shaping setup could help &amp;ndash; but investigating that path would take&amp;nbsp;&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;considerably more&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;&amp;nbsp;time, with no guaranteed payoff. At this point, I made&amp;nbsp;&lt;/span&gt;&lt;span class=&quot;NormalTextRun ContextualSpellingAndGrammarErrorV2Themed SCXW248700128 BCX8&quot;&gt;the&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;&amp;nbsp;call to stop here.&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt; Remaining options required more time with no clear path to a reliable result.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;hr /&gt;
&lt;h3 class=&quot;OutlineElement Ltr SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;TextRun SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;Final Observations&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;div class=&quot;OutlineElement Ltr SCXW248700128 BCX8&quot;&gt;
&lt;p class=&quot;Paragraph SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;TextRun SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;I&amp;nbsp;&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;attempted&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;&amp;nbsp;to connect to the instance&amp;nbsp;&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;overnight, when&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;&amp;nbsp;I expected activity in Integration to be minimal.&amp;nbsp;&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;Interestingly, the debugger connection held for nearly 10 seconds, longer than any of the daytime attempts, before ultimately failing again.&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;&amp;nbsp;Bandwidth-related limits or instability seem like the most plausible explanation from these tests, though I could not prove that definitively or&amp;nbsp;&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;rule out other&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;&amp;nbsp;blockers&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;EOP Selected SCXW248700128 BCX8&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;OutlineElement Ltr SCXW248700128 BCX8&quot;&gt;
&lt;p class=&quot;Paragraph SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;TextRun SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;At this point,&amp;nbsp;&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;it&amp;rsquo;s&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;&amp;nbsp;worth stepping back and looking at the outcome&amp;nbsp;&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;more practically&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;EOP Selected SCXW248700128 BCX8&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;OutlineElement Ltr SCXW248700128 BCX8&quot;&gt;
&lt;p class=&quot;Paragraph SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;TextRun SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;NormalTextRun ContextualSpellingAndGrammarErrorV2Themed SCXW248700128 BCX8&quot;&gt;Optimizely&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;&amp;nbsp;DXP is a managed platform, and remote debugging is not a workflow it currently exposes or documents directly&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;. That makes the result expected &amp;ndash; but the path to that understanding is not always obvious when you start.&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;TextRun SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;For developers working with complex systems, the question itself still comes up. This exploration&amp;nbsp;&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;doesn&amp;rsquo;t&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;&amp;nbsp;change the&amp;nbsp;&lt;/span&gt;&lt;span class=&quot;NormalTextRun ContextualSpellingAndGrammarErrorV2Themed SCXW248700128 BCX8&quot;&gt;platform&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;&amp;nbsp;behavior, but it does make the current boundaries clearer: what can be&amp;nbsp;&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;attempted&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;, what partially works, and where things start to break down in practice.&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;EOP Selected SCXW248700128 BCX8&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;OutlineElement Ltr SCXW248700128 BCX8&quot;&gt;
&lt;p class=&quot;Paragraph SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;TextRun SCXW248700128 BCX8&quot;&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;If nothing else, this should save time for anyone approaching the same&amp;nbsp;&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;idea&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt; &amp;ndash; and provide a more concrete starting point&lt;/span&gt;&lt;span class=&quot;NormalTextRun ContextualSpellingAndGrammarErrorV2Themed SCXW248700128 BCX8&quot;&gt;, should&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;&amp;nbsp;you decide to take it&amp;nbsp;&lt;/span&gt;&lt;span class=&quot;NormalTextRun SCXW248700128 BCX8&quot;&gt;further.&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;EOP Selected SCXW248700128 BCX8&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;</description>            <guid>https://world.optimizely.com/blogs/mike/dates/2026/4/remote-debugging-in-optimizely-dxp-what-is-actually-possible-/</guid>            <pubDate>Mon, 20 Apr 2026 08:45:18 GMT</pubDate>           <category>Blog post</category></item></channel>
</rss>