토스페이먼츠 연동과 Rate Limit, Timeout

2026. 6. 23. 12:50·우아한테크벨로/Level2
728x90

이번 레벨의 마지막 선택 미션으로 이전에 창업 프로젝트를 진행하며 다루어보았던 '토스페이먼츠 결제 연동'을 다시 진행했습니다. 과거에 혼자 구현해 본 경험은 있지만 당시 부족했던 점들을 보완하고 더 깊이 학습하고자 선택했는데, 결과적으로 결제 시스템에 대해 훨씬 더 많은 것을 배울 수 있었습니다.

간단한 초기 연동 과정이 궁금하시다면, 이전에 작성해 둔 제 과거 포스팅(링크)을 참고해 주시면 좋습니다.

결제 위젯 연동 흐름

ref. 결제 위젯 연동하기, 결제 흐름

결제 과정은 크게 다음 4가지 핵심 단계로 이루어집니다.

 

  1. 렌더링 — 클라이언트가 renderPaymentWidget으로 결제 UI를 띄운다.
  2. 요청 — 사용자가 결제 버튼을 누르면 클라이언트가 requestPayment로 결제를 요청한다.
  3. 인증(결제 인증) — 사용자가 카드 정보를 입력/인증하고 카드사가 카드 소유권을 검증한다(사기 결제 방지).
  4. 승인(결제 승인) — 서버가 승인 API를 호출해 결제 금액과 주문 정보를 검증한 뒤 실제로 돈이 빠져나간다. 결제가 최종 완료되는 단계다.

클라이언트와 서버 역할 분담

결제 과정을 안전하게 처리하기 위해 클라이언트와 서버는 각자의 역할을 나누어 수행합니다.

클라이언트

  • 공개 가능한 clientKey를 사용하여 결제 위젯을 렌더링합니다.
  • 사용자의 결제 요청을 처리하고, 인증 결과에 따라 successUrl 또는 failUrl로의 리다이렉트를 담당합니다.

서버

  • 외부에 노출되면 안 되는 secretKey를 안전하게 관리합니다.
  • 사전 저장: 결제 요청 전, orderId와 최종 결제 금액을 데이터베이스에 미리 저장해 둡니다.
  • 데이터 검증: 사용자가 결제 인증을 마치고 successUrl로 리다이렉트될 때, 쿼리 파라미터로 paymentKey, orderId, amount를 전달받습니다. 서버는 전달받은 orderId와 amount가 사전에 저장해 둔 값과 일치하는지 대조합니다.
  • 승인 요청: 검증에 이상이 없다면, 최종적으로 토스페이먼츠 승인 API를 호출하여 결제를 확정합니다. secretKey를 사용하고, 결제전 orderId와 최종 amount를 미리 저장하고, 리다이렉트로 돌아온 값을 검증 해서 승인 API를 호출합니다.

※ paymentKey란? 토스페이먼츠가 발급하는 거래의 고유 식별자입니다. 추후 결제 조회나 결제 취소 시 반드시 필요한 핵심 데이터입니다.

결론

복잡해 보일 수 있는 결제 과정이지만, 사실 서버가 직접 코드로 호출해야 하는 API는 마지막 4단계의 결제 승인 API 단 하나 입니다. 결제창 렌더링, 요청, 인증 등 사용자와 맞닿아 있는 나머지 복잡한 과정은 모두 토스페이먼츠 위젯과 카드사에서 알아서 처리해 줍니다.

POST https://api.tosspayments.com/v1/payments/confirm

 

결제 API 연동 및 예외 핸들링

1. 결제 전 주문 정보 저장

결제 인증 전에 주문 정보를 먼저 저장합니다.(orderId, 최종 amount)

orderId는 서버가 생성하며(6~64자, 영숫자/-/_) 이후 금액 검증의 기준이 됩니다. 이 시점은 아직 구매가 확정된 것이 아닙니다.

