수천 개의 API/BATCH 서버를 하나의 설정 체계로 관리하기
오타를 찾아보세요
# 설정 1
env:
JVM_OPTION: "-Xmx1024m -XX:+UseG1GC -XX:G1HeapRegionSize=8m ..."
# 설정 2
env:
JVM_OPTION: "-Xmx2048m -XX:+UseG1GC -XX:G1HeapRegionSiez=8m ..."자, 여기 두 개의 설정이 있습니다. 이 중에서 다른 점을 찾아보세요. 금방 찾으셨나요? 정답은 설정 2 의 G1HeapRegionSiez 부분이었습니다. 만약 이런 설정이 2,000개가 넘는다면 어떨까요? 실수하기 너무 좋은 상황이 될 것 같습니다.
여러분은 배치 서버를 몇 개까지 운영해 보셨나요? 사실 각각의 문자열은 배치 설정이었고, 설정 2 는 오타가 존재하는 잘못 설정된 배치였습니다. 더 큰 문제는, 이 오타 설정을 가진 배치가 무려 50억 규모의 돈을 정산하는 배치였다는 점입니다.
설정 중복은 인프라 장애로 이어진다
안녕하세요. 토스페이먼츠 서버플랫폼 팀 리더 나재은입니다.
저희 서버플랫폼 팀은 Kubernetes, AWS, 사내 운영 도구 등 개발자와 가까운 곳에서 정책을 만들고 인프라를 운영하고 있습니다. 매일 수천억 원을 정산하는 수천 개의 배치 설정과, 수천 개의 API 서버 설정을 관리해야 합니다.
수천 개의 API 서버 설정이 대부분 비슷하겠죠. 그러나 조금씩은 다르다면 어떻게 해야 할까요? 코드 중복이 버그로 이어지듯, 설정 중복은 인프라 장애로 이어집니다.
단 하나의 설정 체계로, AWS 및 IDC에 있는 하이브리드 클라우드 환경에서 수천 개 서버를 한 번에 제어할 수 있다면 어떨까요? 중복 없이 테스트 가능한 설정 체계가 있으면, 이 문제를 해결할 수 있습니다.
오늘은 토스페이먼츠 서버플랫폼 팀이 '설정' 하나로 얼마나 깊이 고민하고 문제를 해결해왔는지 공유하고자 합니다. 저희는 설정도 코드 개발처럼 바라보고 설계하고 있습니다.
실시간 API 서버 설정, 배치 설정, 이렇게 두 부분으로 나눠서 경험을 풀어드릴게요.
Part 1: 실시간 API 서버 설정
문제: 복사-붙여넣기의 악순환
DevOps의 첫 번째 고객은 역시 개발자죠. 설정은 결국 개발자의 요구사항들이 모이는 지점입니다.
예를 들어:
- 모든 서버에 공통 환경 변수를 넣고 싶다
- Java Heap 메모리를 올리고 싶다
과거에는 복잡한 설정 YAML을 복사해서 붙여넣는 방식으로 대응했어요. 처음에는 빠르고 좋아 보였지만, 시간이 지나면서 설정은 점점 복잡해졌고 실제로 실수도 많이 했습니다. 결국엔 인프라 장애로 이어질 뻔한 상황도 있었죠.
해결책 1: 오버레이 아키텍처

