Performans ayarlama - Olay akışı

Azure Functions
Azure IoT Hub
Azure Cosmos DB

Bu makalede, bir geliştirme ekibinin performans sorunlarını bulmak ve dağıtılmış bir sistemin performansını geliştirmek için ölçümleri nasıl kullandığı açıklanmaktadır. Makale, örnek bir uygulama için yaptığımız gerçek yük testini temel alır.

Bu makale, bir serinin bir parçasıdır. Buradaki ilk bölümü okuyun.

Senaryo: Azure İşlevleri kullanarak olay akışını işleme.

Olay akış mimarisinin diyagramı

Bu senaryoda, bir insansız hava aracı filosu konum verilerini gerçek zamanlı olarak Azure IoT Hub gönderir. İşlevler uygulaması olayları alır, verileri GeoJSON biçimine dönüştürür ve dönüştürülen verileri Azure Cosmos DB'ye yazar. Azure Cosmos DB, jeo-uzamsal veriler için yerel desteğe sahiptir ve verimli uzamsal sorgular için Azure Cosmos DB koleksiyonları dizine eklenebilir. Örneğin, bir istemci uygulaması belirli bir konumun 1 km içindeki tüm insansız hava araçlarını sorgulayabilir veya belirli bir alandaki tüm insansız hava araçlarını bulabilir.

Bu işleme gereksinimleri, tam teşekküllü bir akış işleme altyapısı gerektirmeyen yeterince basittir. Özellikle işleme, zaman pencereleri arasında akışları birleştirmez, verileri toplamaz veya işlemez. Bu gereksinimlere bağlı olarak, Azure İşlevleri iletileri işlemek için uygundur. Azure Cosmos DB ayrıca çok yüksek yazma aktarım hızını destekleyecek şekilde ölçeklendirilebilir.

aktarım hızını izleme

Bu senaryo ilginç bir performans sınaması sunar. Cihaz başına veri hızı bilinir, ancak cihaz sayısı dalgalanabilir. Bu iş senaryosu için gecikme süresi gereksinimleri özellikle katı değildir. İnsansız hava aracının bildirilen konumunun yalnızca bir dakika içinde doğru olması gerekir. Bununla birlikte işlev uygulamasının zaman içindeki ortalama alım oranına ayak uydurması gerekir.

IoT Hub iletileri günlük akışında depolar. Gelen iletiler akışın kuyruğuna eklenir. Akışın okuyucusu (bu örnekte işlev uygulaması), akışta kendi dolaşma hızını denetler. Okuma ve yazma yollarının bu şekilde ayrılmış olması IoT Hub çok verimli hale getirir, ancak aynı zamanda yavaş bir okuyucunun geride bırakabileceği anlamına da gelir. Geliştirme ekibi bu koşulu algılamak için ileti gecikmesini ölçmek için özel bir ölçüm ekledi. Bu ölçüm, bir iletinin IoT Hub ulaşması ve işlevin işlenmek üzere iletiyi alması arasındaki deltayı kaydeder.

var ticksUTCNow = DateTimeOffset.UtcNow;

// Track whether messages are arriving at the function late.
DateTime? firstMsgEnqueuedTicksUtc = messages[0]?.EnqueuedTimeUtc;
if (firstMsgEnqueuedTicksUtc.HasValue)
{
    CustomTelemetry.TrackMetric(
                        context,
                        "IoTHubMessagesReceivedFreshnessMsec",
                        (ticksUTCNow - firstMsgEnqueuedTicksUtc.Value).TotalMilliseconds);
}

yöntemi Application TrackMetric Insights'a özel bir ölçüm yazar. Azure İşlevi'nin içinde kullanma TrackMetric hakkında bilgi için bkz. C# işlevinde özel telemetri.

İşlev ileti hacmine uygunsa bu ölçüm düşük sabit bir durumda kalmalıdır. Bazı gecikme süreleri kaçınılmazdır, bu nedenle değer hiçbir zaman sıfır olmaz. Ancak işlev geride kalırsa, sıraya alınan süre ile işlem süresi arasındaki değişim yukarı doğru gitmeye başlar.

Test 1: Temel