2. 브라우저 결제창 연동

예약 페이지에 토스 결제창 SDK를 붙여 인증받습니다.(위젯 초기화는 clientKey를 사용합니다.)

카드 정보 입력·인증은 결제창과 카드사가 처리하며 서버는 카드번호를 절대 만지지 않습니다. 인증이 성공하면 토스가 successUrl로 paymentKey, orderId, amount를 넘깁니다.

3. successUrl 콜백 - 금액 검증 후 승인

콜백으로 넘어온 amount를 그대로 믿지 않고 주문 저장 금액과 대조합니다. 다르면 PaymentAmountMismatch류 예외로 승인 호출 전에 차단합니다. 토스엔 금액 불일치 전용 코드가 없어 서버가 직접 검증해야 합니다.

일치하면 승인 API를 호출합니다. 성공하면 이후 조회·취소에 필요한 paymentKey를 DB에 저장하고 구매 상태를 CONFIRMED로 바꿉니다.

4. 결제 승인 API 호출(RestClient)

POST https://api.tosspayments.com/v1/payments/confirm를 RestClient로 호출합니다.

  • 바디 3필드: paymentKey, orderId, amount (Content-Type application/json).
  • 인증은 Basic: base64(시크릿키 + ":")를 Authorization: Basic ...로 보냅니다(콜론 뒤 비밀번호는 비우고, 인코딩 시 UTF-8 명시).
  • 시크릿 키(test_sk_)는 노출/하드코딩 금지 — application.yaml 등으로 외부화합니다. 시크릿 키는 서버 승인 전용입니다(클라이언트 키와 역할이 다름).

5. 관심사 분리 — 포트 & 어댑터

도메인/애플리케이션 계층에 PaymentGateway 포트와 도메인 모델(PaymentConfirmation, PaymentResult)을 두고, PaymentService는 Toss와 Toss DTO를 몰라야 합니다. Toss DTO(요청/응답/에러) ↔ 도메인 모델 번역은 어댑터 TossPaymentGateway(부패 방지 계층, ACL)가 맡습니다. PG사를 바꿔도 어댑터만 새로 만들면 되고 도메인은 그대로입니다.

6. 에러 응답을 도메인 예외로 매핑

onStatus(HttpStatusCode::isError, 핸들러)로 4xx/5xx를 가로챕니다. 핸들러에서 본문을 TossErrorResponse({code, message})로 역직렬화한 뒤 도메인 예외로 변환합니다. 변환은 어댑터 안에서 일어나고 Toss DTO는 밖으로 새지 않습니다. 변환한 예외는 사용자 응답으로도 의미 있게 이어집니다(카드 거절은 안내, 키 오류는 알람 등). code별 분기 방향은 자기 서비스에 맞게 설계합니다.

정확한 목록은 Toss Payments 에러 코드의 "결제 승인" 섹션을 참고합니다.

7. failUrl(취소/실패) 처리

failUrl로 code, message, orderId가 넘어옵니다. 실패 사유를 사용자에게 보여주고 결제 대기 상태의 주문/예약을 정리합니다. 단, 사용자가 취소(PAY_PROCESS_CANCELED)하면 orderId가 없을 수 있으니 null 가드를 둡니다.

 

이제까지는 결제 연동과 예외를 핸들링 하는 법을 봤습니다. 그 다음은 타임아웃에 대해서 볼려고 합니다. 위의 내용들은 이전에도 구현을 해보면서 다뤘던 내용들이었기에 어렵지 않았습니다. 하지만 이 다음에 다루는 타임아웃과 rate limit은 저에게는 새로운 내용이었고, 유의미한 학습이었습니다.

Timeout 제어하기

