Kladde.Verlauf braucht eine Schnittstelle, die Termine schreibt und liest. Bevor Lektion 2.1 die erste Abfrage schreibt, steht der Client: die Klasse CosmosClient, die alle Verbindungen, Wiederholungen und Zwischenspeicher des SDK trägt. Fast alle Fehler im Betrieb, die „an Cosmos DB liegen", liegen an dessen Einrichtung.
Vorwissen. Verwaltete Identität und Key Vault stehen in K3, Rollen und Schlüsselfreiheit am Konto in Lektion 3.1 und (Schalter disableLocalAuth) in Lektion 1.1. Das Konto der Lektion hat F-01: cosmos-kladde, Datenbank kladde, Hauptregion germanywestcentral, kein Schlüssel.
Was ausgeführt wurde. Alle Programme dieser Lektion wurden mit Microsoft.Azure.Cosmos 3.64.0 (neueste stabile Fassung am Schreibtag), Newtonsoft.Json 13.0.4, Azure.Identity 1.21.0 und Microsoft.Extensions.DependencyInjection übersetzt und liefen ohne Konto: Der Client wird erzeugt, aber nie angesprochen. Nicht gegen Azure ausgeführt ist alles, was eine Antwort des Dienstes braucht (die Anmeldung, die Regionen, ein echtes 429).
Ein Client, ein Konto
Learn nennt es die erste Regel: ein CosmosClient je Konto für die Lebensdauer der Anwendung. Er holt beim Start Metadaten (Datenbanken, Container, Partitionen, Adressen) und hält Verbindungen offen; ein neuer Client je Anfrage wiederholt das jedes Mal. Dazu kommt die Grenze aus Lektion 1.1: Verwaltungsaufrufe teilen sich 240 RU/s je Konto und lassen sich nicht erhöhen. Aus demselben Grund holen Sie Namen von Datenbank und Container aus der Konfiguration und rufen CreateIfNotExists nur bei der Einrichtung auf.
Alle Mandanten (Büros) eines Kontos laufen über denselben Client, denn ein Mandant ist ein Wert des Partitionsschlüssels. Einen Client je Konto brauchen Sie nur, wenn jeder Mandant ein eigenes Konto hat; Learn rät dann, die Zahl der Clients je Rechner zu begrenzen. Alle Aufrufe sind asynchron; Blockieren mit .Result oder .Wait() führt laut Learn zu Hunger im Thread-Pool.
Gateway oder Direct
- Direct (Standard im .NET-SDK) spricht per TCP direkt mit den Replikaten. Das ist laut Learn die schnellere Wahl, braucht aber Ports im Bereich 10000 bis 20000 (bei privaten Endpunkten 0 bis 65535); sind sie zu, meldet der Client 503.
- Gateway spricht HTTPS über Port 443 mit einem Zwischenknoten, mit einem Sprung mehr. Er ist die Wahl hinter strengen Firewalls oder bei wenigen erlaubten Sockets, und er ist der Modus des Emulators (K3).
Der Lauf bestätigt den Standard: Ein frisch erzeugtes CosmosClientOptions meldet ConnectionMode: Direct, RequestTimeout: 00:00:06, ConsistencyLevel und MaxRetryAttemptsOnRateLimitedRequests als nicht gesetzt (null). null heißt hier: Es gilt der Standard im SDK, und der ist laut Learn 9 Wiederholungen, also bis zu 10 Versuche je Anfrage.
Regionen
Betreiben Sie die Anwendung in derselben Azure-Region wie das Konto. Mit mehreren Regionen im Konto setzen Sie ApplicationRegion (eine Region) oder ApplicationPreferredRegions (eine Liste in Reihenfolge). Nach Learn gehen bei einem Failover Schreibvorgänge an die aktuelle Schreibregion, Lesevorgänge an die erste erreichbare Region der Liste. F-01 nennt eine Hauptregion; die zweite Region im Beispiel unten (North Europe) ist eine Annahme der Übung. Die Namen sind die Konstanten der Klasse Regions (Regions.GermanyWestCentral ergab im Lauf den Text Germany West Central). Offen: welche Zweitregion die Kladde.Verlauf wirklich bekommt, entscheidet Lektion 3.2.
Wiederholungen und 429
Wer mehr verbraucht als bereitgestellt, bekommt 429 mit der Wartezeit im Kopf x-ms-retry-after-ms. Das SDK wartet diese Zeit und versucht es noch einmal; Learn nennt 9 Wiederholungen und höchstens 30 Sekunden Wartezeit insgesamt. Danach erst sieht Ihr Code den Fehler. Zwei Folgen:
- 429 im Portal bedeutet nicht Fehler in der Anwendung: Das SDK hat es meist abgefangen. Learn hält 1 bis 5 Prozent 429 bei vertretbarer Latenz für ein gesundes Zeichen, dass der Durchsatz ausgenutzt wird (bei gleichmäßig verteilten Partitionen).
- Das Ablesen der Wartezeit liegt im Fehlerobjekt:
CosmosException.RetryAfter. Wer nach dem Aufgeben des SDK weitermachen will, wartet diese Zeit.
Nicht wiederholen Sie, was sich nicht ändert: Ein 413 (Element größer als 2 MB, F-08) bleibt 413. Dieser Hilfscode wiederholt nur 429 und wurde gegen eine Attrappe geprüft:
public static async Task<T> WithRetryAfterAsync<T>(Func<Task<T>> call, int maxAttempts, TimeSpan fallback, Func<TimeSpan, Task> delay)
{
for (var attempt = 1; ; attempt++)
{
try { return await call(); }
catch (CosmosException ex) when (ex.StatusCode == HttpStatusCode.TooManyRequests && attempt < maxAttempts)
{
await delay(ex.RetryAfter ?? fallback);
}
}
}
Die Wartefunktion wird übergeben, damit der Test nicht wirklich wartet. Ein Aufruf, der zweimal mit 429 scheitert und beim dritten Mal gelingt, und einer, der mit 413 scheitert, ergaben:
result ok after 3 attempts, waited 250 ms, 250 ms
413 after 1 attempt(s)
Die 250 ms sind der fallback: Ein selbst gebautes CosmosException hat keinen Kopf mit RetryAfter. Bei einem echten 429 steht dort die Zeit des Dienstes (das ist nicht gegen Azure ausgeführt).
Zeitlimits: Eine Gesamtdauer je Aufruf bekommen Sie laut Learn nur mit RequestTimeout und einem CancellationToken zusammen.
Anmeldung mit Entra
Kein Schlüssel im Code oder in Einstellungen (F-01). Der Client nimmt statt des Schlüssels ein TokenCredential; DefaultAzureCredential probiert nacheinander mehrere Quellen, in Azure die verwaltete Identität (K3). Die Identität braucht eine Rolle auf der Datenebene des Kontos (eingebaut: Cosmos DB Built-in Data Reader und Data Contributor; Learn nennt zum Anlegen und Zuweisen Rechte auf sqlRoleDefinitions und sqlRoleAssignments). Lokal läuft die Entwicklung gegen den Emulator, der keine Anmeldung prüft, mit der Verbindung aus den User Secrets (K3, Lektion 3.6.4).
Registrierung mit Dependency Injection
public static IServiceCollection AddKladdeCosmos(this IServiceCollection services, CosmosSettings settings)
{
services.AddSingleton(provider =>
{
var options = new CosmosClientOptions
{
ConnectionMode = ConnectionMode.Direct,
ApplicationPreferredRegions = settings.PreferredRegions,
EnableContentResponseOnWrite = false,
UseSystemTextJsonSerializerWithOptions = new JsonSerializerOptions(JsonSerializerDefaults.Web),
MaxRetryAttemptsOnRateLimitedRequests = 9,
MaxRetryWaitTimeOnRateLimitedRequests = TimeSpan.FromSeconds(30),
};
return new CosmosClient(settings.Endpoint, provider.GetRequiredService<TokenCredential>(), options);
});
return services;
}
Was jede Zeile will: EnableContentResponseOnWrite = false spart bei vielen Schreibvorgängen Speicher und Netz, weil der Dienst das angelegte Element nicht zurückschickt (RU-Kopf und Diagnose bleiben; laut Learn entfällt auch die Antwort eines Triggers). Die beiden letzten Zeilen setzen die Standardwerte ausdrücklich; man setzt sie nur, um sie zu ändern. UseSystemTextJsonSerializerWithOptions lässt Ihre Typen mit System.Text.Json schreiben; mit den Web-Einstellungen wird Id zu id, was Cosmos DB verlangt.
Newtonsoft.Json bleibt Pflicht. Das SDK nutzt es intern für eigene Typen, auch wenn Sie System.Text.Json einstellen, und bricht den Build ab, wenn Sie es nicht selbst referenzieren. Learn empfiehlt Fassung 13.0.4 (ab SDK 3.54.0 mindestens 13.0.4) und warnt vor 10.x:
<PackageReference Include="Microsoft.Azure.Cosmos" Version="3.64.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
Der Test ohne Konto holt den Client zweimal aus dem Container und fragt die Einstellungen ab. Er ergab:
same instance twice: True
connection mode: Direct
preferred regions: Germany West Central, North Europe
429 retries / wait: 9 / 00:00:30
content on write: False
Diagnose
Jede Antwort und jedes CosmosException trägt Diagnostics: alle Versuche, Regionen, Wartezeiten dieses einen Aufrufs. Learn warnt, den Text zu parsen, weil sich sein Aufbau mit jeder Fassung ändert. Loggen Sie ihn als Ganzes, und nur für langsame Aufrufe:
if (diagnostics.GetClientElapsedTime() > threshold)
{
write($"{operation}: {diagnostics}");
}
Mit einer Attrappe von CosmosDiagnostics (eine Klasse, die GetClientElapsedTime und ToString überschreibt) wurden ein Aufruf mit 8 ms und einer mit 450 ms gegen die Schwelle 100 ms geprüft; die Zeile für den schnellen blieb aus:
log lines: 1: slow query: {"elapsedMs":450}
Übung 1
Teil A, ohne Konto. Legen Sie client.cs mit AddKladdeCosmos, Resilience und SlowCallLog an (Pakete in den #:package-Zeilen oben im Programm).
- Lassen Sie die Regionen nicht im Code stehen, sondern aus der Konfiguration kommen:
CosmosSettingsausIConfigurationlesen,EndpointundPreferredRegionsals Einstellungen. - Ergänzen Sie einen Test, der prüft, dass
WithRetryAfterAsyncnachmaxAttemptsaufgibt und den 429 weiterreicht. - Warum ist ein
new CosmosClient(...)in einer Methode, die je Anfrage läuft, gefährlich? Nennen Sie zwei Folgen mit Zahl.
Teil B, mit einem Testkonto (nicht gegen Azure ausgeführt): Ersetzen Sie Endpoint durch den Ihres Kontos, geben Sie sich selbst die Rolle Data Contributor und lesen Sie ein Element. Aufräumen: Rollenzuweisung entfernen.
So prüfen Sie es: Teil A endet mit Exit-Code 0; der zweite Test zeigt 429 nach der letzten Wiederholung.
Lösung 1
- Der Abschnitt
Cosmosinappsettings.jsonhältEndpoint(kein Schlüssel!) undPreferredRegionsals Liste;services.Configure<CosmosSettings>(configuration.GetSection("Cosmos"))und die Registrierung liestIOptions<CosmosSettings>. Regionen und Endpunkt unterscheiden sich je Umgebung, deshalb gehören sie nicht in den Code. - Der Test lässt
callimmer mit 429 scheitern und prüft, dass die Ausnahme nachmaxAttemptsAufrufen ausWithRetryAfterAsyncherauskommt undattempts == maxAttemptsgilt. Der Rahmen steht im Code oben:catch (CosmosException ex) when (… && attempt < maxAttempts)lässt den letzten Versuch durch. - Zum Beispiel: Jeder neue Client holt die Metadaten neu, und Verwaltungsaufrufe teilen sich 240 RU/s je Konto; zudem öffnet jeder Client eigene Verbindungen (laut Learn im Mittel vier je physischer Partition), was Ports und Speicher füllt.
Teil B bleibt ungeprüft.
Was Sie jetzt können
- Sie setzen einen
CosmosClientje Konto als Singleton auf und registrieren ihn mit Dependency Injection. - Sie wählen Direct oder Gateway und setzen bevorzugte Regionen.
- Sie unterscheiden, was das SDK bei 429 selbst tut und was Ihr Code tun muss, und wiederholen 413 nicht.
- Sie melden sich mit Entra an, setzen System.Text.Json und halten Newtonsoft.Json als Pflichtverweis.
- Sie loggen Diagnose gezielt und testen den Client-Code ohne Konto.
Weiterlesen
- Best practices for the .NET SDK: die Prüfliste, Newtonsoft.Json, HttpClient.
- SDK connectivity modes: Gateway, Direct, Ports.
- Diagnose and troubleshoot request rate too large (429): wann 429 gesund ist.
- Connect with role-based access control and Microsoft Entra ID: Schlüssel abschalten, Rollen.
- Manage consistency levels: Überschreiben je Client oder Anfrage.