İlk yük testi anında bir sorun olduğunu gösterdi: İşlev uygulaması tutarlı olarak Azure Cosmos DB'den HTTP 429 hataları aldı ve bu da Azure Cosmos DB'nin yazma isteklerini azalttığını gösteriyordu.

Azure Cosmos DB kısıtlanan isteklerin grafiği

Ekip yanıt olarak Azure Cosmos DB'yi koleksiyon için ayrılan RU sayısını artırarak ölçeklendirdi ancak hatalar devam etti. Zarf arkası hesaplamaları Azure Cosmos DB'nin yazma isteklerinin hacmini takip etmede sorun olmaması gerektiğini gösterdiğinden bu durum garip görünüyordu.

O günün ilerleyen günlerinde, geliştiricilerden biri takıma aşağıdaki e-postayı gönderdi:

Sıcak yol için Azure Cosmos DB'ye baktım. Anlamadığım bir şey var. Bölüm anahtarı deliveryId'dir ancak Azure Cosmos DB'ye deliveryId göndermeyiz. Bir şeyi mi kaçırıyorum?

İpucu da bu. Bölüm ısı haritasına baktığımızda, tüm belgelerin aynı bölüme giriş yaptığı ortaya çıktı.

Azure Cosmos DB bölüm ısı haritası grafiği

Isı haritasında görmek istediğiniz şey, tüm bölümler arasında eşit bir dağılımdır. Bu durumda, her belge aynı bölüme yazıldığı için RU eklemek işe yaramadı. Sorunun kodda bir hata olduğu ortaya çıktı. Azure Cosmos DB koleksiyonunda bölüm anahtarı olsa da, Azure İşlevi aslında bölüm anahtarını belgeye eklemedi. Bölüm ısı haritası hakkında daha fazla bilgi için bkz. Bölümler arasında aktarım hızı dağılımını belirleme.

Test 2: Bölümleme sorununu düzeltme

Ekip bir kod düzeltmesi dağıtıp testi yeniden çalıştırdığında Azure Cosmos DB azaltmayı durdurdu. Bir süre için, her şey güzel görünüyordu. Ancak belirli bir yükte telemetri işlevin yazması gereken daha az belge yazdığını gösterdi. Aşağıdaki grafikte, IoT Hub gelen iletiler ile Azure Cosmos DB'ye yazılan belgeler gösterilir. Sarı çizgi, toplu iş başına alınan ileti sayısıdır ve yeşil, toplu iş başına yazılan belge sayısıdır. Bunlar orantılı olmalıdır. Bunun yerine, toplu iş başına veritabanı yazma işlemlerinin sayısı yaklaşık 07:30'da önemli ölçüde düşer.

Bırakılan iletilerin grafiği

Sonraki grafikte, cihazdan bir iletinin IoT Hub ulaşması ve işlev uygulamasının bu iletiyi işlemesi arasındaki gecikme süresi gösterilir. Zaman içinde aynı noktada gecikmenin önemli ölçüde arttığını, düzeylerin azaldığını ve düşüşlerin arttığını görebilirsiniz.

İleti geçliği grafiği

Değerin 5 dakikada zirveye düşmesinin ve ardından sıfıra düşmesinin nedeni, işlev uygulamasının 5 dakikadan daha geç olan iletileri atmasındandır:

foreach (var message in messages)
{
    // Drop stale messages,
    if (message.EnqueuedTimeUtc < cutoffTime)
    {
        log.Info($"Dropping late message batch. Enqueued time = {message.EnqueuedTimeUtc}, Cutoff = {cutoffTime}");
        droppedMessages++;
        continue;
    }
}

Gecikme ölçümü sıfıra düştüğünde bunu grafikte görebilirsiniz. Bu arada, işlev iletileri attığı için veriler kayboldu.

Neler oluyor? Bu özel yük testi için Azure Cosmos DB koleksiyonunun ayıracak RU'ları olduğundan veritabanında performans sorunu yoktu. Bunun yerine, sorun ileti işleme döngüsündeydi. Basitçe ifade etmek gerekirse, işlev gelen ileti hacmini takip edecek kadar hızlı belge yazmıyordu. Zamanla, daha da geride kaldı.

Test 3: Paralel yazmalar