토스페이먼츠와 같은 외부 API를 연동할 때 타임아웃(Timeout)을 적절히 제어하지 않으면 시스템에 치명적인 문제가 발생할 수 있습니다. 그 이유는 크게 두 가지입니다.

  1. 스레드 풀 고갈 방지: 토스 서버의 응답이 느려지면 해당 요청을 처리하는 우리 서버의 스레드도 무한정 대기하게 됩니다. 이는 결국 스레드 풀 고갈로 이어져, 결제와 무관한 다른 사용자들의 요청까지 모두 마비시키는 도미노 현상을 일으킵니다.
  2. 불확실한 상태와 이중 결제 위험: Read Timeout으로 실패한 결제는 '실패한 것'이 아니라 '성공 여부를 알 수 없는 상태'입니다. 이미 토스 쪽에서는 승인이 완료되었는데 우리 서버가 응답만 받지 못한 것일 수도 있습니다. 이 상태에서 무작정 재시도하면 사용자에게서 돈이 두 번 빠져나가는 이중 승인(Double Charge) 문제가 발생합니다.

결국 우리 서버의 스레드를 보호하기 위해 '타임아웃'을 설정해야 하고, 타임아웃 발생 시 이중 결제 없이 안전하게 재시도(Retry)하기 위한 방어책이 추가로 필요한데, 이때 등장하는 개념이 바로 Idempotency Key(멱등키)입니다.

안전한 재시도를 위한 방어막, Idempotency Key란?

Idempotency(멱등성)란 '동일한 연산을 여러 번 수행해도 결과가 달라지지 않는 성질'을 의미합니다.

그리고 Idempotency Key(멱등키)는 API 요청이 여러 번 발생하더라도 서버에서 단 한 번만 안전하게 처리되도록 보장하기 위해 클라이언트가 요청에 함께 실어 보내는 고유한 식별자(보통 UUID 등)입니다. 주로 토스페이먼츠나 스트라이프 같은 글로벌 결제 API에서 요청 헤더(Idempotency-Key) 형태로 필수적으로 사용됩니다.

멱등키는 왜, 어떻게 이중 승인을 막아줄까?

앞서 언급한 타임아웃 상황을 예시로 멱등키의 필요성과 동작 원리를 살펴보겠습니다.

  • 상황 발생 : 결제 승인 요청을 보냈으나 네트워크 문제로 타임아웃이 발생해 결제 성공 여부가 불확실해졌습니다.
  • 멱등키가 없는 경우의 참사 : 상태를 모르니 일단 재시도를 합니다. 만약 첫 번째 요청이 토스 서버에서 이미 성공해 돈이 빠져나간 상태였다면, 두 번째 요청으로 인해 이중 승인 피해가 발생합니다.
  • 멱등키가 있는 경우의 안전한 처리 (동작 원리) :
    1. 첫 번째 요청 : Idempotency-Key: 고유ID_001을 담아 요청합니다. 토스 서버는 결제를 정상 처리하고 그 결과를 캐시에 저장합니다.
    2. 재시도 요청 : 타임아웃이 발생하여 불안한 마음에 동일한 Idempotency-Key: 고유ID_001을 담아 다시 요청합니다.
    3. 중복 차단 : 토스 서버는 "어? 아까 고유ID_001로 이미 처리한 요청이네?"라고 인지합니다. 그리고 실제로 돈을 또 출금하는 것이 아니라, 아까 저장해 두었던 첫 번째 요청의 성공 응답을 그대로 복사해서 반환해 줍니다.

결론적으로, 외부 결제 API를 호출할 때는 적절한 타임아웃(Timeout) 설정으로 우리 인프라를 보호하고, 멱등성(Idempotency) 키를 활용해 불확실한 상태에서도 이중 결제 없이 안전하게 재시도할 수 있는 두 가지 방어벽을 동시에 구축해야 합니다.

타임아웃을 설정하는 방법은 두 가지가 있습니다.

 

1. application 프로퍼티를 지정하여 타임아웃을 설정하는 방식