API 서버 설정에서 저희가 선택한 해결책은 크게 두 가지인데요, 그중 첫 번째는 오버레이 아키텍처 입니다.
오버레이 아키텍처는 설정값들을 조합 가능한 계층 형태로 구성한 뒤, 하나의 결과물로 해석하는 방법입니다. 저희는 각 계층을 global, cluster, phase 이런 순서대로 나누었습니다. 아래에 있을수록 우선순위가 높습니다. 따라서 맨 위에 있는 계층은 기본값처럼 동작하게 됩니다.
계층별 역할
저희가 정의한 계층들은 각 요구사항에 맞게 정의되어 있습니다. 각 계층의 예시는 아래와 같습니다.
이러한 계층은 개발자의 요구사항에 따라 계속 추가될 수 있습니다.
서버는 이 설정 레이어를 어떻게 인식할까요? 모든 설정을 얇게 여러 개 겹쳐놓고, 아래에서 바라봅니다. 서버는 아래에서 올려다보고, 자신의 위치와 가장 가까운 설정값을 인식하는 거죠.
실제로 사용중인 설정 디렉토리 구조
applications # application 구성 예시
│
├── my-service
│ ├── dev-api-01 # dev api 1호기
│ │ ├── dev.json
│ │ └── values.yaml
│ ├── dev-api-02 # dev api 2호기
│ │ ├── dev.json
│ │ └── values.yaml
│ ├── dev-consumer # dev consumer
│ │ ├── dev.json
│ │ └── values.yaml
│ ├── dev-batch # dev batch
│ │ ├── dev.json
│ │ └── values.yaml
│ ├── staging-api
│ │ ├── prod.json
│ │ └── values.yaml
│ ├── live-api # live api
│ │ ├── prod.json
│ │ └── values.yaml
│ ├── docs # 문서 배포는 dev 환경에만
│ │ ├── dev.json
│ │ └── values.yaml
│ └── values-app-common.yaml # my-service 전체에서 사용되는 공통 설정
│
├── values-apptype-api-common.yaml # api 서버 공통 설정
├── values-apptype-consumer-common.yaml # consumer 서버 공통 설정
├── values-cluster-dev-1-common.yaml # dev H/A 클러스터 1
├── values-cluster-dev-2-common.yaml # dev H/A 클러스터 2
├── values-cluster-live-1-common.yaml # live H/A 클러스터 1
├── values-cluster-live-2-common.yaml # live H/A 클러스터 2
├── values-cluster-live-3-common.yaml # live H/A 클러스터 3
├── values-cluster-live-4-common.yaml # live H/A 클러스터 4
├── values-global-common.yaml # global 공통 설정
├── values-phase-dev-common.yaml # dev 환경 공통 설정
├── values-phase-sandbox-common.yaml # sandbox 환경 공통 설정
├── values-phase-staging-common.yaml # staging 환경 공통 설정
└── values-phase-live-common.yaml # live 환경 공통 설정각 파일이 관련된 계층에 1:1 대응하도록 구성되어 있습니다. applications 디렉토리에 각 서비스들이 존재하며, app 설정들이 여기에 들어갑니다.
app 이외의 설정들은 네이밍 컨벤션을 통해 정의됩니다. phase 계층의 경우 여러 개가 존재할 수 있기 때문에, CI/CD 과정에서 현재 환경에 맞는 값이 선택되어 적용됩니다.
CI/CD 에서도 이 설정을 바라보도록 통합했기 때문에, "개발 환경에서는 카나리 배포를 생략해"와 같은 동작도 모두 한 곳에서 제어할 수 있습니다.
오버레이가 만능은 아니다
오버레이 아키텍처, 상당히 멋있죠? 그러나 아쉽게도 오버레이 아키텍처만으로는 모든 것을 해결할 수 없습니다.
예를 들어, 특정 환경 변수의 값이 너무 길다면 어떨까요? YAML은 key-value 단위로 구분하기 때문에, 값이 배열인 경우 중복을 제거하기 어렵습니다.
예를 들어, JVM_OPTION에서 Heap 메모리만 수정하고 싶은데 전체를 모두 다시 정의해야 하는 문제가 발생합니다. 이러면 힙 메모리 설정만 다른 수백 개의 JVM_OPTION 환경 변수가 생기게 됩니다. 위험하죠.
# 문제 상황: Heap 메모리만 바꾸고 싶은데...
env:
JVM_OPTION: "-Xmx1024m -XX:+UseG1GC -XX:G1HeapRegionSize=8m ..."
# 매번 전체를 다시 작성해야 한다
env:
JVM_OPTION: "-Xmx2048m -XX:+UseG1GC -XX:G1HeapRegionSize=8m ..."해결책 2: 템플릿 패턴
이 문제를 해결하기 위해, 템플릿 패턴을 도입했습니다. JVM_OPTION 같은 문자열 내부에, 중괄호를 활용해 값을 채워 넣을 수 있게 만들었습니다.
가장 간단한 경우
# global 계층
env:
JVM_OPTION: "-Xmx{{MAX_HEAP}}m -XX:+UseG1GC"
MAX_HEAP: 1024
# my-service 계층
env:
MAX_HEAP: 2048
# 최종 결과
env:
JVM_OPTION: "-Xmx2048m -XX:+UseG1GC"
MAX_HEAP: 2048이제 JVM_OPTION의 값은 일종의 템플릿처럼 동작하며, my-service라고 하는 애플리케이션에서는 이제 최대 2048메가의 힙 메모리가 할당됩니다.
오버레이와 템플릿 조합
템플릿도 여전히 설정이기 때문에, 오버레이 아키텍처 위에서 동작합니다.
# global 계층
env:
JVM_OPTION: "-Xmx{{MAX_HEAP}}m -XX:+UseG1GC -XX:G1HeapRegionSize={{REGION_SIZE}}m"
MAX_HEAP: 1024
REGION_SIZE: 8
# live 환경 (phase 계층)
env:
MAX_HEAP: 4096
REGION_SIZE: 16
# 최종 결과
env:
JVM_OPTION: "-Xmx4096m -XX:+UseG1GC -XX:G1HeapRegionSize=16m"
MAX_HEAP: 4096
REGION_SIZE: 16템플릿 자체도 각 계층에 따라 우선순위가 다르게 해석되며, 최종적으로는 모든 값이 조합됩니다.
더 나아가기: 설정에 코드를 넣기
설정은 코드로서 해석할 수도 있습니다. 빌드 과정에서 필요한 파이썬 스크립트를 주입하여, 랜덤한 값을 선택하거나 동적으로 값을 가져올 수도 있습니다.
# 설정 파일 내부에 Python 코드 삽입
import random
jmx_port_random = random.randint(10000, 20000)
my_remote_config = fetch_from_remote_api()선언된 jmx_port_random, my_remote_config 같은 변수들은 이제 설정값으로 사용할 수 있습니다. 물론 템플릿에서도 변수로 사용할 수 있죠.
더 나아가기: 조건부로 값 적용하기
이뿐만이 아닙니다. 로직을 넣기 까다로운 배열 내부에는 조건부 적용을 수행하는 특별한 설정을 추가했습니다.
common:
phase:
env:
- name: PROFILE
value: live
- name: SPRING_PROFILES_INCLUDE
value: live-pg-aws
onTargetClusterNames: [live-1, live-2]
- name: SPRING_PROFILES_INCLUDE
value: live-pg-dc1
onTargetClusterNames: [live-3, live-4]위의 설정은, 서버가 적용되는 클러스터가 서로 다른 경우, SPRING_PROFILES_INCLUDE 이라는 환경변수의 값이 조건부로 각각 달라진다는 의미입니다.
핵심: 계속 진화할 수 있는 구조

