Τον σχεδιασμό του chargeback τον αποφασίζει ένα όριο του Azure Monitor, όχι μια λογιστική προτίμηση: ένα custom metric επιτρέπει 10 dimension keys, το API Management ξοδεύει ήδη 5 από αυτά σε προεπιλογές, και τα ενεργά time series που παράγει μια πολιτική είναι το γινόμενο των μοναδικών τιμών κάθε dimension που προσθέτεις. Τρία dimensions με δέκα τιμές το καθένα είναι χίλια time series, απέναντι σε οροφή 50.000 ενεργών time series ανά region ανά subscription σε παράθυρο δώδεκα ωρών. Βάλε ένα user ID ή ένα request ID σε αυτό το metric και η ίδια η τηλεμετρία της πλατφόρμας γίνεται το outage. Το Μέρος 4 ολοκλήρωσε τα τρία μονοπάτια αιτημάτων. Αυτό το μέρος απαντά στο ερώτημα για το οποίο υπάρχει όλη η σειρά: ποια ομάδα ξόδεψε τι, και πώς το gateway εμποδίζει μία από αυτές να ξοδέψει το quota όλων των άλλων.

Δύο διαπιστευτήρια, δύο διαφορετικές δουλειές

Το API Management σου δίνει δύο μηχανισμούς που ο κόσμος συνήθως αντιμετωπίζει ως εναλλακτικές ενώ είναι συμπληρωματικοί.

Ένα subscription key ταυτοποιεί έναν καταναλωτή για τους σκοπούς των products, των quotas, και των analytics. Φτάνει στο header Ocp-Apim-Subscription-Key, ή σε ένα query parameter subscription-key που ελέγχεται μόνο όταν λείπει το header, και τα δύο ονόματα είναι ρυθμιζόμενα ανά API. Τα keys εκδίδονται σε ζεύγη ώστε μια εφαρμογή να μπορεί να μεταβεί από το key A στο key B και να αναγεννήσει το A με ελάχιστη αναστάτωση.

Δύο πράγματα για τα keys πρέπει να σχεδιαστούν ρητά αντί να θεωρηθούν δεδομένα. Πρώτον, το API Management δεν έχει ενσωματωμένο lifecycle για subscription keys: ούτε ημερομηνίες λήξης, ούτε αυτόματη εναλλαγή. Το rotation είναι ένα workflow που το χτίζεις, με Azure PowerShell ή τα SDKs, και αν δεν το χτίσεις τα keys ζουν για πάντα. Δεύτερον, το key περνά στο backend εξ ορισμού, όπου μπορεί να καταλήξει στα monitoring logs του backend και οπουδήποτε αποστέλλονται αυτά τα logs. Ένα set-header στο τέλος του inbound section που το αφαιρεί είναι μια διόρθωση δύο γραμμών για ένα εύρημα credential-in-logs που αλλιώς το ανακαλύπτει ένας auditor.

Ένα JWT από το Microsoft Entra ID απαντά σε διαφορετικό ερώτημα: είναι αυτός ο καλών εξουσιοδοτημένος αυτή τη στιγμή. Λήγει εγγενώς, μπορεί να ανακληθεί κεντρικά, και μεταφέρει claims. Οι εφαρμογές παίρνουν ένα μέσω του OAuth2 client credentials flow, και το gateway το επικυρώνει πριν τρέξει οτιδήποτε άλλο:

<validate-jwt header-name="Authorization" require-scheme="Bearer"
              require-expiration-time="true" require-signed-tokens="true"
              clock-skew="60"
              output-token-variable-name="jwt"
              failed-validation-httpcode="401"
              failed-validation-error-message="Invalid or missing token.">
  <openid-config url="https://login.microsoftonline.com/{{tenant-id}}/v2.0/.well-known/openid-configuration" />
  <audiences>
    <audience>api://genai-gateway</audience>
  </audiences>
  <required-claims>
    <claim name="roles" match="any">
      <value>model.invoke</value>
    </claim>
  </required-claims>
</validate-jwt>

