en-US/about_Mgx_Tuning.help.txt

TOPIC
    about_Mgx_Tuning

SHORT DESCRIPTION
    Default settings, hard ceilings, and tuning guidance for large tenants.

LONG DESCRIPTION
    Mgx ships with conservative defaults to prevent accidental self-DoS
    against your tenant. Every tunable has a hard ceiling enforced at the
    property level -- invalid values throw rather than silently apply.

    Use Set-MgxOption to change settings at runtime. Only explicitly passed
    parameters are modified; all others retain their current values. Use
    Set-MgxOption -Reset to restore all defaults.

CLIENT-SIDE RATE LIMITING (ON BY DEFAULT)

    Setting Default Hard Max Purpose
    ------- ------- -------- -------
    RateLimitBurst 200 10,000 Token bucket burst capacity
    RateLimitPerSecond 50 10,000 Sustained request rate
    RateLimitQueueLimit 500 100,000 Queued requests before rejection

    Disable explicitly with Set-MgxOption -NoRateLimit.

ADAPTIVE PACING (ON BY DEFAULT)

    A proactive gate ahead of the token bucket. The bucket enforces a hard
    request-rate ceiling; the pacer slows down BEFORE Graph throttles, based
    on what the tenant is signaling. Independent of -NoRateLimit; disable
    explicitly with Set-MgxOption -NoAdaptivePacing.

    State is kept per workload (drive/SharePoint, directory, other), so a
    throttle on one service never slows an unrelated workload in the same
    session. Three mechanisms, all inactive (zero delay) in steady state:

    - AIMD back-off. A 429 caps the workload's request rate (4 req/sec on
      the first throttle, halving on repeat, floor 2). The cap climbs back
      a tenth of the ceiling per clean second and expires outright after
      five quiet minutes. A server Retry-After additionally holds ALL
      requests for that workload, not just the throttled one.

    - Slow start. A workload that is cold (first use, or idle five minutes)
      opens with a 4 req/sec rate cap that doubles each clean second until
      it reaches the ceiling. The first request is never delayed; the cap
      only bites when demand exceeds it, i.e. fan-outs -- the pattern that
      otherwise throttles before completing a single item.

    - Throttle-proximity damping. When Graph reports
      x-ms-throttle-limit-percentage (documented to appear above 80% of
      budget), requests are spaced with a delay that ramps from 0ms at 0.8
      to 2000ms at 1.2, while the report stays fresh (30s).

    Batch outer /$batch POSTs are exempt from the gate -- batch throughput
    is owned by BatchItemsPerSecond and its item-level pacing above -- but
    their responses still feed the pacer's signals.

    Watch it work: Get-MgxTelemetry shows total pacing wait, activation
    count, the last reported throttle percentage, and a per-workload state
    line (active caps, slow start, latency vs. baseline). Run any cmdlet
    with -Verbose to see individual pacing waits.

RETRY BUDGET CAPS

    Setting Default Hard Max Purpose
    ------- ------- -------- -------
    MaxRetryAttempts 7 50 Retries per request
    MaxRetryAfterSeconds 120s 600s Caps server Retry-After delays
    TotalTimeoutSeconds 300s 3,600s Total time across all retries
    AttemptTimeoutSeconds 30s 300s Per-request timeout

CIRCUIT BREAKER

    Setting Default Hard Max Purpose
    ------- ------- -------- -------
    CircuitBreakerFailureRatio 10% 100% Failure ratio to trip
    CircuitBreakerMinThroughput 40 1,000 Min requests before evaluating
    CircuitBreakerSamplingDurationSeconds 30s 300s Evaluation window
    CircuitBreakerDurationSeconds 15s 300s Open-state duration

    Trips on 500/502/503/504 (server errors). Does NOT trip on 429 -- that
    is expected throttling, not failure.

