Skip to content

Fix KIS rate-limit classification and share limiter per app-key#11

Merged
smallfish06 merged 2 commits intomainfrom
feat/error-logging
Apr 19, 2026
Merged

Fix KIS rate-limit classification and share limiter per app-key#11
smallfish06 merged 2 commits intomainfrom
feat/error-logging

Conversation

@smallfish06
Copy link
Copy Markdown
Owner

Summary

  • KIS returns HTTP 500 with msg_cd=EGW00201 for per-second TPS overruns rather than HTTP 429 — we classified those as ErrServerError and surfaced 502 to callers. Now inspected via a new internal/kis/error_mapping.go and mapped to ErrRateLimitExceeded → 429.
  • Each KIS adapter instantiated its own ratelimit.New(..., 15, 3) limiter, so N adapters talking to the same app-key collectively fired 15×N rps at KIS and tripped the upstream quota. Added ratelimit.Shared(name, rps, burst, key) and bound kis.Client to it in SetCredentials, so every Client using the same app-key cooperates on a single token bucket.
  • Moved apiLimiter.Wait from doRequest into doRequestOnce so the 401 refresh-and-retry path is also throttled.
  • Also includes structured logging for KIS proxy errors via slog (separate commit).

Test plan

  • go test ./... -count=1
  • TestClassifyUpstream covers EGW00201 masked as 500, plain 429, generic 500, 4xx, non-JSON bodies
  • TestShared_sameKeyReturnsSameLimiter + TestShared_serializesIndependentCallers verify the registry and cooperative rate behavior
  • Monitor prod KIS proxy error rate after deploy — expect EGW00201 → 429 (not 502) and overall rate-limit hits to drop once all adapters for the same app-key share one bucket

Out of scope

  • Cross-process limiter coordination (Redis-backed bucket) — only relevant if krsec is deployed as multiple instances sharing an app-key. Current fix covers the single-process case that matches the observed log.
  • Retry/backoff on 429 — belongs in the caller (e.g. kis-requester), not inside the proxy.

🤖 Generated with Claude Code

- Extract KIS upstream error classification into internal/kis/error_mapping.go
  so the HTTP transport no longer hard-codes vendor-specific msg codes. KIS
  returns HTTP 500 with msg_cd=EGW00201 for per-second TPS overruns rather
  than HTTP 429; classify those as ErrRateLimitExceeded so the proxy
  surfaces 429 to callers instead of 502.
- Add ratelimit.Shared(name, rps, burst, key) that returns one *Limiter
  per (name, key) pair. Bind kis.Client in SetCredentials so every Client
  that talks to the same KIS app-key cooperates on a single token bucket
  — previously each adapter instantiated its own 15 rps limiter, letting
  N adapters collectively exceed the upstream quota.
- Move apiLimiter.Wait into doRequestOnce so the 401 refresh-and-retry
  path is also throttled.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Introduced a new `DeleteToken(appKey string)` method in the `Manager` interface to allow token deletion.
- Implemented `DeleteToken` across `filetoken.Manager`, `kis`, and `kiwoom` token managers.
- Updated relevant tests to validate `DeleteToken` functionality.
}

func classifyUpstream(status int, body []byte) error {
switch parseMsgCode(body) {
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for further case, suppress lint

@smallfish06 smallfish06 merged commit 9cc87c3 into main Apr 19, 2026
4 of 5 checks passed
@smallfish06 smallfish06 deleted the feat/error-logging branch April 19, 2026 04:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant