Showing Posts From
제품
- 21 Dec, 2025
라이브 제품 버그, 하지만 재현이 안 된다
금요일 오후 4시 고객사에서 전화가 왔다. "또 멈췄어요." 이번 주에 세 번째다. 제품 돌려봤다. 정상이다. 어제도 밤새 돌렸다. 문제없었다. 로그 파일 확인했다. 깨끗하다. "몇 시에 멈췄나요?" 물었다. "오전 11시쯤이요." 대답이 왔다. 우리 랩에선 11시에도 돌았다. 24시간 내내 돌았다.팀장이 "현장 가봐야 하나" 물었다. 부산이다. KTX 타고 3시간. 가서 뭘 본다는 건지 모르겠다. 제품 들고 가서 보면 또 정상일 거다. 항상 그랬다. 재현되지 않는 버그 펌웨어 개발자의 악몽이다. 버그가 있는데 재현이 안 된다. 고쳐야 하는데 뭘 고치나. 증상을 봐야 원인을 찾지. 증상이 안 나타나면 손을 못 댄다. 웹개발자들 부럽다. 로그 서버에 다 남잖아. 스택 트레이스 보면 어디서 터졌는지 나오잖아. 우리는? UART 로그가 전부다. 그것도 고객사에 케이블 연결해달라고 부탁해야 한다. "이거 연결하면 보증 안 돼요?" 묻는다. 할 말이 없다.문제는 환경이다. 확신한다. 우리 랩이랑 현장이랑 뭔가 다른 거다. 온도? 랩은 23도로 유지된다. 현장은? 모른다. 습도? 랩은 50%. 현장은? 역시 모른다. 전원? 랩은 SMPS로 깨끗하게 들어간다. 현장은 공장 전원이다. 다른 장비들이 같이 쓴다. 노이즈 있을 거다. 전자파? 랩은 조용하다. 현장은? WiFi 공유기, 블루투스 기기, 모터, 형광등... 온갖 게 다 있다. 로그를 달라고 했다 고객사에 요청했다. "UART 로그 좀 받아주세요. 멈출 때 어떤 메시지가 나오는지 봐야 해요." "어떻게 받나요?" 답이 왔다. 설명서 보냈다. USB-UART 케이블 연결하고, 시리얼 터미널 열고, 115200 baud로 설정하고... 사흘 뒤에 연락 왔다. "케이블이 없어요." 택배로 보냈다. 일주일 뒤에 또 연락 왔다. "연결했는데 아무것도 안 나와요." 사진 보내달라고 했다. RX랑 TX를 반대로 꽂았다. 다시 설명했다. "노란 선이 RX, 하얀 선이 TX..." 이틀 뒤에 스크린샷이 왔다. 로그가 찍혀 있었다. 감동이었다.그런데 로그를 봐도 모르겠다. 마지막 줄이 "WDT Reset" 이다. Watchdog Timer가 시스템을 리셋한 거다. 뭔가 무한루프를 돌았거나, 인터럽트가 계속 들어왔거나, 태스크가 블록됐거나. 셋 중 하나다. 문제는 왜 그랬느냐다. 원인을 모른다. 코드를 다시 봤다 밤 10시. 사무실에 나 혼자 남았다. 코드를 처음부터 다시 읽었다. WiFi 연결 루틴. 문제없어 보인다. MQTT 통신 부분. 타임아웃 처리 돼 있다. 센서 읽기. 에러 체크 있다. 그런데 한 군데가 신경 쓰인다. WiFi 재연결 로직이다. while(WiFi.status() != WL_CONNECTED) { WiFi.reconnect(); vTaskDelay(100); }타임아웃이 없다. 만약 WiFi가 계속 안 잡히면? 이 루프를 못 빠져나온다. Watchdog이 걸린다. "설마 WiFi가 안 잡힐 리가." 생각했었다. 고객사는 WiFi 환경이 좋다고 했다. 공유기 바로 옆이라고. 하지만 전자레인지를 돌리면? 2.4GHz 대역에 간섭이 생긴다. WiFi가 끊길 수 있다. 재연결 시도하는데 계속 실패하면? 루프를 못 빠져나온다. "이거다." 확신은 없지만 가능성은 있다. 패치를 만들었다 타임아웃을 추가했다. 10초 동안 연결 안 되면 루프를 빠져나온다. 에러 플래그를 세운다. 다음 사이클에서 다시 시도한다. uint32_t start = xTaskGetTickCount(); while(WiFi.status() != WL_CONNECTED) { if(xTaskGetTickCount() - start > pdMS_TO_TICKS(10000)) { connection_error = true; break; } WiFi.reconnect(); vTaskDelay(100); }로그도 추가했다. "WiFi reconnection timeout" 메시지가 뜨게. 빌드했다. 테스트했다. WiFi 공유기를 껐다. 10초 후에 타임아웃 로그가 떴다. 시스템은 리셋되지 않았다. 좋다. 펌웨어를 고객사에 보냈다. "이걸로 업데이트해보세요. 혹시 전자레인지나 다른 2.4GHz 기기가 근처에 있나요?" 답장이 왔다. "옆 테이블에 전자레인지 있어요. 점심시간에 씁니다." 빙고일 수도 있다. 11시쯤 멈춘다고 했다. 점심 준비 시작하는 시간이다. 일주일을 기다렸다 업데이트 후에 문제가 안 생겼다. 월요일, 화요일, 수요일... 일주일이 지났다. "아직 안 멈췄어요." 고객사에서 연락 왔다. 목소리가 밝았다. 확신은 여전히 없다. 정말 전자레인지 때문이었는지 모른다. 다른 이유였을 수도 있다. 타이밍 이슈였거나, 메모리 문제였거나. 하지만 결과적으로 해결됐다. 그걸로 됐다. 팀장이 "잘했어" 했다. 기분은 좋지 않았다. 운이 좋았을 뿐이다. 정확한 원인을 찾은 게 아니다. 가능성 있는 부분에 방어 코드를 넣은 거다. 현장 디버깅의 어려움 펌웨어는 환경에 민감하다. 같은 코드가 랩에선 되고 현장에선 안 된다. 온도. MCU 스펙에 -40도에서 85도까지 동작한다고 써 있다. 하지만 70도 넘어가면 이상한 일이 생긴다. 클럭이 불안정해진다. EEPROM 쓰기가 실패한다. 전원. 스펙에 3.3V ±10%라고 돼 있다. 2.97V도 괜찮다는 뜻이다. 하지만 2.97V에서 WiFi 켜면 전압이 순간적으로 더 떨어진다. Brown-out이 걸린다. 리셋된다. 타이밍. 인터럽트가 100us마다 온다. 처리 시간이 95us다. 여유가 5us다. 그런데 캐시 미스가 한 번 나면? 110us 걸린다. 다음 인터럽트를 놓친다. 전자파. CE 인증 받았다. EMC 테스트 통과했다. 그런데 현장에 가면 다르다. 대형 모터가 돈다. 용접기가 작동한다. I2C 통신이 가끔 깨진다. 이런 것들은 랩에서 재현이 안 된다. 환경을 똑같이 만들 수 없다. 방어적 코딩 결국 답은 방어적 코딩이다. 모든 경우의 수를 생각한다. 타임아웃을 건다. 무한루프를 만들지 않는다. 10초 안에 응답 안 오면? 포기하고 다음으로 넘어간다. 재시도 로직을 넣는다. 한 번 실패했다고 끝이 아니다. 3번, 5번 시도한다. 그래도 안 되면 에러 플래그를 세운다. Watchdog을 건다. 시스템이 멈추면 자동으로 리셋된다. 영구적으로 죽는 것보단 낫다. 전원을 체크한다. 전압이 떨어지면 WiFi를 끈다. 센서 읽기를 줄인다. 파워를 아낀다. 로그를 남긴다. 될 수 있는 한 많이. 나중에 분석한다. 완벽한 코드는 없다. 하지만 덜 깨지는 코드는 있다. 여전히 불안하다 패치가 나간 지 한 달이 지났다. 문제 보고가 없다. 하지만 확신은 없다. 진짜 원인을 찾은 건지 모른다. 그냥 증상을 숨긴 건 아닌지. 다른 조건에서 또 터질까 봐 걱정된다. 겨울에 온도가 내려가면? 습도가 올라가면? 양산 물량이 늘어나면 더 다양한 환경에 노출된다. 지금은 100대가 돌고 있다. 다음 달에 1000대가 나간다. 통계적으로 문제가 생길 확률이 높아진다. 밤에 전화 올까 봐 무섭다. "또 멈춰요." 그 말이. 교훈이랄 것도 없다 재현 안 되는 버그는 항상 있다. 피할 수 없다. 할 수 있는 건 최선을 다하는 거다. 로그를 많이 남긴다. 타임아웃을 건다. 에러 처리를 꼼꼼히 한다. 그리고 운을 믿는다. 엔지니어가 운을 믿는다는 게 웃기지만 사실이다. 현장은 랩이 아니다. 변수가 너무 많다. 모든 걸 컨트롤할 수 없다. 다음 주에 또 전화 올까. 아닐 수도 있다. 모른다. 주말이다. 쉬어야 하는데 머릿속에서 코드가 돈다. WiFi 루프, 인터럽트 타이밍, 전원 노이즈... 그만 생각하자. 맥주나 마셔야겠다.원인을 못 찾아도 일단 돌아가면 된다. 그게 현실이다.