Το output-token-variable-name είναι το κομμάτι που κερδίζει τη θέση του: το επικυρωμένο token προσγειώνεται σε μια policy variable, οπότε μεταγενέστερες πολιτικές μπορούν να διαβάσουν ένα claim από αυτό αντί να εμπιστεύονται ένα header που το ελέγχει ο καλών. Αυτό μετράει άμεσα, γιατί η επόμενη απόφαση είναι ποια τιμή γίνεται το κλειδί του quota.

Η παγίδα της ανώνυμης πρόσβασης

Αξίζει να ειπωθεί καθαρά, γιατί η ρύθμιση που την προκαλεί μοιάζει ακίνδυνη. Το αν ένα API μπορεί να κληθεί χωρίς κανένα subscription key εξαρτάται από δύο ανεξάρτητες ρυθμίσεις: αν το ίδιο το API απαιτεί subscription, και αν κάθε product στο οποίο έχει ανατεθεί απαιτεί ένα. Αν το API δεν απαιτεί subscription, οι κλήσεις χωρίς key επιτρέπονται στο API context ακόμη κι όταν ένα product-scoped key θα δούλευε επίσης. Το gateway κάνει ακριβώς αυτό που του είπαν· κανείς δεν του είπε την πρόθεση.

Για ένα gateway μπροστά από πληρωμένη χωρητικότητα μοντέλων, ο κανόνας είναι ότι κάθε API απαιτεί subscription και κάθε product απαιτεί subscription, και η πολιτική validate-jwt τρέχει ούτως ή άλλως, οπότε μια μη αυθεντικοποιημένη κλήση αποτυγχάνει δύο φορές. Μετρούν και τα scopes των keys: ένα service-scoped ή all-APIs-scoped key ανοίγει κάθε API στο instance, κάτι που είναι σωστό για τα εργαλεία της ίδιας της ομάδας πλατφόρμας και λάθος για έναν καταναλωτή. Οι καταναλωτές παίρνουν product-scoped keys.

Quota, και πάνω σε τι να μετρηθεί

Η πολιτική ορίου tokens από το Μέρος 1 παίρνει ένα counter-key, και αυτή η επιλογή είναι ολόκληρο το μοντέλο tenancy. Το να κλειδώσεις στο subscription ID δίνει έναν κουβά ανά καταναλωτή, που είναι αυτό που χρειαζόταν το περιστατικό του Μέρους 1: το batch job του marketing δεν μπορεί να στραγγίξει το quota του customer assistant, επειδή είναι διαφορετικά subscriptions με διαφορετικούς counters.

<llm-token-limit counter-key="@(context.Subscription.Id)"
                 tokens-per-minute="{{tpm_tier}}"
                 estimate-prompt-tokens="true"
                 remaining-tokens-variable-name="remainingTokens"
                 remaining-tokens-header-name="x-remaining-tokens" />

Το να επιστρέφεις το υπόλοιπο περιθώριο σε ένα response header αξίζει τα δύο επιπλέον attributes. Ένας client που βλέπει το δικό του headroom μπορεί να κάνει πίσω πριν πάρει 429, και μια συζήτηση support για throttling ξεκινά από έναν αριθμό που τον βλέπουν και οι δύο πλευρές.

Τα products γίνονται τα tiers: ένα interactive product με υψηλό όριο ανά λεπτό και μέτριο ημερήσιο quota, ένα batch product με το αντίστροφο, και ένα sandbox product με όρια αρκετά χαμηλά ώστε ένας ανεξέλεγκτος βρόχος σε ένα notebook να είναι ενόχληση αντί για περιστατικό. Ένας tenant είναι μια εφαρμογή Entra ID συν ένα ή περισσότερα product subscriptions, που προμηθεύονται από το Terraform του layer 30-tenants του Μέρους 2, ποτέ από ticket.

Ο προϋπολογισμός των dimensions

