Fix global rate limit ACME

Signed-off-by: Bryan Frimin <bryan@probo.com>
This commit is contained in:
Bryan Frimin
2026-07-22 17:28:35 +02:00
parent 0f7b639d2f
commit 0b577cda40
3 changed files with 388 additions and 239 deletions

View File

@@ -877,20 +877,15 @@ WHERE
return nil
}
func (c *Certificate) LoadNextForProvisioningForUpdateSkipLocked(
// LoadNextForBeginChallengeForUpdateSkipLocked selects the next PENDING/RENEWING
// row eligible to start a new ACME order. These rows use exponential backoff
// from ssl_last_attempt_at: 15m * 2^min(retry,5). Ordinary failures only reach
// retry counts 0–2 before FAILED; higher exponents are unused by the current
// failure budget but keep the SQL ceiling defensive.
func (c *Certificate) LoadNextForBeginChallengeForUpdateSkipLocked(
ctx context.Context,
tx pg.Tx,
pollLease time.Duration,
) error {
// PROVISIONING rows with an open order become eligible again only after
// pollLease elapses since the last attempt. The caller sizes pollLease to
// exceed the maximum Process window: the claim's FOR UPDATE lock is released
// before Process runs, so a shorter interval would let another worker claim
// the same row while its poll/issue is still in flight. Pending/Renewing
// rows (and provisioning rows without an order) use exponential backoff from
// ssl_last_attempt_at: 15m * 2^min(retry,5). Ordinary failures only reach
// retry counts 0–2 before FAILED; higher exponents are unused by the current
// failure budget but keep the SQL ceiling defensive.
q := `
SELECT
id,
@@ -913,18 +908,90 @@ FROM
certificates
WHERE
status = ANY(@statuses)
AND (
ssl_last_attempt_at IS NULL
OR ssl_last_attempt_at < CURRENT_TIMESTAMP - (
INTERVAL '15 minutes' * (POWER(2, LEAST(ssl_retry_count, 5))::int)
)
)
ORDER BY
updated_at ASC
LIMIT 1
FOR UPDATE SKIP LOCKED
`
rows, err := tx.Query(
ctx,
q,
pgx.StrictNamedArgs{
"statuses": []string{
string(CertificateStatusPending),
string(CertificateStatusRenewing),
},
},
)
if err != nil {
return fmt.Errorf("cannot query certificate begin-challenge queue: %w", err)
}
certificate, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[Certificate])
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return ErrResourceNotFound
}
return fmt.Errorf("cannot collect certificate: %w", err)
}
*c = certificate
return nil
}
// LoadNextForPollOrderForUpdateSkipLocked selects the next PROVISIONING row
// eligible to poll its ACME order. A row with an open order becomes eligible
// again only after pollLease elapses since the last attempt. The caller sizes
// pollLease to exceed the maximum Process window: the claim's FOR UPDATE lock
// is released before Process runs, so a shorter interval would let another
// worker claim the same row while its poll/issue is still in flight. A row
// without an open order is a defensive edge case (the state machine always
// pairs PROVISIONING with an order) and falls back to the same exponential
// backoff as begin-challenge rows.
func (c *Certificate) LoadNextForPollOrderForUpdateSkipLocked(
ctx context.Context,
tx pg.Tx,
pollLease time.Duration,
) error {
q := `
SELECT
id,
hostname,
http_challenge_token,
http_challenge_key_auth,
http_challenge_url,
http_order_url,
ssl_certificate,
encrypted_ssl_private_key,
ssl_certificate_chain,
status,
ssl_expires_at,
ssl_retry_count,
ssl_last_attempt_at,
provisioning_error,
created_at,
updated_at
FROM
certificates
WHERE
status = @provisioning_status
AND (
ssl_last_attempt_at IS NULL
OR (
status = @provisioning_status
AND http_order_url IS NOT NULL
http_order_url IS NOT NULL
AND ssl_last_attempt_at < CURRENT_TIMESTAMP - make_interval(secs => @poll_lease_seconds)
)
OR (
NOT (
status = @provisioning_status
AND http_order_url IS NOT NULL
)
http_order_url IS NULL
AND ssl_last_attempt_at < CURRENT_TIMESTAMP - (
INTERVAL '15 minutes' * (POWER(2, LEAST(ssl_retry_count, 5))::int)
)
@@ -940,17 +1007,12 @@ FOR UPDATE SKIP LOCKED
ctx,
q,
pgx.StrictNamedArgs{
"statuses": []string{
string(CertificateStatusPending),
string(CertificateStatusProvisioning),
string(CertificateStatusRenewing),
},
"provisioning_status": string(CertificateStatusProvisioning),
"poll_lease_seconds": pollLease.Seconds(),
},
)
if err != nil {
return fmt.Errorf("cannot query certificate provisioning queue: %w", err)
return fmt.Errorf("cannot query certificate poll-order queue: %w", err)
}
certificate, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[Certificate])