그렇다면 제가 소개드린 기능 중 가장 강력한 것은 무엇일까요? 딱 하나만 꼽으라면 뭘까요? 어떤 요구사항이든 무엇이든 구현 가능한 그런 기능이요.
이 설정 체계의 가장 강력한 기능은 바로 ‘진화’입니다.
사실 오버레이 아키텍처도, 템플릿 패턴도 처음부터 있었던 건 아닙니다. 인프라가 변하고 요구사항이 추가되면서, 단순한 YAML 덩어리에서 동적인 설정 인프라로 진화시켜나간 결과예요.
제가 초반에 "설정도 코드 개발처럼 바라보고 설계한다"고 말씀드렸던 것, 기억나시나요? 그 말처럼, 토스페이먼츠의 API 설정 인프라는 이후 더 많은 요구사항이 추가되더라도 그에 맞춰 유연하게 적응할 수 있게 되었습니다.
Part 2: 배치 서버 설정
한 달에 수조 원!
토스페이먼츠의 배치 서버는 매일 수십억 원의 거래 정산을 담당하고 있습니다. 이런 구조에서는 작은 설정 하나만 잘못돼도 정산이 누락되거나 금액이 잘못 계산되는 치명적인 장애로 이어질 수 있어요.
항공모함 하나 가격이 수조 원 정도 한다고 합니다. 토스페이먼츠 배치 인프라는 매달 항공모함 하나를 구매할 수 있는 돈을 매일매일 다루는 거죠!
적정 기술! Simple Is the Best!
결론부터 말씀드리면, 이겁니다: "단순한 게 최고다."
이렇게 위험이 큰 도메인일수록, 복잡하기보다 단순한 인프라 구조가 더 강력합니다.
저희는 여러 배치 솔루션을 검토했지만, 최종적으로는 Jenkins를 선택했습니다. 물론 Jenkins가 완벽한 솔루션은 아닙니다. 하지만 토스페이먼츠의 배치 로직을 제어하는 용도로는 충분하다고 판단했습니다. 학습 곡선도 꽤 낮고요.
혹시 이와 관련된 내용이 더 궁금하시다면, 토스페이먼츠 ’레거시 정산 개편기’도 꼭 읽어보세요. 제 글은 인프라 개선이지만, 레거시 정산 개편기는 애플리케이션 도메인단의 개선이에요. 또 다른 재미가 있으실 겁니다.
배치 설정에서 해결해야 하는 문제