spring:
  http:
    clients:
      connect-timeout: 2000 # 연결 제한 시간
      read-timeout: 5000    # 응답 대기 제한 시간
      imperative:
        factory: simple   # jdk는 read timeout을 못 잡으므로 반드시 simple 또는 apache로 설정

 

2. 코드를 통해서 Bean 등록으로 RestClient의 SimpleClientHttpRequestFactory를 정의하는 방식

    @Bean
    public RestClient tossRestClient(
            @Value("${toss.base-url}") String baseUrl,
            @Value("${toss.secret-key}") String secretKey,
            @Value("${toss.connect-timeout-ms}") int connectTimeoutMs,
            @Value("${toss.read-timeout-ms}") int readTimeoutMs
    ) {
        String basic = Base64.getEncoder()
                .encodeToString((secretKey + ":").getBytes(StandardCharsets.UTF_8));

        SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
        factory.setConnectTimeout(connectTimeoutMs);
        factory.setReadTimeout(readTimeoutMs);

        return RestClient.builder()
                .baseUrl(baseUrl)
                .defaultHeader(HttpHeaders.AUTHORIZATION, "Basic " + basic)
                .requestFactory(factory)
                .build();
    }

 

Rate Limit 대응

앞서 타임아웃과 멱등키를 통해 '느린 응답'과 '불확실한 상태'로부터 시스템을 보호했습니다. 하지만 서버가 안전하려면 한 가지 더 고려해야 할 것이 있습니다. 바로 과도하게 몰리는 트래픽입니다.

우리 서버는 결제 과정에서 두 가지 형태의 Rate Limit(호출량 상한)을 마주하게 됩니다.

  1. 들어오는 요청 (Inbound): 우리 서버가 감당할 수 있는 트래픽의 한계 (클라이언트 → 우리 서버)
  2. 나가는 요청 (Outbound): 외부 API(토스페이먼츠)가 우리에게 허용한 호출의 한계 (우리 서버 → 토스 서버)

이를 제어하기 위해 토큰 버킷(Token Bucket) 알고리즘을 도입하여 양방향 방어선을 구축해 보았습니다.

핵심 알고리즘: 토큰 버킷(Token Bucket)

토큰 버킷 알고리즘은 크게 두 가지 속성으로 작동합니다.

  • Capacity (허용 버스트): 버킷에 담을 수 있는 최대 토큰 수입니다. 이 값을 키우면 트래픽이 순간적으로 확 몰릴 때(Burst) 한 번에 허용할 수 있는 최대 요청량이 늘어납니다.
  • RefillPerSec (평균 TPS 상한): 1초마다 버킷에 보충되는 토큰의 수입니다. 이 값을 키우면 일정 시간 동안 받아낼 수 있는 전반적인 평균 처리량(TPS)이 증가합니다.

요청이 들어올 때마다 토큰을 1개씩 소비하며, 토큰이 부족하면 해당 요청은 429 Too Many Requests 에러와 함께 거부됩니다.

public class TokenBucketRateLimiter {

    private final long capacity;        // 버킷 최대 용량(버스트 허용량)
    private final double refillPerSec;  // 초당 보충 토큰 수 = 허용 TPS 상한
    private final LongSupplier nanoClock;

    private double availableTokens;
    private long lastRefillNanos;

    public TokenBucketRateLimiter(long capacity, double refillPerSec, LongSupplier nanoClock) {
        this.capacity = capacity;
        this.refillPerSec = refillPerSec;
        this.nanoClock = nanoClock;
        this.availableTokens = capacity;            // 시작은 가득 찬 상태
        this.lastRefillNanos = nanoClock.getAsLong();
    }

    /**
     * 토큰이 있으면 1개 소비하고 true, 없으면 false.
     */
    public synchronized boolean tryConsume() {
        // TODO: refill() 후 토큰이 1개 이상이면 1개 소비하고 true, 아니면 false.
        refill();
        if (availableTokens >= 1) {
            availableTokens -= 1;
            return true;
        }
        return false;
    }

