토스 SLAHS 2024 - 대외계 구조 개선과 모니터링 강화로 시스템 연속성 확보하기 해당 영상을 보고 내용을 추가해서 만든 포스팅이다.
나는 열정 넘치는 신입 개발자 동동이다.
어느날, 대표님이 내게 찾아와 한 마디를 던지고 갔다.
요새 서비스가 좀 느려진 거 같네요... 1ms 개선될 때마다 월급 만원씩 올려 드릴게요...😉
내가 만든 서비스가 느리다고...? 절대 월급이 올라가서가 아니다. 내 자존심이 걸린 문제다. 꼭 원인을 찾아서 해결하고 말겠어.🔥

우리 서버는 굉장히 간단한 구조이다.
위와 같이 메인 서버가 중심에서 DB와 대외계와 소통하며
client에 결과값을 전달한다.
앞에서부터 모든 경우의 수를 살펴보자.
나의 서버가 잘못되었을 리 없다. 일단 고객이 의심스럽다.
고객 와이파이 or 5G가 잘못된 거 아닌가?
일단 사내 와이파이를 이용해 API 요청을 날려 보았다.
curl -w "result.json" -o /dev/null -s "https://api.example.com"
{"latency":["total":1.265323]}
에헤에에? API 하나에 1265ms란 말인가..
내가 범인인가?
먼저 내가 작성한 코드들을 살펴보았다.
import { Injectable } from '@nestjs/common';
import axios from 'axios';
import { InjectRepository } from '@nestjs/typeorm';
import { UserEntity } from './entities/user.entity';
import { Repository } from 'typeorm';
@Injectable()
export class ApplicationService {
constructor(
@InjectRepository(UserEntity)
private userRepository: Repository<UserEntity>,
) {}
async myCode() {
const user = await this.getDataFromDatabase();
const externalData = await this.getDataFromExternalApi();
return {
user,
externalData,
};
}
async getDataFromDatabase() {
return this.userRepository.find();
}
async getDataFromExternalApi() {
const response = await axios.get('https://api.협력해요.com/data');
return response.data;
}
}
흠,,, DB가 문제인건가..
쿼리 로깅을 통해 속도를 확인해 봐야겠다.
query: SELECT * FROM user
execution time: 34 ms
직접 서버에 로그를 남긴 것을 확인해 보아도
MySQL에서 slow_query 옵션을 사용해 보아도
DataBase 성능은 확실한 것으로 보인다.
네이놈 협력서버!! 네 죄를 네가 알렸다. 부리나케 증거 수집을 위해 axios 로깅 시스템을 구축하였다.
@Injectable()
export class AxiosLoggingService {
constructor(
@Inject(API_LOGGER) private readonly logger: Logger,
private readonly requestContextService: RequestContextService,
) {
axios.interceptors.request.use((config) => {
this.logger.log({
level: 'info',
type: 'axios',
url: config.url,
method: config.method,
message: `[Request]`,
request: config.data,
startTime: Date.now(),
});
return config;
});
axios.interceptors.response.use((response) => {
const executionTime = Date.now() - response.config.startTime;
this.logger.log({
level: 'info',
executionTime: `${executionTime}ms`,
url: response.config.url,
method: response.config.method,
statusCode: response.status,
statusText: response.statusText,
type: 'axios',
message: `[Response]`,
request: response.config.data || '',
response: response.data || '',
});
return response;
});
}
}
axios의 interceptor를 사용해 외부 api 응답속도를 확인해 보았다.
{
"executionTime": "1100ms"
}
...빙고! 🎯
검거완료
바로 해당 자료와 함께 이메일을 보내보았다.
안녕하세요, 동동이님! 협력사 기술전문가 나전문입니다.
요청 주신 API 응답시간은 확인해 보니134ms로 지연 현상을 확인할 수 없었습니다.해당 요청에 대한 로그 첨부해 보내드립니다. 안_느림.json
항상 더 나은 서비스를 제공하기 위해 노력하겠습니다.
감사합니다,
나전문 드림
하지만 협력사 측에서도 요청에 대한 로그을 확인해 주며 이상이 없다는 것을 보았다.
아니 그럼 대체 어디가 문제인건데!
내 월급!!! 아니 내 자존심!!!
아까 시스템 구조를 다시 살펴보자.

어어 저기 뭔가 보이는데

어어?!?