문제 1: 개발자의 반복 작업
Jenkins는 GitOps를 잘 지원하지 않는 단점이 있습니다. 개발자는 Jenkins Web UI를 통해 매번 bash script를 직접 입력해야 했고, 어디에 Java가 설치되어 있는지 알기 어려웠으며, 심지어는 장비마다 설치된 Java 버전까지 조금씩 달랐습니다.
장비 환경이 다른 것 뿐만 아니라 Jenkins Job마다 Java를 실행하는 스크립트도 조금씩 달랐어요. 뭐가 잘 안되면 저희 팀이 처음부터 끝까지 모든 실행 흐름을 하나씩 점검해가며 해결을 해야 했고, 그럴 때 마다 저희는 눈물을 흘렸습니다.
그래서 저희는 ’선언만 하면 사용할 수 있는’ 공통 구조를 만들기로 했습니다.
문제 2: 메모리 부족
Jenkins 워커 장비의 메모리 부족 문제도 있었죠. 하나의 Jenkins 워커에 몇 개의 프로세스를 실행해야 적절한 리소스 활용일까요? 복수 개의 프로세스가 장비의 리소스를 동시에 사용하는 시간이 존재하기 때문에, 이때 메모리 부족으로 인한 프로세스 비정상 종료가 가끔씩 발생했습니다.
해결책 1: Job-DSL 플러그인 어댑터

첫 번째 솔루션은 ‘Job-DSL 플러그인 어댑터 구현’이고, 이는 개발자의 반복적인 설정 작업을 해결합니다.
Job-DSL은 Groovy 코드로 Jenkins Job을 정의할 수 있게 해주는 Jenkins 플러그인입니다. 마치 쿠버네티스와 동일하죠. 이 플러그인을 그대로 사용하면, 복잡한 Jenkins 내부 구조와 설정 계층들을 이해해야만 합니다.
저희는 이 플러그인을 한 번 감싸서, 개발자가 Jenkins를 쉽게 다룰 수 있도록 만들었습니다. 토스페이먼츠 개발자들은 Jenkins를 잘 몰라도, 단순히 Groovy 함수 호출만 할 수 있으면 사용할 수 있게 되었습니다.
어댑터 예시: JavaRunJobBuilder
어댑터의 실체는 일종의 빌더 패턴으로 만든 선언형 클래스입니다.
import com.tosspayments.jenkinsconfigs.JavaRunJobBuilderV2
import com.tosspayments.jenkinsconfigs.JavaVersion
import com.tosspayments.jenkinsconfigs.sharedlibrary.JobNames
import com.tosspayments.jenkinsconfigs.sharedlibrary.NodeLabel
import com.tosspayments.jenkinsconfigs.sharedlibrary.MonitoringChannel
// 이렇게만 선언하면, Jenkins UI에 "MyFirstJenkinsBatch" 이라는 이름의 Job이 생성됩니다
new JavaRunBatchJobBuilder()
.jobName("MyFirstJenkinsBatch")
.jobWorkerNodeSpecification(NodeLabel.runnerDynamicallyProvisioned_cpu8_mem16g_default) // Job은 이 장비를 **독점해서** 사용할 수 있습니다.
.jobParameters({
dateParameterDefinition { // Jenkins 에 별도로 설치된 custom plugin 까지 같이 사용할 수 있습니다
name("InputDateParam")
dateFormat("yyyy-MM-dd")
defaultValue("LocalDate.now()")
description("날짜를 입력해주세요")
}
stringParam {
name("InputStringParam")
description("문자열을 입력해주세요")
}
})
.gitRepositoryUrl("https://github-in-tosspayments/tosspayments/my-batch")
.applicationName("my-batch-application")
.applicationPhase("dev") // 토스페이먼츠의 표준 application phase 에 맞게 선언됩니다
.javaVersion(JavaVersion.JDK_21)
.javaVmOptions([
"-Dspring.batch.job.names=MyFirstBatch1",
"-Dspring.profiles.active=${applicationPhase}"
])
.javaHeapSize("4g")
.triggerCronScheduleExpression("15 8 29 3,6,9,12 *")
.monitoringChannel(MonitoringChannel.pgSettlementBatchError) // 토스페이먼츠의 표준 모니터링 시스템과 자동으로 연동됩니다
.jmxPrometheusJavaAgentEnabled(true)
.jmxAsyncProfilerEnabled(true)
.jmxAsyncProfilerOptions(["start", "event=cpu"])
.javaThreadDumpEnabled(true)
.javaThreadDumpIntervalInSeconds(30)
.pinpointAgentEnabled(true)
.scavengerEnabled(true)
.build(this)이 파일을 IDE에서 열고 클래스에 점만 찍으면, 자동완성으로 어떤 설정이 있는지 쭉 보이도록 만들었어요.
자세히 보시면 Job 이름 같은 간단한 설정부터, Java version, JVM option, 토스페이먼츠 공통 모니터링 인프라 연동 설정까지. Jenkins만의 설정뿐 아니라, 개발자가 배치에 기대하는 모든 설정을 한 곳에서 제어할 수 있습니다.
특히 Pinpoint, Scavenger, Prometheus, Async profiler 등 개발자가 직접 연동하기 까다롭거나 장비 환경에 의존적인 설정들도, 선언만 하면 어떤 배치 장비에서 수행하더라도 동일한 개발자 경험을 제공할 수 있게 만들었어요.
Groovy 코드이기 때문에, 반복문을 통해 수십 개의 배치를 한 번에 만들 수도 있고, 조건문을 통해 환경별로 다른 배치를 만들 수도 있어요.
토스페이먼츠의 개발자가 만족할 때까지

