Troubleshooting Java 1장: 앱에서 모호한 부분 밝히기
일반적으로 소프트웨어 개발자는 새로운 기능 구현 or 에러 조치를 위한 코딩보다 소프트웨어의 작동 방식을 이해하는 데 더 많은 시간을 쓴다.
소프트웨어 개발자는 보통 갖가지 조사 기업을 디버깅이라고 표현하지만, 개발자는 ‘문제를 찾고 해결하는 것’외에도 코드의 작동 방식을 분석하는 다양한 목적으로 디버깅을 수행한다.
- 새로운 프레임워크 배우기
- 문제의 근본 원인 찾기
- 기존 로직을 이해하여 새로운 기능으로 확장하기
1.1 앱을 더 쉽게 이해하는 방법
‘코드 조사’는 소프트웨어 기능의 특정한 동작으 분석하는 프로세스이다. 초창기에 코드를 살펴보는 주 목적은 에러(버그)를 찾아 고치는 것이었다. 그래서 ‘디버깅’이라는 용어를 아직도 사용한다. 하지만 현대의 앱은 매우 복잡하므로 여러가지 목적으로 코드 조사를 한다.
- 문제점 발견
- 소프트웨어 기능의 작동 원리를 이해하고 개선
- 기술 또는 라이브러리 학습
소프트웨어 작동 방식을 조사하는 기법은 다양하다.
- 코드 읽기
- 디버깅
- 로그 분석
- 프로파일링
- 스레드 상태 분석
- 메모리 상태 분석
- 모킹 및 스터빙
코드를 읽는 것과 책을 읽는 것은 근본적으로 다르다. 코드는 한 메서드에서 다른 메서드로, 한 파일에서 다른 파일로 수시로 왔다 갔다 해야한다.
1.2 일반적인 코드 조사 시나리오
- 어떤 코드나 소프트웨어 기능이 왜 예상했던 것과 다른 결과를 내는지 이해한다.
- 디펜던시로 사용하는 기술이 어떻게 작동되는지 이해한다.
- 앱 속도 저하 같은 성능 이슈가 불거진 원인을 파악한다.
- 앱이 갑자기 중단된 원인을 찾아낸다.
1.2.1 예상과 다른 아웃풋의 원인을 밝힌다.
아웃풋(output)이란, 데이터를 변경하거나 정보를 주고받는 코드 로직의 실행 결과, 또는 다른 컴포넌트나 시스템을 상대로 한 액션(action)이다.
시나리오 1: 단순 케이스
DB에 레코드를 삽입하는 앱이 있을 때, 실행 결과 이 앱은 레코드의 일부만 추가했다. 이 앱이 생성한 레코드가 DB에 더 많이 있어야 하는데(예상 아웃풋) 실제로는 더 적은 레코드가 DB에 삽입된 것(실제 아웃풋)이다.
시나리오 2: 어디서부터 디버깅을 시작해야 할까?
코드가 엄청 복작합 앱에서 레코드가 DB에 올바르게 저장되지 않는 문제가 생겼다고 해보자. 이때 디버거로 브레이크 포인트를 추가해서 문제의 코드를 알려주면 좋겠지만, 이는 해결책이 되지 않는다.
프로파일러는 앱이 실행되는 동안 어떤 코드가 실행되는지 식별하는 도구다. 이를 이용해서 디버거로 어디서부터 조사를 시작해야 할지 영감을 줄 수 있다.
시나리오 3: 멀티스레드 앱
멀티스레드 아키텍처를 기반으로 한 로직을 처리할 때는 간섭(interference)에 민감한 편이어서 디버거를 사용할 수 없는 경우가 많다.
즉, 디버거를 사용하더라도 실행 시점마다 앱이 다르게 동작할 수 있다는 말인데, 이러한 특성을 하이젠버그 실행(Heisenberg execution)이라고 한다.
때문에 프로파일러를 활용해도 한계가 있는 경우가 많다. 대안으로는 앱에서 로깅을 사용하는 방법(5장), 디버거로 조사할 수 있게 스레드 수를 1개로 줄이는 방법을 생각해볼 수 있다.
시나리오 4: 주어진 서비스에 잘못된 호출 보내기
다른 시스템의 컴포넌트나 외부 시스템과 올바르게 상호작용(interaction)하지 못하는 시나리오도 있다. 가령, 개발한 앱이 다른 앱에 HTTP 요청을 보냈는데, 그쪽 앱에서 HTTP 요청의 포맷이 올바르지 않다고 알려주는 경우가 있다.
이것은 잘못된 아웃풋의 일례다. 먼저, 코드의 어느 부분이 요청을 보내는지 찾아본다. 이 부분을 이미 알고 있다면 디버거를 사용해서 앱이 어떻게 요청을 생성하는지 살펴보고 어디가 잘못됐는지 확인한다. 앱의 어느 코드가 요청을 전송하는지 모르면 프로파일러를 사용하여 찾을 수 있다.
앱이 요청을 주고받는 위치를 특정하기 곤란한 경우에는 다른 앱을 스텁(stub)으로 바꾸어버릴 수도 있다. 스텁은 문제를 식별하기 위한 방향으로 제어할 수 있는 가짜 앱이다. 예를 들면, 스텁으로 요청을 차단시켜 앱이 응답을 무한 대기하도록 만들면 코드의 어느 부분이 요청을 보내는지 알 수 있다.
1.2.2 특정 기술을 습득한다
공식 문서를 읽는게 필수인 것은 맞지만, 어떤 기술은 너무 복잡한 나머지 책이나 스펙을 읽는 것만으로는 배우기가 어렵다. 그러므로 항상 프레임워크나 라이브러리를 깊이 파헤쳐보면서 제대로 이해하는 것이 중요하다.
가령, 스프링 시큐리티를 사용하면서 제대로 작동되지 않는 부분이 있으면 찾아내서 해결해야 하는데, 최선의 방법은 스프링 시큐리티의 코드를 직접 들여다보는 것이다. 하이버네이트의 경우에도 기본적인 내용 외에 실무에서 다양한 경우에 적용하려면 복잡한 지식이 필요하며 프레임워크를 파헤치는게 도움이 된다.
1.2.3 속도 저하 이유를 알아낸다
성능 문제는 앱을 실행하는 시시각각 발생하기 때문에 다른 문제와 마찬가지로 해결 방안을 강구하기 전에 원인을 조사해야 한다. 따라서 성능 문제의 원인을 파악하기 위해 다양한 디버깅 기법을 알고 있어야 한다.
가장 흔히 발생하는 성능 문제는 앱의 응답 속도와 연관이 있다. 하지만 속도 저하는 성능 문제의 한 부류일 뿐이다. 다른 예로, 모바일 기기 배터리를 너무 빨리 소모하는 앱의 디버깅을 했는데 특별한 용도 없이 열린 상태로 실행되면서 성능 및 메모리 이슈를 일으키는 좀비 스레드(zombie thread)를 발견한 적이 있다. 이처럼 배터리 소모 현상도 일종의 앱 성능 문제라고 볼 수 있다. 네트워크 대역폭을 너무 많이 쓰는 것도 전형적인 성능 문제이다.
속도 문제만 놓고보면 많은 경우에 파일이나 DB에서 읽기/쓰기 or 네트워크를 통해 데이터 정송하는 등의 I/O호출 과정에서 발생한다. 이때 원인은 프로파일러로 쉽게 찾을 수 있다.
1.2.4 앱 크래시가 발생하는 이유를 이해한다
앱은 여러 가지 이유로 응답을 멈출 때가 있다. 이러한 앱 크래시는 특정한 조건에서 발생하는 경우가 많은데, 로컬 환경에서는 앱 크래시를 재현하기가 어려워 다른 문제보다 조사하기기가 더 까다로운 편이다.
문제를 조사할 때는 먼저 그 문제를 연구할 수 있는 환경에서 문제를 재현하는 것이 중요하다. 하지만 항상 문제를 재현하기는 거의 불가능하다.
앱 크래시는 보통 다음 두 가지 형태로 일어난다.
- 앱이 완전히 멈춘다.
- 실행은 계속되지만 요청에 응답하지 않는다.
앱이 완전히 멈췄다는 것은 복구 불가능한 에러가 발생했다는 뜻이다. 대부분 메모리 에러 떄문에 일어난다. 자바는 힙 메모리가 가득 차 앱이 더 이상 작동하지 않으면 OOM 에러를 낸다.
힙 메모리 문제를 조사하려면 특정 시점에 힙 메모리에 어떤 데이터가 포함되어 있었는지 스냅샷에 해당하는 힙 덤프를 사용한다. OOM 에러 메시지가 표시되고 앱 크래시가 발생할 때마다 이런 스냅샷이 자동 생성되도록 자바 프로세스를 구성할 수 있다.
앱은 계속 실행되고 있지만 요청을 해도 응답이 없는 경우, 스레드 덤프는 안에서 무슨 일이 일어나고 있는지 분석하는 최상의 도구다.
댓글
아직 댓글이 없습니다