Τώρα ο περιορισμός από την εισαγωγή, διατυπωμένος ως ο σχεδιαστικός κανόνας που παράγει. Το Azure Monitor βάζει οροφή 10 dimension keys στα custom metrics, και το API Management χρησιμοποιεί 5 από αυτά για προεπιλογές, μεταξύ των οποίων Region, Service ID, Service Name, και Service Type. Αυτό αφήνει μέγιστο 5 custom dimensions ανά πολιτική. Το μεγαλύτερο όριο είναι συνδυαστικό: τα ενεργά time series ισούνται με το γινόμενο των μοναδικών τιμών στα dimensions σου μέσα στην περίοδο, οπότε τρία dimensions με δέκα τιμές το καθένα συνεισφέρουν χίλια, και η περιφερειακή οροφή είναι 50.000 ενεργά time series ανά subscription σε δώδεκα ώρες. Πολλαπλά instances του API Management στο ίδιο region συνεισφέρουν στο ίδιο περιφερειακό σύνολο.

Η συνέπεια είναι ένας καθαρός διαχωρισμός που αξίζει να γραφτεί στην τεκμηρίωση της ίδιας της πλατφόρμας:

  • Τα metrics μεταφέρουν μόνο dimensions χαμηλής cardinality. Tenant, model alias, environment. Τρία dimensions, φραγμένα σύνολα τιμών, dashboards που φορτώνουν γρήγορα και alerts που πυροδοτούνται στο σωστό πράγμα.
  • Τα logs μεταφέρουν όλα τα υπόλοιπα. Request ID, user ID αν καταγράφεται καθόλου, μεγέθη prompt και completion, latency, ποιο backend το εξυπηρέτησε. Το Application Insights και το Log Analytics είναι εκεί όπου ζει η απόδοση ανά αίτημα, και το μηνιαίο chargeback job κάνει queries σε αυτά.
<llm-emit-token-metric namespace="llm-metrics">
  <dimension name="Tenant"      value="@(context.Subscription.Name)" />
  <dimension name="ModelAlias"  value="@(context.Request.MatchedParameters.GetValueOrDefault("model","unknown"))" />
  <dimension name="Environment" value="{{env}}" />
</llm-emit-token-metric>

Τρία dimensions, σκόπιμα. Το τέταρτο που θα ζητήσει κάποιος είναι το user ID, και η απάντηση είναι όχι: ανήκει στην εγγραφή του log, όπου το cardinality κοστίζει αποθήκευση αντί για ολόκληρο τον προϋπολογισμό metrics ενός region.

Chargeback που αντέχει μια διαφωνία με το οικονομικό τμήμα

Το μηνιαίο job είναι συνηθισμένο data engineering, και η αξιοπιστία του στηρίζεται στο να είναι ρητό για την ακρίβεια, όχι στο να είναι ακριβές. Τα Μέρη 3 και 4 καθιέρωσαν τρεις κλάσεις, και το έγγραφο του chargeback τις ονομάζει:

  • Μη streamed σύγχρονο: μετρημένο από το ίδιο το usage του μοντέλου. Ακριβές.
  • Streamed σύγχρονο: prompt και completion tokens εκτιμημένα στο gateway, επειδή το streaming επιβάλλει εκτίμηση ανεξαρτήτως ρύθμισης πολιτικής. Κατά προσέγγιση, με την απόκλιση να παρακολουθείται ως δικό της metric.
  • Batch: ακριβές, από τα request counts του ίδιου του job και το usage ανά απάντηση στο αρχείο εξόδου, επειδή το υπέβαλε το control plane.

Το job παράγει έναν αριθμό ανά tenant και μια γραμμή συμφωνίας: το άθροισμα της αποδοθείσας δαπάνης απέναντι στο πραγματικό κόστος των πόρων για την περίοδο. Ένα υπόλοιπο λίγων ποσοστιαίων μονάδων, σταθερό μήνα με τον μήνα, είναι το τίμιο κόστος της εκτίμησης. Ένα υπόλοιπο που μεγαλώνει είναι bug, συνηθέστερα ένας καλών που βρήκε μονοπάτι γύρω από το gateway. Το να δημοσιεύεις το υπόλοιπο αντί να το κρύβεις είναι αυτό που κάνει τον υπόλοιπο αριθμό πιστευτό, και λειτουργεί ταυτόχρονα ως ανιχνευτής για το μονοπάτι παράκαμψης για το οποίο προειδοποίησε το Μέρος 1.

Όταν μια ομάδα χρειάζεται δικό της gateway

