Zum Inhalt springen
SharePoint Entwicklung

Provisioning im Großformat: PnP-Templates, Throttling und Race Conditions bei tausenden Bibliotheken

Hunderte Sites, tausende Bibliotheken, Views und Berechtigungen automatisiert provisionieren: PnP-Provisioning-Erfahrungen aus dem Enterprise-Maßstab — inkl. 429-Throttling, Idempotenz und einem Lost-Update-Race mit ETag-Lösung.

Wer eine Migration als Transformation anlegt (Teil 1 unserer Serie), muss die Zielstruktur erst einmal bauen: In unserem Fall hunderte Projekt-Sites mit jeweils dutzenden Bibliotheken, einheitlichen Inhaltstypen, projektspezifischen Views und einem mehrstufigen Berechtigungsmodell. Manuell undenkbar — also PnP-Provisioning per Azure Function, Template-getrieben.

Bei kleinen Umgebungen ist das ein gelöstes Problem. Im Großformat kommen drei Gegner dazu: Throttling, fehlende Idempotenz und Parallelität.

Die Architektur

Unser Provisioning läuft als Azure Function mit zertifikatsbasierter App-Only-Authentifizierung (PnP.Core + Key Vault). Ein PnP-Provisioning-Template definiert Inhaltstypen, Felder, Bibliotheken und Views deklarativ; projekt- und baulos-spezifische Strukturen werden dynamisch ergänzt. Wichtig war uns die Trennung:

  • Template = konzernweiter Standard (Felder, Inhaltstypen, Basis-Views)
  • Code = projektspezifische Dynamik (Anzahl/Benennung der Strukturen, Navigation, Berechtigungsstufen)

So bleibt der Standard reviewbar und versionierbar, ohne dass jede Projektvariante ein eigenes Template braucht.

Gegner 1: Throttling (HTTP 429)

Tausende Objekte anzulegen heißt: zehntausende REST/CSOM-Calls. SharePoint Online drosselt aggressiv, und zwar pro App pro Tenant — Ihr Provisioning konkurriert also mit allem anderen, was Ihre App-Registrierung sonst noch tut.

Was sich bewährt hat:

  1. Retry-After respektieren — immer. Wer nach einem 429 sofort retried, verlängert die Sperre. PnP.Core bringt das Handling mit; eigene HTTP-Aufrufe brauchen es explizit.
  2. Verbindungs-Gating auf Prozessebene. Wir begrenzen die parallelen Verbindungen prozessweit (bei uns: 12) statt pro Task — sonst multiplizieren sich parallele Arbeitspakete unbemerkt zu hunderten simultanen Calls.
  3. Throttle-Telemetrie mitschreiben. Ein simples JSON-Protokoll (Zeitpunkt, Endpoint, Retry-After-Dauer) zeigt, welche Call-Muster drosseln — Optimierung ohne Messung ist Raten.
  4. Batching, wo die API es hergibt. Views, Felder und Items lassen sich in $batch-Requests bündeln; das reduziert Call-Anzahl und damit Throttling-Druck erheblich.

Gegner 2: Fehlende Idempotenz

Bei hunderten Sites wird ein Lauf mittendrin abbrechen — Function-Timeout, Throttling-Kaskade, Deployment. Die wichtigste Eigenschaft eines Enterprise-Provisionings ist deshalb: Jeder Lauf muss gefahrlos wiederholbar sein.

Konkret heißt das: Vor jedem Anlegen prüfen, ob das Objekt existiert — und dann nicht blind überspringen, sondern auf Soll-Zustand prüfen (Drift-Detection). Ein „existiert schon” kann auch „existiert falsch” bedeuten. Ein Beispiel aus unserer Praxis: Ein Case-Sensitivity-Fehler beim View-Vergleich führte dazu, dass Views als vorhanden galten, die es nicht waren — der Lauf war grün, die Struktur unvollständig. Seitdem gilt: Vergleichslogik wird getestet wie Fachlogik.

