Zum Inhalt springen
SharePoint Entwicklung

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

PnP-Provisioning für hunderte Sites und tausende Bibliotheken: 429-Throttling, Idempotenz und ETag-Lösung für Race Conditions.

Aktualisiert am

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.

Die automatische Anlage von Projekträumen ist der Kern von Smarter Project Portal. Wie sie fachlich aufgebaut ist, beschreibt der Beitrag SharePoint-Projektraum automatisch anlegen.

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#

Ähnliche Beiträge

SharePoint: alte Versionen automatisch löschen und
Schema: links eine Projektliste mit dem Steuerfeld Versionen löschen und den Werten Ja, Erledigt, Nein und Fehler, in der Mitte der Wochenlauf, rechts das Ergebnis je Bibliothek mit gelöschten Versionen und freigegebenem Speicher

SharePoint: alte Versionen automatisch löschen und Speicherplatz freigeben

Alte Versionen in SharePoint Online per PowerShell und Azure Function löschen: gesteuert über eine Liste, app-only, mit Zeitbudget und Probelauf. Mit Code.

Weiterlesen
SharePoint Intranet Telefonbuch mit Skills Feature
SharePoint Intranet Telefonbuch mit Skills Feature

SharePoint Intranet Telefonbuch mit Skills Feature

SharePoint Personensuche und Kompetenzsuche mit Microsoft 365 Skills, Skill-Katalog, SPFx, SharePoint Search und Microsoft Graph.

Weiterlesen
Versionshistorie intelligent migrieren: Weniger

Versionshistorie intelligent migrieren: Weniger Speicher, gleiche Nachvollziehbarkeit

Wie Hash-basierte Versions-Deduplizierung bei einer 25-TB-Migration SharePoint-Online-Speicher spart und Migration-API-Bugs umgeht.

Weiterlesen

Fragen zu diesem Thema?

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