bir iletiyi işleme zamanı performans sorunuysa, çözümlerden biri daha fazla iletiyi paralel olarak işlemektir. Bu senaryoda:

  • IoT Hub bölüm sayısını artırın. Her IoT Hub bölüme bir kerede bir işlev örneği atanır, bu nedenle aktarım hızının bölüm sayısıyla doğrusal olarak ölçeklendirilmesini bekleriz.
  • İşlev içindeki belge yazmalarını paralel hale getir.

İkinci seçeneği keşfetmek için ekip, işlevi paralel yazmaları destekleyecek şekilde değiştirdi. İşlevin özgün sürümü Azure Cosmos DB çıkış bağlamasını kullandı. İyileştirilmiş sürüm Azure Cosmos DB istemcisini doğrudan çağırır ve Task.WhenAll kullanarak yazma işlemlerini paralel olarak gerçekleştirir:

private async Task<(long documentsUpserted,
                    long droppedMessages,
                    long cosmosDbTotalMilliseconds)>
                ProcessMessagesFromEventHub(
                    int taskCount,
                    int numberOfDocumentsToUpsertPerTask,
                    EventData[] messages,
                    TraceWriter log)
{
    DateTimeOffset cutoffTime = DateTimeOffset.UtcNow.AddMinutes(-5);

    var tasks = new List<Task>();

    for (var i = 0; i < taskCount; i++)
    {
        var docsToUpsert = messages
                            .Skip(i * numberOfDocumentsToUpsertPerTask)
                            .Take(numberOfDocumentsToUpsertPerTask);
        // client will attempt to create connections to the data
        // nodes on Azure Cosmos DB clusters on a range of port numbers
        tasks.Add(UpsertDocuments(i, docsToUpsert, cutoffTime, log));
    }

    await Task.WhenAll(tasks);

    return (this.UpsertedDocuments,
            this.DroppedMessages,
            this.CosmosDbTotalMilliseconds);
}

Yarış koşullarının yaklaşımla mümkün olduğunu unutmayın. Aynı insansız hava aracından gelen iki iletinin aynı ileti grubuna ulaştığını varsayalım. Bunları paralel olarak yazarak, önceki ileti sonraki iletinin üzerine yazabilir. Bu belirli senaryo için, uygulama zaman zaman ileti kaybetmeyi tolere edebilir. İnsansız hava araçları her 5 saniyede bir yeni konum verileri gönderdiğinden Azure Cosmos DB'deki veriler sürekli olarak güncelleştirilir. Ancak diğer senaryolarda iletilerin sırayla işlenmesi önemli olabilir.

Bu kod değişikliğini dağıttıktan sonra uygulama 32 bölümlü bir IoT Hub kullanarak 2500'den fazla istek/sn alabildi.

İstemci tarafında dikkat edilmesi gerekenler

Genel istemci deneyimi, sunucu tarafında agresif paralelleştirme ile azaltılabilir. Azure Cosmos DB kapsayıcısına ayrılan aktarım hızını artırmak için gereken istemci tarafı işlem kaynaklarını önemli ölçüde azaltan Azure Cosmos DB toplu yürütücü kitaplığını (bu uygulamada gösterilmez) kullanmayı göz önünde bulundurun. Toplu içeri aktarma API'sini kullanarak veri yazan tek bir iş parçacıklı uygulama, istemci makinesinin CPU'sunu doygun hale getirerek paralel olarak veri yazan çok iş parçacıklı bir uygulamaya kıyasla neredeyse on kat daha fazla yazma aktarım hızı elde eder.

Özet

Bu senaryo için aşağıdaki performans sorunları belirlendi:

  • Yazılmakta olan belgelerde eksik bölüm anahtarı değeri nedeniyle sık erişimli yazma bölümü.
  • IoT Hub bölüm başına seri olarak belge yazma.

Geliştirme ekibi bu sorunları tanılamak için aşağıdaki ölçümlere dayanır:

  • Azure Cosmos DB'de kısıtlanmış istekler.
  • Bölüm ısı haritası — Bölüm başına en fazla tüketilen RU sayısı.
  • Alınan iletiler ve oluşturulan belgeler.
  • İleti gecikmesi.

Sonraki adımlar

Performans kötü modellerini gözden geçirme