기둥 뒤에 인터넷 제공업체 있어요..
아니 진짜로..? 믿었던 인터넷이 문제인가
curl -so /dev/null --noproxy "*" -w \
"{\"latency\":[{\"dnslookup\":%{time_namelookup},\"tcp\":%{time_connect},\"ssldone\":%{time_appconnect}},\"total\":%{time_total}]}\n" \
https://www.협력업체.com
아래는 위 명령어에 대한 응답예시이다.
{
"latency":[
{
"dnslookup":0.147391,
"tcp":0.152435,
"ssldone":0.185845
},
"total":0.265323
]
}
https 통신의 경우
통신 전체에 있어서 이러한 과정을 모두 정밀하게 측정할 수 있다.
또한 curl 옵션 설정을 변경하여 원하는 정보를 추가로 로깅할 수 있다.
sudo mtr -z -r -c1 -w -b -p --json www.협력업체.com
{
"report": {
"mtr": {
"src": "MacBookPro",
"dst": "www.naver.com",
"tos": 0,
"tests": 1,
"psize": "64",
"bitpattern": "0x00"
},
"hubs": [
{
"count": 1,
"host": "rt-ac59u_v2-03e0 (192.168.50.1)",
"ASN": "AS???",
"Loss%": 0.0,
"Snt": 1,
"Last": 3.437,
"Avg": 3.437,
"Best": 3.437,
"Wrst": 3.437,
"StDev": 0.0
},
{
"count": 2,
"host": "192.168.0.1",
"ASN": "AS???",
"Loss%": 0.0,
"Snt": 1,
"Last": 6.511,
"Avg": 6.511,
"Best": 6.511,
"Wrst": 6.511,
"StDev": 0.0
},
{
"count": 3,
"host": "175.208.209.254",
"ASN": "AS4766",
"Loss%": 0.0,
"Snt": 1,
"Last": 6.416,
"Avg": 6.416,
"Best": 6.416,
"Wrst": 6.416,
"StDev": 0.0
},
{
"count": 4,
"host": "61.78.43.90",
"ASN": "AS4766",
"Loss%": 0.0,
"Snt": 1,
"Last": 5.09,
"Avg": 5.09,
"Best": 5.09,
"Wrst": 5.09,
"StDev": 0.0
},
{
"count": 5,
"host": "112.189.15.89",
"ASN": "AS4766",
"Loss%": 0.0,
"Snt": 1,
"Last": 7.687,
"Avg": 7.687,
"Best": 7.687,
"Wrst": 7.687,
"StDev": 0.0
},
{
"count": 6,
"host": "???",
"ASN": "AS???",
"Loss%": 100.0,
"Snt": 1,
"Last": 0.0,
"Avg": 0.0,
"Best": 0.0,
"Wrst": 0.0,
"StDev": 0.0
},
{
"count": 7,
"host": "112.174.75.34",
"ASN": "AS4766",
"Loss%": 0.0,
"Snt": 1,
"Last": 11.512,
"Avg": 11.512,
"Best": 11.512,
"Wrst": 11.512,
"StDev": 0.0
},
{
"count": 8,
"host": "???",
"ASN": "AS???",
"Loss%": 100.0,
"Snt": 1,
"Last": 0.0,
"Avg": 0.0,
"Best": 0.0,
"Wrst": 0.0,
"StDev": 0.0
}
]
}
}
위 데이터를 토대로 그림을 그려보면
192.으로 시작하는 사내 private ip를 통해 라우팅을 하다
(AS???로 표기됨)
AS4766부터는 통신사 중계망을 통해 전달되고
목적지(대외계 채널)에 도착하고 있다.

curl을 통해 전체 통신에 소요되는 시간을 확인할 수 있고
mtr을 통해 목적지로 가는 hop마다 소요되는 시간을 더 정밀하게 확인할 수 있다.
보통 위 메트릭들을 grafana와 같은 툴을 이용해 시각화를 한다.

위 메트릭을 수집하여 분석해 보니
ISP 업체가 문제가 확실해 보였다..
ISP망은 우리가 직접 관리하는 부분이 아니기 때문에 우리가 해결할 수 있는 부분이 아니였다.
나는 ISP 제공업체로 한 편의 메일에 지금까지 기록했던 메트릭과 함께 개선 요청을 하였다.
하지만 해당 문제는 그렇게 빠르게 해결될 거 같지 않아 보였다.
사내에서 무언가 조정을 통해 바꿀 수 있는 부분이 있을까?
다행히도 우리 서비스는 만약을 대비해서 이중화가 잘 되어 있었다. ISP 제공 업체 또한 2곳을 사용하고 있었고 한 곳에서만 인터넷 지연 현상이 발견 되었다.
Bgp를 조정하여 ISP 네트워크의 지연이 확인될 경우 다른 ISP로 트래픽을 옮길 수 있도록 하여 문제를 해결하였다.
그리고 나는 연봉 2억의 사나이가 되었다.
연봉 2억 개발자님 메로나 하나만 사주세요