뿐만 아닙니다. 방금 보신 것은 JavaRunBatchJob으로 Java 배치 애플리케이션을 실행할 때 사용하는 클래스예요.
개발자분들은 Jenkins에서 빌드도 하고 싶고 배포도 하고 싶고, Jenkins Pipeline도 쓰고 싶고, 심지어 Shell 명령어까지 직접 호출하고 싶을 거예요.
해당하는 모든 요구사항에 대한 패턴을 미리 다 만들어 두었습니다. 개발자가 "이런 기능은 없나요~?"하면 저희가 만들어드리는 거죠.
Job-DSL 플러그인 어댑터가 가져온 변화
그럼에도 개발자가 실수를 할 수 있을까요? 그럼요. 실제로 있었던 일인데, STAGING 장비에 LIVE 배치를 할당한 적도 있었습니다.
설정은 Groovy 코드로 작성되기 때문에, 테스트가 가능하죠. 설정이 적용되기 전에 테스트를 수행해 이러한 업무 규칙이 어긋나지 않았는지를 검증하도록 만들었습니다. 배포 안정성이 더욱 올라갔어요. 덕분에 개발자들이 실수해도, 잘못된 설정이 운영 환경에 적용되는 것을 근본적으로 막아낼 수 있습니다.
뿐만 아니라 내부적으로 Pinpoint 연동, Heap Dump 수행과 같은 토스페이먼츠 공통 인프라 새로운 기능을 추가하더라도, 그 연동 자체는 DevOps가 중앙에서 한 번만 수행하면 됩니다. 물론 개발자분들께는 켜고 끌 수 있는 옵션들을 제공해 드렸죠.
해결책 2: Dynamic Provisioning 인프라

두 번째 솔루션은 ‘Dynamic Provisioning 인프라 도입’이고, 이는 앞서 설명드린 배치의 메모리 부족 문제를 해결합니다.
한 노드에 몇 개의 프로세스를 실행하지?
이제 설정은 다루기 쉬워졌지만, 어떤 노드에 몇 개의 Java 프로세스를 띄울지는 또 다른 문제였습니다.
동적 프로비저닝의 해법
인프라는 단순해야 됩니다. 무조건 전용 노드를 붙여주면, 문제를 간단하게 해결할 수 있습니다. 문제는 비용이죠.저희는 시간에 따라 전용 노드가 동적으로 늘어나고 줄어들게 만들었습니다.
프로비저닝 실행 흐름:
이제 여러 개의 서로 다른 Java 프로세스가 같은 노드를 경쟁하듯 사용하지 않습니다. 노드가 부족한 경우 장비를 동적으로 받아옵니다.
새 장비에는 배치 프로세스를 즉시 실행할 수 있게 Jar 파일 등이 준비되어야 합니다. 때문에 배치 빌드 배포 프로세스에 대한 경로 표준화 작업이 같이 필요했습니다. 그래야 Jar를 동적으로 가져올 수 있기 때문이죠.
이제 배치가 한 번에 수십 개씩 병렬적으로 실행되더라도, 개발자는 더 이상 인프라 용량에 대해 신경 쓰지 않아도 됩니다. 또한 오랜 기간 사용하지 않은 장비는 자동으로 종료됩니다. 비용 문제를 해결한 거죠.
각 장비는 기본적으로 한 번 쓰고 버리는 형태로 사용이 가능하므로, OS 보안 패치 등 커널 수준의 작업도 매우 적은 비용으로 가능해졌습니다.
개발자와 DevOps 간의 단일 업무 인터페이스 구축