    /**
     * 다음 요청이 통과 가능해질 때까지 권장 대기 시간(초). 토큰이 충분하면 0.
     */
    public synchronized long retryAfterSeconds() {
        // TODO: refill() 후 토큰이 1개 이상이면 0, 부족하면 1개가 찰 때까지 필요한 초를 올림으로 반환한다.
        refill();
        if (availableTokens >= 1) {
            return 0;
        }
        return (long) Math.ceil((1 - availableTokens) / refillPerSec);
    }

    /**
     * 마지막 보충 이후 경과 시간에 비례해 토큰을 보충한다(상한 capacity).
     */
    private void refill() {
        // TODO: 경과 시간(nanoClock - lastRefillNanos)에 비례해 토큰을 보충하고(상한 capacity), lastRefillNanos 를 갱신한다.
        long now = nanoClock.getAsLong();
        double elapsedSec = (now - lastRefillNanos) / 1_000_000_000.0; // (현재 시간 - 마지막 충전 시간)
        availableTokens = Math.min(capacity, availableTokens + elapsedSec * refillPerSec);
        lastRefillNanos = now;
    }

}

첫 번째 방어: 들어오는 요청 제어 (Inbound Rate Limit)

모든 클라이언트의 요청이 토스 API 호출로 이어지는 것은 아닙니다. 따라서 서버가 결제 외의 다른 정상적인 요청들을 처리하는 데 병목이 생기지 않도록, 우리 서버 자원의 한도에 맞춰 인바운드 Rate Limit을 설정해야 합니다.

요청이 버킷의 한도를 초과하면, 게이트웨이(HandlerInterceptor) 단에서 컨트롤러 로직을 타기 전에 즉시 요청을 차단합니다. 이때 무작정 거부하는 것이 아니라, 언제 다시 요청하면 되는지를 알려주는 Retry-After 헤더에 남은 시간을 담아 429 응답을 내려줍니다.

public class RateLimitInterceptor implements HandlerInterceptor {

  private final TokenBucketRateLimiter rateLimiter;

  public RateLimitInterceptor(TokenBucketRateLimiter rateLimiter) {
    this.rateLimiter = rateLimiter;
  }

  @Override
  public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
    if (!rateLimiter.tryConsume()) {
      response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
      response.setHeader(HttpHeaders.RETRY_AFTER, String.valueOf(rateLimiter.retryAfterSeconds()));
      return false;
    }
    return true;
  }

}

두 번째 방어: 나가는 요청 제어와 429 재시도 (Outbound Rate Limit)

토스페이먼츠 역시 자신들의 서버를 보호하기 위해 Rate Limit을 걸어둡니다. 이 한도를 초과해 토스 서버로 요청을 보내면 토스 역시 우리에게 429 에러를 반환합니다.

이를 현명하게 다루기 위해 클라이언트(RestClient) 관점에서도 방어 로직을 추가했습니다.

 

1. 보내기 전에 스스로 조절하기 (Fail-Fast)

한도를 넘겨서 토스에 요청해 보았자 어차피 429로 거부당할 것이 뻔합니다. 따라서 동일한 토큰 버킷을 적용해, 우리 쪽에서 나가는 호출량이 상한을 넘으면 아예 외부 네트워크로 요청을 보내지 않고 즉시 예외(OutboundRateLimitException)를 던지도록 했습니다.

public class OutboundRateLimitInterceptor implements ClientHttpRequestInterceptor {

  private final TokenBucketRateLimiter rateLimiter;

  public OutboundRateLimitInterceptor(TokenBucketRateLimiter rateLimiter) {
    this.rateLimiter = rateLimiter;
  }

  @Override
  public ClientHttpResponse intercept(
      HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
    if (!rateLimiter.tryConsume()) {
      throw new OutboundRateLimitException(HttpStatus.TOO_MANY_REQUESTS, "LOCAL_RATE_LIMIT_EXCEEDED", "나가는 호출이 자체 Rate Limit을 초과해 외부로 보내지 않았습니다.");
    }
    return execution.execute(request, body);
  }

}