Περιστασιακά οι απαιτήσεις ενός καταναλωτή δεν χωρούν σε κοινή υποδομή: ένα ρυθμιζόμενο workload που χρειάζεται δική του απομόνωση δικτύου, ή μια ομάδα που πρέπει να διαχειρίζεται τα δικά της APIs χωρίς να αγγίζει κανενός άλλου. Τα workspaces του API Management υπάρχουν γι' αυτό, και η επιλογή είναι ανάμεσα στο προεπιλεγμένο managed gateway της υπηρεσίας, διαθέσιμο στα v2 tiers χωρίς επιπλέον κόστος gateway και με πρόσβαση σε ενσωματωμένες δυνατότητες, και σε ένα ξεχωριστό workspace gateway, διαθέσιμο σε Basic v2, Standard v2, Premium, και Premium v2, που αγοράζει ισχυρή απομόνωση runtime και ανεξάρτητη κλιμάκωση, hostname, και ρύθμιση δικτύου, με τίμημα το επιπλέον κόστος, το μεγαλύτερο deployment, και την υποστήριξη σε λιγότερα regions.

Μια λεπτομέρεια αποφασίζει αν αυτό είναι αναστρέψιμο: η ρύθμιση virtual network ενός workspace gateway μπορεί να οριστεί μόνο όταν δημιουργείται το gateway, και δεν αλλάζει μετά. Η ρύθμιση δικτύου του είναι επίσης ανεξάρτητη από αυτήν του API Management instance. Ένα workspace gateway που δημιουργήθηκε χωρίς απομόνωση, από ομάδα που αργότερα τη χρειάζεται, είναι ένα rebuild.

Τρόποι αποτυχίας που αξίζει να προσέχεις

  • Ένα dimension υψηλής cardinality σε μια πολιτική. Δεν θα βγάλει σφάλμα. Θα καταναλώσει αθόρυβα τον προϋπολογισμό ενεργών time series του region, και το πρώτο σύμπτωμα είναι ότι τα custom metrics άλλων ομάδων παύουν να εμφανίζονται.
  • Keys που δεν λήγουν ποτέ. Δεν υπάρχει ενσωματωμένη λήξη ή εναλλαγή. Χωρίς rotation workflow, το credential που εκδόθηκε σε μια αποσυρμένη εφαρμογή δουλεύει ακόμη, και τίποτα δεν το αναφέρει.
  • Το subscription key στα logs του backend. Η προεπιλεγμένη συμπεριφορά το προωθεί. Αφαίρεσέ το στο inbound, και έλεγξε τα logs του backend μία φορά για επιβεβαίωση αντί να το υποθέσεις.
  • Ένα API που δεν απαιτεί subscription. Ανώνυμη πρόσβαση στο API context, που προκύπτει από μια ρύθμιση που διαβάζεται ως επιεικής και όχι ως ανοιχτή.
  • Chargeback δημοσιευμένο χωρίς το υπόλοιπό του. Την πρώτη φορά που ένας product owner θα βρει μια απόκλιση που δεν αποκάλυψες, κάθε μελλοντικός αριθμός γίνεται διαπραγματεύσιμος.

Τι κληρονομεί το Μέρος 6

Tenants με πραγματικές ταυτότητες, quotas που κρατούν, και ένας μηνιαίος αριθμός ανά ομάδα με δηλωμένη ακρίβεια. Κάτι που στήνει το επόμενο ερώτημα με ακρίβεια: το φθηνότερο token είναι αυτό που δεν στάλθηκε ποτέ, και το semantic caching είναι ο πρώτος μηχανισμός σε αυτή τη σειρά που χαμηλώνει τον λογαριασμό αντί απλώς να τον αποδίδει. Αλλάζει επίσης αθόρυβα το τι σημαίνουν οι παραπάνω αριθμοί, επειδή ένα cache hit είναι ένα αίτημα που δεν κόστισε τίποτα και πρέπει παρ' όλα αυτά να εμφανιστεί στην αναφορά κάποιου.

Διάβασε επίσης

Για την πλευρά υποδομής και πλατφόρμας της λειτουργίας κάτι τέτοιου σε κλίμακα, οι σημειώσεις πεδίου είναι στο ercan.cloud, και ο κόμβος είναι στο ercanermis.com.

Αναφορές