이러한 설정 구조와 인프라 추상화 덕에, 개발자는 애플리케이션 로직 개발에 더 집중할 수 있게 되었습니다. 리소스 걱정 없이 모든 메모리와 CPU를 전용으로 할당받고, 더 높은 메모리가 필요한 경우에도 단순히 Groovy 코드 한 줄만 바꾸면 끝입니다.
무엇보다 중요한 것은, 배치 설정 또한 개발자와 DevOps 간의 단일 업무 인터페이스가 생겼다는 사실입니다. 이후에 새로운 인프라 요구사항이 발생하더라도 DevOps는 적은 비용으로 이를 구현할 수 있습니다.
자, 여기서 아까의 그 질문을 다시 드릴게요. 배치 설정 체계의 가장 강력한 기능은 무엇일까요?
배치의 핵심도 여전히: 계속 진화할 수 있는 구조

네, 맞습니다. 똑같이 "진화"입니다.
API 설정 체계의 핵심도 진화였죠. 배치 설정도 마찬가지입니다. Jenkins Web 클릭 반복 작업부터 시작해, 비용까지 챙긴 Dynamic Provisioning 구조까지.
항상 어떠한 요구사항에 맞게 설정 인프라가 진화한다는 점이 공통점입니다. 설정도 일종의 소프트웨어라는 것이지요.
머나먼 여정의 결과

25년 9월, JDK 25가 릴리즈 된 사실을 알고 계신가요? 토스 컨퍼런스에서 이 발표를 할 당시에는 JDK 25가 나오기 전이었는데, 이 글을 쓰는 시점에서는 이미 JDK 25를 사용해볼 수 있게 되었습니다.
개발자들의 요구사항을 철저히 구현한 설정 체계를 보유한 토스페이먼츠에서는, 수백수천 대의 JDK 버전 변경은 이제 순식간에 가능합니다.
또한 Pinpoint 같은 APM이 새로 개발되어 수백 대의 장비에 당장 적용해야 하더라도, 전혀 문제가 없습니다. 몇 명 안 되는 DevOps 엔지니어가 개발자 수십 명을 서포팅하는 거죠.
여태까지의 요약
단순히 복사-붙여넣기에서부터 시작한 설정이:
- API의 오버레이 아키텍처와 템플릿 패턴으로 확장되었고
- 배치에서는 선언형 구조와 Dynamic Provisioning으로 진화했습니다.
이 모든 것은 토스페이먼츠의 개발자들을 든든하게 받쳐줄 수 있는 기반 인프라가 되어 줍니다. 개발자들이 더 이상 설정 때문에 머리 아픈 일이 발생하지 않고, 이를 통해 레거시 개편도 하고, 성능 향상도 하고, 더 나은 API를 만드는 데 집중할 수 있게 도와줍니다.
“그 다음은?”을 같이 고민하실 분을 찾습니다
이렇게 API와 BATCH에 적용되는 각각의 설정을, 토스페이먼츠 서버플랫폼 팀이 어떻게 바라보고 생각하는지 같이 알아보셨습니다.
이 여정은 끝이 아닙니다. 토스페이먼츠 인프라가 존재하는 한, 설정의 진화는 계속됩니다.
그리고 이 설정의 진화처럼, 토스페이먼츠 서버플랫폼 팀 역시 계속 성장하고 있습니다. "그 다음은?"이라는 고민을 같이 하실 분을 저희는 기다리고 있겠습니다.
이 자리에 계신 뛰어난 여러분들과, 언젠가 멋진 인프라를 함께 만들어 나갈 수 있기를 기대합니다. 토스페이먼츠 채용은 언제나 열려있습니다.
감사합니다.
✅ 이번 아티클은 Toss Makers Conference의 세션을 바탕으로 재구성되었습니다.