위 과정에서 왜 요청을 대기(Blocking)시키지 않고 즉시 거부(Fail-Fast)할까?라는 의문이 생길 수 있습니다.

토큰이 찰 때까지 스레드를 대기시키면 흐름은 매끄러워 보일 수 있습니다. 하지만 트래픽이 몰릴 경우, 대기 중인 요청들이 서버의 스레드를 계속 점유하게 되어 스레드 풀 고갈이 발생합니다. 결국 외부 API와 무관한 다른 요청들까지 처리하지 못하고 서버 전체가 마비될 위험이 커지므로 즉시 거부하는 것이 안전합니다.

 

2. 429 응답을 받았을 때의 안전한 재시도 (Backoff Retry)

만약 토스 측에서 429 Too Many Requests 에러와 함께 Retry-After 헤더를 보내왔다면, 해당 시간만큼 대기(Backoff)한 후 재시도를 수행합니다. (Retry-After가 없다면 1초 등 짧은 고정 간격으로 폴백합니다).

public class RetryAfterInterceptor implements ClientHttpRequestInterceptor {

  private static final long DEFAULT_RETRY_AFTER_SECONDS = 1L;

  private final int maxAttempts;

  public RetryAfterInterceptor(int maxAttempts) {
    this.maxAttempts = maxAttempts;
  }

  @Override
  public ClientHttpResponse intercept(
      HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
    ClientHttpResponse response = execution.execute(request, body);
    int attempt = 1;
    while (isTooManyRequest(response) && attempt < maxAttempts) {
      long waitSecond = parseRetryAfterSeconds(response);
      response.close();
      sleepSeconds(waitSecond);
      response = execution.execute(request, body);
      attempt++;
    }
    return response;
  }

  private boolean isTooManyRequest(ClientHttpResponse response) throws IOException {
    return response.getStatusCode().value() == HttpStatus.TOO_MANY_REQUESTS.value();
  }

  private long parseRetryAfterSeconds(ClientHttpResponse response) {
    var value = response.getHeaders().getFirst(HttpHeaders.RETRY_AFTER);
    if (value == null) {
      return DEFAULT_RETRY_AFTER_SECONDS;
    }
    try {
      return Math.max(0, Long.parseLong(value.trim()));
    } catch (NumberFormatException e) {
      return DEFAULT_RETRY_AFTER_SECONDS;
    }
  }

  private void sleepSeconds(long seconds) {
    try {
      Thread.sleep(seconds * 1000L);
    } catch (InterruptedException e) {
      Thread.currentThread().interrupt();
      throw new IllegalStateException("재시도 대기 중 인터럽트되었습니다.", e);
    }
  }

}

Timeout 재시도와 429 재시도의 차이점

  • Timeout 재시도: "요청이 정상 처리되었는지 모름". 이중 결제 위험이 크기 때문에 반드시 멱등키가 필요합니다.
  • 429 재시도: "요청이 아직 처리되지 않았음"이 명확함. 다시 보내도 안전하지만, Timeout 등 다른 불확실한 예외가 발생할 가능성을 대비해 동일한 멱등키를 유지한 채 재시도하는 것이 안전장치입니다.

이렇게 직접 interceptor를 정의해서 재시도를 관리 할 수도 있지만, @Retryable 어노테이션을 통해서 간단하게 관리 할 수도 있습니다.