BATCH CONSTRAINTS

    Setting Default Hard Max Purpose
    ------- ------- -------- -------
    BatchChunkConcurrency 1 10 Concurrent batch chunks
    BatchItemsPerSecond 20 1,000 Inter-chunk pacing (items/sec)
    Batch size 20 20 Items per /$batch POST (Graph limit)
    Per-item retries 3 3 Retries for failed items in a batch

    Sequential chunk execution (default) paces at 20 items/sec to stay under
    Graph's write throttle ceiling.

    Pacing adapts to what the tenant actually tolerates. A 429 halves the write
    rate, down to a floor of 2 items/sec, and the reduced rate carries across
    Invoke-MgxBatchRequest calls so a loop of small batches does not immediately
    get throttled again. It also climbs back: every two consecutive chunks
    without a 429 add a tenth of the configured rate, and once no throttling has
    been seen for five minutes the configured rate is restored outright. Run
    Invoke-MgxBatchRequest with -Verbose to see each adjustment.

WRITE COST: PACE BY WRITES, NOT ITEMS

    BatchItemsPerSecond paces batch items, but Graph throttles directory
    writes. Those are the same thing only when each item performs one write.
    A compound item multiplies the true rate:

        Item shape Writes per item
        ---------- ---------------
        POST /users (plain create) 1
        PATCH /users/{id} 1
        POST /groups with 20 members@odata.bind ~21

    At the default 20 items/sec, plain creates put ~20 writes/sec on the
    tenant; group creates with 20 bound members put ~420 writes/sec on it.

    The trap: heavy-write batches look fine at first. Graph's write throttle
    is a sliding budget, not an instant rate check -- a burst allowance
    absorbs the early chunks, then sustained throttle waves begin. Observed
    on a live ~100k-object tenant: 57,000 plain user creates ran at
    20 items/sec for 48 minutes with zero visible throttling, while group
    creates with 20-member binds at the same item rate ran clean for several
    thousand items and then drew 429 waves with multi-minute Retry-After
    backoff.

    Adaptive pacing recovers on its own (rates halve on 429, climb back on
    clean chunks, restore fully after five clean minutes) and no items are
    lost -- but recovery time is wall-clock time. For compound items, set
    the rate for the write cost up front:

        # Starting point: divide the default budget by writes-per-item
        # e.g. ~21-write group creates:
        Set-MgxOption -BatchItemsPerSecond 2

    Then let adaptive pacing find the tenant's real ceiling from below,
    instead of starting above it and paying for the discovery in Retry-After
    waits. Run with -Verbose to watch the rate adjustments.

POST SAFETY
    Non-idempotent methods (POST) only retry on 429 -- not on 5xx. A 500 on
    POST may mean the request was partially processed; blindly retrying could
    create duplicates.

LARGE TENANT CONFIGURATION
    Default settings are tuned for tenants up to ~10k objects. Larger tenants
    should adjust timeouts, rate limits, and batch pacing.

    Observed throughput (v1.0.1, live tenant):

        Operation Objects Throughput Notes
        --------- ------- ---------- -----
        User creation (batch POST) 25,000 20 items/sec Zero 429s with default pacing
        User enumerate (-All) 47,000 2,653/sec Streaming pagination
        Batch GET Mixed 107/sec Default settings

    50k+ tenants (read-heavy):

        Set-MgxOption -TotalTimeoutSeconds 600 `
                      -AttemptTimeoutSeconds 60 `
                      -RateLimitBurst 300 `
                      -RateLimitQueueLimit 2000

        TotalTimeoutSeconds 300 -> 600 Full enumeration takes > 5 min
        AttemptTimeoutSeconds 30 -> 60 Large pages can exceed 30s
        RateLimitBurst 200 -> 300 Absorbs pagination spikes
        RateLimitQueueLimit 500 -> 2,000 Fan-out queues more requests

    100k+ write operations:

        Set-MgxOption -BatchItemsPerSecond 15 `
                      -TotalTimeoutSeconds 3600 `
                      -AttemptTimeoutSeconds 180

        BatchItemsPerSecond 20 -> 15 Stay under write throttle
        TotalTimeoutSeconds 300 -> 3,600 100k writes takes ~110 min
        AttemptTimeoutSeconds 30 -> 180 Batch POST slows under load

SEE ALSO
    Set-MgxOption
    Get-MgxOption
    Get-MgxTelemetry
    about_Mgx