꽤 오랜만에 블로그를 쓰네요. 9월에 마지막으로 포스트를 썼으니 2달 만의 복귀입니다. 마침 글또 9기를 시작한 만큼 다시 열심히 블로그를 써보려고 합니다. 💪
그래서 오늘은 글쓰기에 대한 워밍업(?)을 할 겸, 예전에 겪었던 재미있는 썰 하나를 풀어보려고 합니다. 제목에서 짐작할 수 있듯이 이 포스트에서는 제가 프론트엔드 개발자가 된 이래로 저를 가장 피곤하게 만들었던 버그에 대해 이야기하려고 합니다.
프론트엔드 개발자로 첫 커리어를 시작한 이후 수많은 버그를 만나왔고 또 해결해왔는데요, 그 중에서도 기억에 남는 버그가 하나 떠올랐거든요.
부제를 달자면… AWS에 기여하게 된 과정에 대한 썰(?)이라고도 할 수 있겠다
해당 버그를 만났던 게 2021년 1월이니까… 거의 3년 전 일이네요. 3년 전에 해결했던 버그가 아직까지도 기억에 남는 이유는 아무래도 제 개발자 인생에서 가장 힘들게 해결한 문제여서 그렇지 않나 생각을 해봅니다. 기능 구현도 아니고 단순한 버그 해결에 거의 2주 이상의 시간을 쏟아부었거든요.
지금은 그때보다 짬이 더 찼다 보니 조금 더 쉬운 방법으로 해결할 수도 있을 것 같은데요, 당시에는 엄청 힘들게 고생하면서 해결한 기억이 있어서 그때의 썰을 풀어보고자 합니다. 기술적으로는 WebRTC와 관련된 문제이긴 한데, 관련 지식이 없더라도 이해할 수 있도록 쉽게 써보려고 합니다.
문제의 발견
3년 전, 저는 반려동물 모니터링 서비스를 개발하던 스타트업에서 일하고 있었습니다. 당시 서비스의 프론트엔드 개발을 거의 혼자 담당하고 있었죠.
그러던 어느 날, 백로그에 새로운 이슈가 등록되었습니다. 이슈 내용이 정확하게 기억이 나진 않는데, 대충 이런 내용이었습니다.
아이폰 사용자가 음성 파일 재생 API를 호출하면 홈캠에서 소리가 작게 나옴
일단 이게 무슨 뜻인지를 이해하려면 당시 서비스의 구조를 알아야 합니다.
서비스는 두 대의 스마트폰이 WebRTC로 P2P 통신을 하는 상황을 가정합니다. 한 대는 CCTV의 역할로 사용하며, 가정에 설치되어 반려동물 영상을 스트리밍하는 용도입니다. 요거는 네이티브 앱으로 만들었고, 역할 상 홈캠에 해당합니다.
또 다른 한 대는 해당 영상을 원격에서 살펴보고 제어하는 용도로, 집 밖에 있는 반려동물 주인이 사용합니다. 요거는 웹뷰로 만들었고, 역할 상 뷰어에 해당합니다.
홈캠을 통해 스트리밍 된 영상을 뷰어에서 보는 예시
뷰어에서는 스트리밍 영상을 시청하는 것뿐만 아니라 몇 가지 추가 기능을 제공했는데요, 그중 하나가 홈캠 기기에 저장된 음성 파일을 재생하도록 요청하는 것이 있었습니다. 반려동물의 심리 안정을 위한 파도나 모닥불, 귀뚜라미와 같은 자연의 소리를 재생하는 기능이 있거든요.
음성 파일 재생 원리는 단순하게 뷰어에서 API를 호출하면, 홈캠에서 해당 파일을 재생하는 방식이었습니다. 그런데 유독…! 아이폰 유저가 API를 호출할 때만 홈캠에서 음성 파일의 소리가 작게 나온다는 것이 문제의 내용이었습니다.
이상한 현상의 재현
처음에 이 이슈를 봤을 때, 희한하다는 생각은 했지만 대수롭지 않게 여겼습니다. 단순히 소리가 작게 나온다는 게 문제일 뿐, 동작 자체가 안 되는 건 아니었으니까요. 그래서 작업의 우선순위도 낮게 잡아두었습니다.
또한 이 문제의 직접적인 원인이 프론트엔드는 아니라고 생각했습니다. 뷰어에서는 그냥 단순히 재생 API만 호출할 뿐, 실제로 소리를 재생하는 기기는 홈캠이니까요.
const response = axios.post('/api/play-sound', {
sound: 'wave',
});
프론트엔드의 코드를 대충 적어보면 이런데요, 정상적으로 API를 호출하고 있으니까 문제는 홈캠에서 발생하는 것이라고 생각했습니다. 그래서 우선은 앱 개발자분께 이 이슈를 할당해 드렸죠.
그런데 앱 개발자분이 며칠 코드를 살펴보더니, 문제 원인이 앱에 있는 건 아닌 것 같다 고 말씀하셨습니다. 홈캠에서는 뷰어의 브라우저 종류에 따라 음량을 조절하는 코드를 작성한 적이 없었다고 하시면서 말이죠.
앱: “그런 코드는 작성한 적이 없는데요?” 웹: “엥 저돈데요?”
여기서부터 고개가 갸우뚱했습니다. ‘아니, 홈캠과 뷰어 양쪽에서 음량 조절과 관련된 코드를 작성한 적이 없는데…?’ 게다가 뷰어의 스마트폰 기종에 따라 홈캠의 음량이 달라진다는 것도 이상했습니다.
그래서 일단은 소리가 얼마나 작게 나오는지 다 같이 확인해 보기로 했습니다.
“혹시 이거 재현되나요?”
제가 물어보자마자 앱 개발자분께서는 아이폰을 꺼내며 말씀하셨습니다.
“네네, 이거 아이폰에서 켤 때만 그렇더라구요. 한 번 들어보세요.”
동시에 확인을 해보았다
테스트를 위해 홈캠을 설치하고 아이폰으로 뷰어를 실행했습니다. 그리고 뷰어에서 음성 파일 재생 API를 호출하는 버튼을 클릭했습니다.
그런데… 정말로 소리가 작게 나왔습니다. 아이폰 기기 자체의 음량을 최대한으로 키우면 들을 수 있긴 했는데, 일반적인 음량에서는 거의 들릴락 말락 할 수준이었습니다. 일단 현상이 재현되는 것을 확인하고 난 후, 제가 말했습니다.
“제 스마트폰 갤럭시니까 이걸로 한 번 다시 들어가 볼게요.”
안드로이드 스마트폰을 꺼내서 다시 위 과정을 반복했습니다. 그런데 이번에는 소리가 정상적으로 재생되었습니다. 정말로 아이폰 유저의 경우에만 소리가 작게 나오는 현상이었던 것이죠.
범위를 좁혀가기
막막함 그 자체
그렇게 문제가 재현되는 것을 다 같이 확인했으니 이제 원인을 찾아야 했습니다. 저는 뷰어와 관련된 프론트엔드 코드를 전체적으로 다시 살펴보기 시작했습니다.
그런데… 도저히 저 증상만으로는 원인을 찾을 수가 없었습니다. 짐작도 가지 않았습니다. 뷰어에서는 그냥 API를 호출하기만 하는데, 이게 문제의 원인이라는 것이 이해가 안 됐습니다. 어디서부터 해결해야 할지를 모르겠으니까 진짜 막막하더라구요.
그래도 일단 이것저것 시도해 보기로 했습니다. 문제를 둘러싸고 있는 환경을 조금씩 바꿔가면서 어떤 것이 문제의 원인인지를 찾아보기로 했습니다. 문제의 범위를 좁혀나가는 방법으로 말이죠.
아이폰 웹뷰의 문제인가?
우선 이 문제가 아이폰에서 발생하는 것인지, 아니면 아이폰에 종속된 사파리 브라우저에서 발생하는 것인지, 아니면 웹뷰에서만 발생하는 문제인지를 확인해 보기로 했습니다.
아이폰에서 크롬을 설치한 후, 뷰어의 URL을 주소창에 직접 입력하는 방식으로 접속해 보았습니다. 그랬더니… 홈캠에서 정상적으로 소리가 재생되었습니다! 한편 같은 URL을 iOS 사파리에서 실행했을 때는 여전히 소리가 작게 나왔습니다.
이를 통해 이슈가 아이폰 자체의 문제가 아니라 iOS 사파리 브라우저에서 발생하는 것임을 확인할 수 있었죠. 즉 문제 상황을 다음과 같이 수정할 수 있었습니다.
사용자가 iOS에서 사파리 브라우저를 통해 음성 파일 재생 API를 호출하면 홈캠에서 소리가 작게 나옴
모바일 사파리의 문제인가?
사파리 브라우저에서 발생하는 문제라는 것을 확인했으니, 이것이 iOS에서만 발생하는 문제인지, 아니면 Mac OS에서도 발생하는 문제인지를 확인해 보기로 했습니다. 만약 Mac OS에서도 발생한다면, 컴퓨터를 통해서도 재현이 가능하니까 디버깅이 훨씬 수월해질 것이라고 생각했거든요.
그래서 저는 Mac OS에서 사파리 브라우저를 실행한 후, 뷰어의 URL을 주소창에 직접 입력하는 방식으로 접속해 보았습니다. 그랬더니… 홈캠에서 여전히 소리가 작게 재생되었습니다. 즉 사파리 브라우저라면 iOS든 Mac OS든 상관없이 문제가 발생한다는 것이죠.
마주한 문제 상황을 다시 아래처럼 수정했습니다.
사용자가 사파리 브라우저를 통해 음성 파일 재생 API를 호출하면 홈캠에서 소리가 작게 나옴
WebRTC의 문제인가?
WebRTC 통신이 없는 상태에서는 음성 재생 API가 정상 동작하는가?
문제를 둘러싼 환경 중 하나가 WebRTC 통신이었습니다. 뷰어와 홈캠이 WebRTC로 P2P 통신을 하는 와중에 API를 호출하고 있었으니까요. 그래서 WebRTC 통신이 없는 상태에서는 음성 재생 API가 정상 동작하는지를 확인해 보기로 했습니다.
뷰어에서 임시로 WebRTC 연결 코드를 주석 처리한 후, 사파리에서 다시 음성 재생 API를 호출해 보았습니다. 그랬더니… 홈캠에서도 정상적으로 소리가 재생되었습니다.
이를 통해 WebRTC 통신이 문제의 원인 중 하나라는 것을 알게 되었고, 문제 상황을 더 구체화할 수 있었습니다.
사용자가 사파리 브라우저를 통해 WebRTC 통신을 하는 도중에 음성 파일 재생 API를 호출하면 홈캠에서 소리가 작게 나옴
WebRTC 연결 과정 중 정확히 어떤 부분이 문제인가?
결국에는 WebRTC 통신이 문제의 원인이었는데요, 이 문제를 해결하기 위해서는 WebRTC의 연결 과정을 이해해야 했습니다.
WebRTC의 연결 과정을 시그널링(Signaling) 이라고 부르는데 이 과정이 꽤 복잡합니다. 이를 간단하게 요약해서 소개하자면 다음과 같은데요.
- 시그널링 서버에 하나의 P2P 통신 단위인 채널(Channel) 을 생성
- 각 단말은 시그널링 서버에 연결하고 채널에 입장
- 각 단말은 채널을 통해 자기 자신에게 접근 가능한 주소를 상대방에게 전달
- 각 단말이 상대방에게 어떤 미디어를 전송할지를 교환
- 각 단말은 상대방에게 미디어를 전송
이 중에서 정확히 어떤 부분이 문제의 원인인지를 찾아보기로 했습니다. 다행히(?) Mac OS의 사파리에서도 문제가 재현되니까요.
크롬과 사파리 두 개의 브라우저를 동시에 켜놓고, 똑같은 코드를 한 라인씩 실행하며 디버깅을 시작했습니다. 사파리에서만 문제가 발생하기 때문에, 분명 코드를 실행하는 과정에서 차이가 있을 것이라고 생각했거든요.
그런데 몇 번을 살펴봐도 차이가 없었습니다. 개발자 도구를 통해 개발 과정에서 사용하는 변수들의 값들을 하나씩 살펴봤지만… 차이가 없었습니다.
‘분명히 원인은 사파리에서 WebRTC를 연결하는 과정 중에 있는데…’ 라는 생각은 들었지만, 정확한 물증이 없다 보니 머리를 또 싸맬 수밖에 없었습니다.
그러다가 문득 위에서 4번째 단계로 설명했던 각 단말이 상대방에게 어떤 미디어를 전송할지를 교환하는 과정에서 사용하는 문자열 데이터의 형태가 수상하다는 것이 눈에 들어왔습니다. 데이터의 형태는 대충 아래와 같았는데요.
v=0\r\n\o=- 20518 0 IN IP4 203.0.113.1\r\n\s= \r\n\t=0 0\r\n\c=IN IP4 203.0.113.1\r\n\a=ice-ufrag:F7gI\r\n\a=ice-pwd:x9cml/YzichV2+XlhiMu8g\r\n\a=fingerprint:sha-1 42:89:c5:c6:55:9d:6e:c8:e8:83:55:2a:39:f9:b6:eb:e9:a3:a9:e7\r\n\m=audio 54400 RTP/SAVPF 096\r\n\a=rtpmap:0 PCMU/8000\r\n\a=rtpmap:96 opus/48000\r\n\a=ptime:20\r\n\a=sendrecv\r\n\
개발자 도구를 통해 확인한 변수의 값들은 모두 동일했으니, 이 이상하게 생긴 데이터가 문제의 원인이 아닐까 싶었거든요.
SDP의 어떤 부분이 문제인가?
이 데이터의 이름은 SDP(Session Description Protocol) 라고 부릅니다. WebRTC에서는 스트리밍 미디어의 해상도나 형식, 코덱 등의 멀티미디어 콘텐츠의 초기 값을 상대방에게 전달하기 위한 프로토콜… 이긴 한데, 당시에는 이게 뭔지도 몰랐습니다. 그냥 WebRTC 통신을 하기 위해 필요한 데이터라고만 알고 있었죠.
아무튼 위 SDP 데이터를 보기 쉽게 줄 바꿈 했더니, 다음과 같이 키=값
형태로 이루어져 있는 것을 확인할 수 있었습니다.
v=0
o=- 20518 0 IN IP4 203.0.113.1
s=
t=0 0
c=IN IP4 203.0.113.1
a=ice-ufrag:F7gI
a=ice-pwd:x9cml/YzichV2+XlhiMu8g
a=fingerprint:sha-1 42:89:c5:c6:55:9d:6e:c8:e8:83:55:2a:39:f9:b6:eb:e9:a3:a9:e7
m=audio 54400 RTP/SAVPF 0 96
a=rtpmap:0 PCMU/8000
a=rtpmap:96 opus/48000
a=ptime:20
a=sendrecv
...
실제로는 거의 30~40줄 정도에 달하는 복잡하고 긴 문자열이었는데요, 이 문자열을 일일이 비교하며 살펴보기에는 너무 복잡하더라구요.
결국 SDP와 관련된 자료를 찾아보다가, JavaScript에서 이를 보기 쉬운 형태로 파싱 해주는 sdp-transform라는 라이브러리를 발견했습니다. 이 라이브러리를 사용했더니 SDP 데이터를 다음과 같은 JavaScript 객체 형태로 변환할 수 있었습니다.
const udp = {
version: 0,
iceUfrag: 'F7gI',
icePwd: 'x9cml/YzichV2+XlhiMu8g',
media: [
{
rtp: [Object],
fmtp: [],
type: 'audio',
port: 54400,
protocol: 'RTP/SAVPF',
payloads: '0 96',
ptime: 20,
direction: 'sendrecv',
candidates: [Object],
},
],
// ...
};
물론 각 키와 값마다 역할이 있겠지만 제가 중요하게 여긴 것은 그게 아닙니다. 제가 두 눈을 부릅뜨고 살펴본 부분은 사파리와 크롬에서 생성한 SDP의 값에서 다른 부분이 있는지 였습니다. 두 브라우저에서 생성한 SDP 데이터를 비교해 보았더니… 정말로 차이가 있었습니다! media
프로퍼티에 들어가는 direction
값이 다르더라구요.
동일한 코드인데도 불구하고, 사파리에서 생성한 SDP 데이터의 direction
값은 "sendrecv"
였고, 크롬에서 생성한 SDP 데이터의 direction
값은 "recvonly"
였습니다. direction
으로 올 수 있는 값은 총 4가지인데요.
"sendrecv"
: 양방향 통신"recvonly"
: 수신만 가능한 단방향 통신"sendonly"
: 송신만 가능한 단방향 통신"inactive"
: 통신 불가능
즉, 사파리에서 생성한 SDP 데이터는 양방향 통신을 하도록 설정되어 있었고, 크롬에서 생성한 SDP 데이터는 수신만 가능한 단방향 통신을 하도록 설정되어 있었다는 것이 차이점이었죠. 뷰어는 스트리밍 영상을 보는 역할이기 때문에 "recvonly"
가 적합한 값이었는데 사파리에서는 잘못된 값이 들어있었습니다.
드디어 원인을 찾은 것 같다는 생각에 가슴이 터질듯한 두근거림이 느껴졌습니다. 호흡을 가다듬고 뷰어의 SDP 값을 "recvonly"
로 수정해 다시 시도해 보았더니… 정말로 문제가 해결되었습니다! 드디어 사파리에서 API를 요청한 경우에도 홈캠에서 소리가 정상적으로 나오기 시작했습니다.
당시에 저는 엄청나게 환호하면서 기뻐했던 것 같습니다. 2주 이상을 삽질했던 문제를 해결할 수 있는 방법을 찾았으니까요. 문제 상황 역시 더 구체화할 수 있었습니다.
사용자가 사파리 브라우저를 통해 WebRTC 통신을 하는 과정에서 SDP 문자열의 값이 잘못 설정되는 현상으로 인해 음성 파일 재생 API를 호출하면 홈캠에서 소리가 작게 나옴
하지만 이 문제가 왜 발생했는지는 아직도 이해가 안 되었습니다. 그래서 정확히 저 부분이 왜 문제를 일으켰는지를 더 파악해 보기로 했습니다.
왜 사파리에서만 SDP의 값이 다른가?
그렇다면 왜 사파리에서만 SDP의 값이 달랐던 걸까요? 사실 이것은 브라우저 호환성 문제와 관련이 있습니다.
당시 WebRTC는 아직 표준이 완전히 정립되지 않은 기술이었는데요, 이로 인해 브라우저마다 지원하는 기능과 구현 방법이 조금씩 달랐습니다. 이는 결국 브라우저 호환성 문제와 연관되는데요, 그렇기 때문에 WebRTC 관련 웹 애플리케이션을 만들 때는 adapter.js라는 폴리필 라이브러리를 사용합니다. 이 라이브러리는 각 브라우저마다 다르게 구현된 WebRTC API를 표준에 맞게 구현해 주는 역할을 통해 호환성 문제를 해결합니다.
저 역시 adapter.js를 사용하고 있었고, 그렇기 때문에 브라우저 별로 다르게 구현된 폴리필 메서드를 실행하게 된 것이죠. 그런데 폴리필 라이브러리에서 버그가 있었는지, 사파리에서만 direction
값이 "sendrecv"
로 잘못 설정되는 것이 원인이었습니다.
폴리필 라이브러리를 아예 사용하지 않을 수는 없어서, 이를 어떻게 해결해야 할지에 대해서 다시 고민을 했습니다. 그런데 알고 보니 기존 API를 대체하는 최신 API가 있더라구요.
최신 API를 사용하니 SDP의 direction
값을 수동으로 설정해 줄 수 있어서 값이 잘못 들어가는 문제를 해결할 수 있었습니다.
문제의 원인을 겨우 찾아냈는데 알고보니 이미 다른 사람이 등록한 이슈라는 것에 대단히(?) 충격받았다
즉… 사파리에서만 SDP의 값이 달랐던 이유는 아래의 세 가지 문제가 복합적으로 겹쳐서 발생한 것이었습니다.
- WebRTC로 웹 애플리케이션을 제작하려면 폴리필 라이브러리인 adapter.js를 필수적으로 사용해야 함
- adapter.js의 특정 메서드가 사파리에서 잘못 동작하는 버그가 있었음
- AWS에서 제공하는 예제 코드가 레거시 API를 기반으로 작성되어 있음
여기까지 온 상황에서 문제는 다음과 같이 더 구체적으로 정의할 수 있었습니다.
WebRTC 폴리필 라이브러리인 adapter.js의 특정 메서드가 사파리에서 잘못 동작하는 문제가 있었고, 해당 메서드가 레거시 API였기 때문에 사용자가 사파리 브라우저를 통해 WebRTC 통신을 하는 과정에서 SDP 문자열의 값이 잘못 설정되는 현상이 발생하여 음성 파일 재생 API를 호출하면 홈캠에서 소리가 작게 나옴
문제의 해결, 그리고 기여
문제가 발생했던 SDP 코드를 최신 API로 교체하고, 최신 API 사용이 불가능한 경우에는 SDP를 수동으로 수정하는 코드를 작성했습니다. 해당 코드를 적용한 이후에는 문제가 더 이상 발생하지 않음을 확인했습니다. 덕분에 무사히 해당 이슈를 닫을 수 있었죠.
저도 굉장히 오랜 기간 삽질을 했었다 보니, 다른 사람도 동일한 문제를 겪진 않았으면 하는 마음에 AWS 예제 코드에 기여하고 싶다는 생각이 들었습니다. 그래서 “최신 API를 쓰는 것을 고려해라” 라는 내용을 추가하는 풀 리퀘스트를 올려두었죠.
풀 리퀘스트를 올려두었는데 2년 동안 아무도 확인 안 해주다가 갑자기 올해 4월에 머지됐다.. ㅋ
그렇게 해당 이슈는 제 기억에서 점점 잊고 있었죠. 그런데 2년이 지난 올해 4월, 갑자기 풀 리퀘스트가 머지되었다고 메일이 왔습니다. 그렇게 뜬금없이(?) AWS 컨트리뷰터가 되었다…는 소식을 전하면서 이야기를 마치고자 합니다.
느낀 점
지금 다시 그때를 회상하며 포스트를 적다 보니 든 생각인데, 생각보다 별로 재미있는 이야기(…)는 아니네요. 그럼에도 불구하고 아직까지도 이 이슈가 기억에 남는 이유를 몇 개 꼽아보았는데요.
일단은 물리적, 정신적으로 가장 많은 리소스를 투자해서 해결한 버그였기 때문입니다. 일단 WebRTC와 AWS 환경에 대한 지식이 부족한 상태기도 했고, 골때리는 브라우저 호환성 문제도 함께 얽혀서 문제의 복잡도가 상당히 높았기 때문입니다. 그래서 순수 개발 시간만으로 2주 이상이라는 긴 시간 동안 삽질을 했던 것 같습니다.
그리고 프론트엔드가 원인이 아닐 거라고 섣부르게 추측했던 것에 대한 반성이 있습니다. 사실 최초에 보고된 이슈 내용을 살펴보면, 프론트엔드가 원인이 아닐 것이라고 단언했거든요. 그런데 알고 보니 결국에는 프론트엔드에서 문제를 해결할 수 있었던 것을 보며, ‘개발자는 함부로 단언해서는 결코 안 되고, 문제 속에 숨겨진 속뜻을 잘 찾아야 한다’ 는 것을 깨달았습니다. 왜냐하면 짜잔~ 절대라는 것은 없으니까요.
또한 문제의 원인을 찾아내는 과정에서 오는 뿌듯함도 있었습니다. 조금씩 포위망을 좁혀가면서 문제의 원인을 찾아내는 과정은 마치 바둑 한 판을 두는 것과도 같았습니다. 논리적으로 불가능한 상황들을 하나씩 배제해 가면서 경우의 수를 좁혀나갔고 원인을 찾아냈거든요. 답을 찾아가는 과정은 정말로 괴롭고 고통스러운 시간이었는데요, 답을 얻고 나니까 그동안의 고생이 더욱 값진 카타르시스를 만들어줬습니다. 과장 좀 보태서 개발자라는 직업에 대한 보람(?)까지도 느낄 수 있었습니다.
마지막으로는 삽질을 하긴 했지만 결국에는 문제를 내 손으로 해결해 냈다는 성취감이 있었던 것 같습니다. 결국에는 어떤 문제를 만나더라도 충분한 시간만 주어진다면 문제를 해결할 수 있다는 자신감을 얻을 수 있었거든요. 사실 이를 통해 큰 문제를 만나더라도 두려워하지 않고 도전할 수 있는 용기를 얻을 수 있었다… 라고 하면 좀 거창하긴 한데, 아무튼 어느 정도는 사실이기도 합니다. ㅋㅋㅋ
아무튼 이렇게 제 인생에서 가장 깊게 삽질한 이야기를 마칩니다. 여러분도 만약 어떤 문제를 만나게 된다면, 문제의 범위를 좁혀나가고, 당연한 것도 의심해보고, 문제를 해결한 후에는 기록을 남겨두시길 바랍니다. 그래야 나중에 다시 같은 문제를 만났을 때 빠르게 해결할 수 있으니까요.