Gegner 3: Parallelität — das Lost-Update-Race

Der subtilste Fehler des Projekts: Bei paralleler Provisionierung mehrerer Strukturen derselben Site verschwanden sporadisch Navigationseinträge. Von 19 erwarteten Links kamen manchmal nur 6 an — nicht reproduzierbar, nie dieselben.

Die Ursache ist ein klassisches Read-Modify-Write-Race: Zwei parallele Worker lesen dieselbe Navigations-Konfiguration, ergänzen jeweils ihren Eintrag und schreiben zurück — der zweite Schreiber überschreibt den ersten (Lost Update).

Die Lösung: Optimistic Concurrency mit ETag/IF-MATCH und Retry-Schleife:

// Lost-Update-sicheres Update einer geteilten Property
for (var attempt = 1; attempt <= maxAttempts; attempt++)
{
    var (value, etag) = await ReadWithETagAsync(url);      // GET liefert ETag

    var updated = AddEntryIdempotent(value, newEntry);      // per fachlichem
    if (updated is null) return;                            // Schlüssel: schon da

    var request = new HttpRequestMessage(HttpMethod.Put, url)
    {
        Content = new StringContent(updated)
    };
    request.Headers.TryAddWithoutValidation("IF-MATCH", etag); // NICHT "*"

    var response = await client.SendAsync(request);
    if (response.IsSuccessStatusCode) return;                // gewonnen

    if (response.StatusCode == HttpStatusCode.PreconditionFailed)
        continue;                                            // 412: verloren → neu lesen

    response.EnsureSuccessStatusCode();
}

Drei Details, die den Unterschied machen:

  • IF-MATCH: * schützt nicht — es sagt nur „überschreibe, was immer da ist”, also exakt das Race, das wir verhindern wollen. Es braucht den konkreten ETag des gelesenen Stands. (Achtung: manche Client-Bibliotheken erzwingen intern * — dann hilft nur der rohe REST-Call.)
  • Idempotenz per fachlichem Schlüssel: Der Update-Schritt prüft, ob der Eintrag logisch schon existiert — so sind Retries und wiederholte Läufe gefahrlos.
  • 412 ist kein Fehler, sondern das System, das funktioniert: Precondition Failed heißt „jemand war schneller” — neu lesen, neu anwenden, neu schreiben.

Lessons Learned

  1. Provisioning ist Software, kein Skript. Ab einer gewissen Größe braucht es Tests, Telemetrie, Idempotenz und Concurrency-Design — dieselben Standards wie jede andere Backend-Komponente.
  2. Parallelität zuerst designen, nicht nachrüsten. Jede geteilte Ressource (Navigation, Property Bags, zentrale Listen) ist ein Race-Kandidat.
  3. Grüne Läufe verifizieren. Ein Provisioning-Lauf ohne Soll-Ist-Abgleich ist eine Hoffnung, kein Ergebnis — dieselbe Lektion wie bei der Migration selbst.

Sie provisionieren SharePoint-Strukturen im dreistelligen Site-Bereich und kämpfen mit Throttling, Abbrüchen oder Heisenbugs? Genau diese Probleme haben wir gelöst — wir unterstützen Sie gerne.

Alle Teile der Serie:

  1. Warum keine 1:1-Migration
  2. 25 TB SharePoint-Migration: Warum Standard-Tools nicht reichen
  3. Die SharePoint Migration API in der Praxis
  4. Term Store am Limit: Taxonomie-Migration im geteilten Tenant
  5. Versionshistorie intelligent migrieren
  6. Provisioning im Großformat: PnP, Throttling, Berechtigungen (dieser Beitrag)
  • PnP
  • Provisioning
  • SharePoint Online
  • Azure Functions
  • Throttling
  • C#

Fragen zu diesem Thema?

Wir unterstützen Sie gerne bei der Umsetzung in Ihrer Umgebung.