Wie umgehe ich Throttling Limits mit Admin API Key?

Ich erhalte die Meldung „429 Too Many Requests“ für API-Anfragen an meine selbst gehostete Instanz, selbst mit:

  • DISCOURSE_MAX_ADMIN_API_REQS_PER_MINUTE erhöht auf 600
    • Ich habe dies im env-Abschnitt von app.yml festgelegt und dann ./launcher rebuild ausgeführt und bestätigt, dass die Variable im neu erstellten Container gesetzt wurde.
    • Dies ist weit mehr als die Anzahl der Anfragen pro Minute, die ich versuche.
  • Ein uneingeschränkter Admin-API-Schlüssel.

Es scheint, dass dies bereits ohne klare Antwort diskutiert wurde, warum die Änderung von DISCOURSE_MAX_ADMIN_API_REQS_PER_MINUTE nicht zu funktionieren scheint:

Wie kann ich sicherstellen, dass API-Anfragen mit einem Admin-Schlüssel/Benutzer nicht gedrosselt werden?

Hallo @aas,

Könnten Sie etwas Kontext geben?

  • Wie viele API-Anfragen stellen Sie? Pro Sekunde, Minute, Stunde, pro Tag?
  • Sind Sie sicher, dass Sie einen Admin-API-Schlüssel verwenden?
  • Kommen all diese Anfragen von derselben IP-Adresse? Vielleicht aufgrund eines Reverse-Proxys?

Könnte es Nginx oder eine andere Software sein, die Sie mit diesem Fehler trifft?

1 „Gefällt mir“

Hallo @Bas,

Entschuldigen Sie die verspätete Antwort!

Ich schaue mir das jetzt noch einmal an, da wir eine Discourse-Integration gestartet haben und sicherstellen wollen, dass wir keine Probleme mit Ratenbegrenzungen bekommen.

Ich habe es mit einem neuen Schlüssel getestet, um sicherzugehen, dass er in keiner Weise eingeschränkt ist. Um genau zu sein, was meinen Sie genau mit einem Admin-API-Schlüssel?

Ich habe einen Schlüssel mit den folgenden Einstellungen erstellt:

Dort steht: „API-Schlüssel hat keine Einschränkung und alle Endpunkte sind zugänglich.“

Ich teste dies, indem ich API-Anfragen von einer lokalen Python-Shell aus sende, sodass sie von derselben IP-Adresse kommen. Wir sind auch auf Ratenbegrenzungen gestoßen, als wir ein Skript auf unserem Server ausgeführt haben. In diesem Fall kamen alle Anfragen von derselben IP-Adresse.

Ich habe bestätigt, dass die Ratenbegrenzung mit dem folgenden Code erreicht wird:

async def get_topic_post_stream(topic_id):
    url = f"{DISCOURSE_URL}/t/{topic_id}"
    async with httpx.AsyncClient(headers=HEADERS) as client:
        topic = await client.get(url)
    return topic.status_code


async def get_topic_post_streams(topic_ids):
    tasks = [functools.partial(get_topic_post_stream, topic_id) for topic_id in topic_ids]
    topics = await aiometer.run_all(
        tasks,
        # max_per_second=1,
        )
    return topics

# Holen Sie sich nur einen Ausschnitt von 15 der Themen in topic_ids zum Testen.
topics = asyncio.run(get_topic_post_streams(topic_ids[:15]))

Beachten Sie, dass der Parameter max_per_second auskommentiert ist, was zu keinen Einschränkungen bei der Anzahl der Anfragen führt.

Dies ist in 2,05 Sekunden abgeschlossen und 2 der 15 Anfragen geben 429 zurück.

Wenn ich es mit max_per_second=1 ausführe, ist alles erfolgreich abgeschlossen.

Lassen Sie mich wissen, wenn ich weitere Details angeben kann. Danke!

@Bas, hier ist das JavaScript-Äquivalent des Python-Codes, um die Reproduktion über die Entwicklerkonsolen zu erleichtern:

const DISCOURSE_URL = '';
const HEADERS = {
    'Api-Key': '',
    'Api-Username': '',
    'Content-Type': 'application/json'
};


const topicIds = Array.from({ length: 100 }, (_, i) => i + 1);

async function getTopicPostStream(topicId) {
    const url = `${DISCOURSE_URL}/t/${topicId}`;
    const response = await fetch(url, { headers: HEADERS });
    return response.status;
}

async function getTopicPostStreams(topicIds) {
    const results = await Promise.all(topicIds.map(topicId => getTopicPostStream(topicId)));
    return results;
}

// Überlasten Sie die Anfragen nicht und sehen Sie, dass Sie zwei 429er erhalten.
(async () => {
    const topics = await getTopicPostStreams(topicIds.slice(0, 15));
    console.log(topics);
})();

async function getTopicPostStreamsRateLimited(topicIds) {
    const results = [];
    for (const topicId of topicIds) {
        const result = await getTopicPostStream(topicId);
        results.push(result);
        await new Promise(resolve => setTimeout(resolve, 1000)); // Verzögerung um 1 Sekunde
    }
    return results;
}

// 1 Anfrage pro Sekunde gibt alle 200er zurück
(async () => {
    const topics = await getTopicPostStreamsRateLimited(topicIds.slice(0, 15));
    console.log(topics);
})();
1 „Gefällt mir“

Wenn ich raten müsste, ist dies höchstwahrscheinlich das Problem. Sie werden nicht auf API-Basis, sondern auf IP-Basis einer Ratenbegrenzung unterzogen.

Sie könnten sich die IP-spezifischen Einstellungen hier ansehen: Available settings for global rate limits and throttling - #27

Bitte lassen Sie mich wissen, ob das Ihr Problem tatsächlich löst :slight_smile:

1 „Gefällt mir“

Danke, @Bas!

Meiner Meinung nach sollte ich diese 429er unabhängig von den in diesem Beitrag genannten Einstellungen nicht erhalten. In dem von mir bereitgestellten Beispiel habe ich 15 Anfragen gesendet, was unter allen Standard-API-Grenzwerten liegt. Ich habe dies mit einem Admin-API-Schlüssel und Benutzernamen getan.

Das Beispiel überschreitet nicht die folgenden Standardwerte pro IP:

Es überschreitet nicht einmal die Limits für Nicht-Admins:

Das Ändern von DISCOURSE_MAX_REQS_PER_IP_MODE auf warn oder none hat nicht geholfen.

Fehlt mir etwas? :thinking:

Übrigens habe ich die Einstellungen geändert, indem ich app.yml bearbeitet und ./launcher destroy app && ./launcher start app ausgeführt habe.

Ich kann in /var/log/nginx/access.log sehen, dass die IP-Adresse korrekt ist, daher glaube ich nicht, dass Discourse alle Anfragen von derselben IP erhält.

Ich kann auch die IP-Adressen der Benutzer im Adminbereich sehen.

Dies sind die Einstellungen, die ich geändert habe:

  DISCOURSE_MAX_ADMIN_API_REQS_PER_MINUTE: 1200
  DISCOURSE_MAX_USER_API_REQS_PER_MINUTE: 60
  DISCOURSE_MAX_REQS_PER_IP_MODE: none
  DISCOURSE_MAX_REQS_PER_IP_PER_10_SECONDS: 100
  DISCOURSE_MAX_REQS_PER_IP_PER_MINUTE: 400

EDIT: Ich habe gerade den Antwortinhalt einer der fehlgeschlagenen Anfragen überprüft und festgestellt, dass nginx erwähnt wurde:

<html>\r\n<head><title>429 Too Many Requests</title></head>\r\n<body>\r\n<center><h1>429 Too Many Requests</h1></center>\r\n<hr>\n<center>nginx</center>\r\n</body>\r\n</html>\r\n

Ich werde weitere Nachforschungen zu den Themen anstellen, die nginx erwähnen.

1 „Gefällt mir“

Die beiden relevanten Abschnitte der nginx-Konfiguration scheinen zu sein:

limit_req_zone $binary_remote_addr zone=flood:10m rate=12r/s;
limit_req_zone $binary_remote_addr zone=bot:10m rate=200r/m;
limit_req_status 429;
limit_conn_zone $binary_remote_addr zone=connperip:10m;
limit_conn_status 429;
server {
  listen 80;
  return 301 https://community.ankihub.net$request_uri;
}

und

  location @discourse {
add_header Strict-Transport-Security 'max-age=31536000'; # remember the certificate for a year and automatically connect to HTTPS for this domain
  limit_conn connperip 20;
  limit_req zone=flood burst=12 nodelay;
  limit_req zone=bot burst=100 nodelay;
    proxy_set_header Host $http_host;
    proxy_set_header X-Request-Start "t=${msec}";
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $thescheme;
    proxy_pass http://discourse;
  }
}

Meine verbleibenden Fragen sind nun:

  • Soll ich beide Abschnitte bearbeiten, um sie an meine Discourse-Einstellungen anzupassen? Oder nur die Werte für location @discourse?

  • Wie kann ich diese Werte korrekt ändern und die Änderungen über Rebuilds hinweg beibehalten?
    Ich nehme an, ich kann die nginx-Konfiguration direkt im Container bearbeiten und dann den Container stoppen/starten. Aber es sieht so aus, als ob diese Werte ursprünglich aus templates/web.ratelimited.template.yml stammen und bei einem Rebuild überschrieben werden könnten?

Vielen Dank für deine Hilfe! :pray:

1 „Gefällt mir“

Autsch, da verlassen wir jetzt wohl meinen Komfortbereich, fürchte ich.

Wenn Sie von Nginx rate-limited werden, dann macht es Sinn, an diesen Einstellungen herumzuspielen und sie weniger restriktiv zu machen. Ich bin mir nicht sicher, ob Nginx IP-Adressen auf eine Whitelist setzen kann?

Etwas wie

map $remote_addr $exclude_from_limit {
    default 0;
    192.168.1.1 1;
    192.168.1.2 1;
}

und dann die Limits in ein if einschließen

        if ($exclude_from_limit = 0) {
            limit_req zone=flood burst=24 nodelay;
            limit_req zone=bot burst=400 nodelay;
            limit_conn connperip 20;
        }

Ja, Sie sollten einige Ersetzungen mit Pups während des Builds vornehmen, um dies persistent zu machen. Sehen Sie sich zum Beispiel web.ssl.template.yml an, wie Sie dies angehen können.

Oder Sie könnten das vergessen und Ihr API-Client-Skript langsamer laufen lassen, indem Sie an strategischen Stellen einige Sleeps einfügen. ← empfohlener Ansatz

4 „Gefällt mir“

[quote=„Richard – Communiteq, Beitrag: 10, Thema: 274206, Benutzername: RGJ“]Man könnte das vergessen und sein API-Client-Skript langsamer laufen lassen, indem man an strategischen Stellen einige Pausen einlegt.
[/quote]

Wie in einem rescue, wenn es Ratenbegrenzungen erhält. Das ist es, was ich normalerweise tue.

1 „Gefällt mir“

Was ist die maximal zulässige nachhaltige Rate von API-Anfragen pro Minute oder Sekunde bei der Standardinstallation?

Ich habe eine pro Sekunde versucht und scheine ein Limit erreicht zu haben. Während 1 Anfrage pro 7 Sekunden gut funktioniert.