하지만 두 가지를 동시에 사용하는 것은 재시도 횟수 증복의 위험이 있습니다. 그 이유는 interceptor에서 3번 재시도하도록 설정하고, @Retrayable에서도 3번 재시도하도록 설정했다면, 3번의 실패마다 다시 3번씩 재시도를 시도하게 되어 총 9번의 의도치 않은 중복 요청이 발생합니다.

    @Retryable(
            retryFor = TossInfrastructureException.class,
            maxAttempts = 3,
            backoff = @Backoff(delay = 1000, multiplier = 2)
    )
    public PaymentResult confirm(final PaymentConfirmation confirmation) {
        return executeWithHandling(() -> {
            TossPaymentResponse response = tossRestClient.post()
                    .uri("/v1/payments/confirm")
                    .contentType(MediaType.APPLICATION_JSON)
                    .header("Idempotency-Key", confirmation.orderId())
                    .body(ConfirmRequest.builder()
                            .paymentKey(confirmation.paymentKey())
                            .orderId(confirmation.orderId())
                            .amount(confirmation.amount())
                            .build())
                    .retrieve()
                    .onStatus(HttpStatusCode::isError, (req, res) -> handleTossError(res.getStatusCode(), res.getBody()))
                    .body(TossPaymentResponse.class);
            return toResult(response);
        }, "결제 승인");
    }

마치며: Rate Limit과 서킷 브레이커(Circuit Breaker)

이번 구현을 통해 트래픽의 '호출량(Throughput)'을 제어하여 우리 서버와 토스 서버를 모두 보호하는 방법을 배웠습니다.

하지만 Rate Limit은 '상대 서버가 정상적으로 살아있다'는 전제하에 과도한 요청을 막는 수단입니다. 만약 토스 서버에 장애가 생겨 연속으로 5xx 에러나 Timeout이 발생한다면 어떨까요? 이때는 요청량과 무관하게 통신 자체를 잠시 끊어버리는 서킷 브레이커(Circuit Breaker)가 필요합니다.

서킷 브레이커를 통해 불필요한 실패 요청을 중단시키면 우리 서버의 스레드도 보호하고, 장애가 발생한 상대 서버에게도 회복할 시간을 벌어줄 수 있습니다. 이는 Rate Limit과는 결이 다른 방어망이므로, 추후 시스템의 안정성을 더 높이기 위해 꼭 다루어보고 싶은 주제입니다.

728x90

'우아한테크벨로 > Level2' 카테고리의 다른 글

Level 2 전체 회고  (0) 2026.06.11
『오브젝트』 저자 조영호 개발자님 초청 강연 회고  (0) 2026.05.10
'우아한테크벨로/Level2' 카테고리의 다른 글
  • Level 2 전체 회고
  • 『오브젝트』 저자 조영호 개발자님 초청 강연 회고
Bello's
Bello's
개발하는 벨로
  • Bello's
    벨로의 개발일지
    Bello's
  • 전체
    오늘
    어제
    • 분류 전체보기 (205)
      • 노예 일지 (7)
        • 스타트업 노예일지 (3)
      • CS 이론 (81)
        • 학과 수업 (4)
        • 알고리즘 (64)
        • 시스템 프로그래밍 (3)
        • 데이터 통신 (1)
        • 운영체제 (2)
        • 데이터베이스 (1)
      • project (3)
      • 나는 감자다. (4)
      • Spring (27)
      • 모각코 (45)
        • 절개와지조(모각코) (7)
        • 어쩌다보니 박준태가 조장이조 (11)
        • 어쩌다보니 박준태가 또 조장이조 (12)
      • LikeLion🦁 (20)
      • 캘리포니아 감자 (4)
      • OpenSource Contribute (1)
      • 우아한테크벨로 (13)
        • 프리코스 회고록 (6)
        • Level 1 (2)
        • Level2 (3)
        • Level3 (1)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    우테코
    절개와지조
    오블완
    JPA
    Spring
    나는 감자
    감자
    타임리프
    뛰슈
    어렵다
    백준
    자바
    프리코스
    누적합
    DFS
    그래프 순회
    BFS
    회고록
    모각코
    8기
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.5
Bello's
토스페이먼츠 연동과 Rate Limit, Timeout
상단으로

티스토리툴바