Regarding Network Software Robustness
Posted on Tuesday, November 2, 2010.
1st draft: 2 November 2010
양 철 웅 (cwyang)
cwyang.tistory.com
이 글에서는 네트워크 소프트웨어의 안정성을 강화하기 위한 방안을 기술한다.
네트워크 상의 소프트웨어에게 있어서 안정적이라는 의미는 크게 두 가지로 나눌 수 있다. 그 하나는 제품이 안정적이라는 의미(product robustness)이며 다른 하나는 운영이 안정적으로 이루어질 수 있음(operational robustness)을 의미한다.
제품이 안정적이라는 것은 쉽게 말하여 소프트웨어에 버그가 없음을 의미한다. 소프트웨어가 크래시가 발생하지 않으며, 제작 의도대로 올바른 동작을 한다는 것이다. 개인 사용자를 대상으로 하는 소프트웨어의 경우는 제품 (사용)의 안정성이 가장 중요하다. 운영이 안정적이라는 것은 네트워크 소프트에 있어서 결국 사용 품질을 결정할 수 있는 종단 사용자에게 불편을 끼치지 않는다는 것을 의미한다.
제품은 오류가 없이 안정적이지만 운영이 불안할 수도 있다. 라우터의 경우를 예로 들면 한계 처리 용량보다 많은 용량의 트래픽이 들어올 경우 패킷 로스가 발생하여 서비스 품질이 떨어진다. 이러한 경우 그 이유를 제품이 명확히 제시할 수 없다면 이 제품은 오류는 없으나 운영 면에 있어서 약점이 있는 것이다. 반대로 오류가 많아도 운영이 안정적일 수도 있다. 제품 설계 자체가 액티브-스탠바이 이중 구조로 되어 있어 장애가 발생할 경우 자체 감지하여 백업 처리가 이루어진다면 실 운영 상에는 아무 문제가 없다. 실질적으로 모든 부분을 장애에 강하게 설계하기 힘들기 때문에 많은 경우 제품의 오류는 운영의 불안정을 초래한다. 하지만 확실한 것은 제품의 안정성 및 운영의 안정성 두 가지 모두가 네트워크 소프트웨어의 경우 중요하다는 것이다.
안정적인 제품은 테스트를 통해 만들어진다. 제품은 모니터링(계측)을 통하여 안정적인 운영이 이루어진다.
먼저 테스트에 대하여.
테스트를 통해서 제품을 안정화 할 수가 있기 때문에 개발 방법론으로서 테스트 주도 개발(test-driven development)이 주목을 받고 있다. 또한 대안 언어로서 주목 받고 있는 프로그래밍 언어 중에는 ML, Haskell, Erlang등이 있는데, 이들의 공통점은 함수형 언어라는 것이다. 함수형 언어는 입력 값에 대응하여 결과를 출력하는 메카니즘에 집중하며, 사이드 이펙트를 악의 축으로 삼는다. 테스트 기반 개발은 유닛 테스트를 반드시 만들도록 하고, 개발 중의 중간 단계에서부터 유닛 테스트를 통과해야만 되도록 하여 테스트를 강제화 한다. 함수형 언어는 프로그램의 기본 단위가 사이드 이펙트가 없는 함수이다 보니 유닛 테스트를 쉽게 만들 수 있다는 장점을 가진다. 이 두 가지 개발 메카니즘은 제품의 품질 측면에서 상당한 효과를 가져온다고 알려져 있다.
그러면, 이 두 가지 개발 메카니즘을 적용하지 않았던 소프트웨어에 대해서는 어떻게 하여야 하는가? 유닛 테스트도 코드 베이스를 많이 커버하지 못하고, 복잡한 로직과 사이드 이펙트들로 인해 굉장히 난감한 상황을 만들어내는 소프트웨어라고 한다면… 코드 베이스를 모두 버리고 무에서 다시 시작해야 하는가? 그것도 나쁜 선택은 아니라고 생각하지만… 그것이 선택지가 되지 않는 경우는 대개 “테스트를 최대한 많이 한다” 라는 접근 방법을 택한다. 그러나 테스트를 최대한 많이 하는 것 보다 더 중요한 것이 있는데, 그것은 테스트를 하기에 앞서서 제품 자체를 테스트가 가능하도록 보완하여야 한다는 것이다.
다음은 모두 같은 이야기다.
- 제품이 테스트가 가능하여야 한다. Software should be testable.
- 제품 기획 단계에서부터 테스트를 어떻게 할 것인지가 도출되어야한다. Test strategy should be determined specifically in the planning phase.
- 테스트 가능성은 그 자체가 필수적인 기능이다. Testability is a feature itself.
기획 단계에서부터 테스트를 염두에 둔 기획이 들어가지 않으면 나중에 돌아오는 것은 개발자로부터의 “이 제품은 네트워크 소프트웨어라는 특성 상 테스트가 힘듭니다"라는 말이다.
최근의 개발방법론에서 QC팀이 있는 경우 기획단계에서부터 참여를 권하는 것은 이러한 이유이다.
두 번째, 모니터링에 대하여.
네트워크 소프트웨어가 잘 운영되고 있는 지를 알기 위해서는 운영 상태를 계측(measure)해야 한다. 즉, 계측을 통해 다음 사항을 알 수 있어야 한다.
- 소프트웨어가 장애로 인해 “죽는다” (process crash or process stall)
- 요청에 대한 응답 처리가 실패한다 (logic error or unexpected outside reason)
- 기대 성능에 부합하지 못한다. (low performance)
계측 대상은 여러가지가 있겠지만, 계측 방법은 외부에서 계측하는 방법과 자기 자신이 계측하는 두 가지로 나뉘어진다. 외부에서 계측하는 것에 의한 장점은 네트워크 소프트웨어의 특성 상 피어(peer)를 효과적으로 시뮬레이션 할 수 있다는 것이다. 계측 주체가 분리되어 있으므로 계측 대상의 오류에 자유롭다는 장점 또한 존재한다. 자기 자신이 계측하는 방법은 외부 계측에 비해 소프트웨어의 모든 상태를 알 수 있다는 점에서 장점이 존재한다.
어떤 파라메터를 어떤 방법으로 계측하여 위에 언급된 소프트웨어 운영에 필수적인 안정성을 답보할 것인가. 이것 역시 기획 단계에서부터 미리 고려되어야 한다.
즉, 중요한 것은 소프트웨어를 “Testable”하며 “Measurable”하게 기획, 개발해야 한다는 것이다. 만약 그렇게 되어 있지 않다면, 그렇게 되도록 소프트웨어를 개선하는 것이 제일 먼저 해야 할 일이라는 것이다.
Testable Software
소프트웨어를 테스트 가능하게 만든다는 것은 (1) 소프트웨어의 구성 요소인 함수가 테스트 가능하며 (2) 소프트웨어가 구현한 기능이 테스트가 가능하다는 것이다. 또한 테스트가 가능하다는 것은 (Input, Environment) 쌍이 결정되면 반드시 유일한 Output이 존재한다는 것이다. 이 때에 Environment는 소프트웨어의 상태(state)일 수도 있고, 외부의 환경일 수도 있다. 많은 경우에 있어서 Input/Output보다 Environement는 복잡한 상태를 가지게 되므로 Environment가 Test에 영향을 미치지 않을 경우 테스트는 보다 용이해지며 궁극적으로 사람의 개입 없이 자동화가 가능하다. 함수형 언어가 각광 받는 것은 각 함수에 대한 테스트 - 유닛 테스트 - 에 있어서 복잡한 상태인 Environment를 고려하지 않아도 되기 때문에 쉽게 테스트를 할 수 있고, 프로그램의 로직도 단순해지기 때문이다. 이와 마찬가지로 기능을 테스트 함에 있어서, 각 기능이 Environment에 의존하지 않거나 의존성이 매우 적으며, Input에 대한 Output의 검사가 용이하다면 기능 테스트 역시 쉽고 자동화될 수 있으며 소프트웨어의 안정성이 향상된다.
테스트를 감안 하지 않고 기획, 설계된 소프트웨어의 취약성은 위에 언급된 주로 Input에 대한 Output의 검사에 대한 메카니즘의 부재에 기인한다.
전형적인 네트워크 소프트웨어는 다음의 구성 요소를 가진다.
-
UI (CLI, GUI): 사용자가 설정을 입력한다.
- Input: 사용자의 입력
- Output: 설정 화일
- Environement: 현재 설정상태
-
설정 처리부: 설정내용을 소프트웨어가 읽어들여 기동준비를 마친다
- Input: 설정화일
- Output: 새로운 운영상태
- Environment: 현재 운영 상태
-
네트워크 처리부 - I: 네트웍 입력에 대해 어떠한 설정을 수행할 것인가를 결정한다.
- Input: ingress packet/session, 혹은 request
- Output: 해당 packet, session 혹은 request에 부여되는 설정상태 (session status)
- Environment 현재 운영 상태
-
네트워크 처리부 - II: 결정된 설정에 준하여 작업을 수행하고 후처리를 한다.
- Input: ingress packet/session, 혹은 request
- Output: egress packet/session, 혹은 response, 운용로그, 갱신된 통계치
- Environment: 현재 운영 상태
각 요소는 모두 중요하다. 일단 설정이 완료된 상태에서는 네트워크 처리 부만이 운용에 관여하므로 상대적으로 그것이 가장 중요하나, 사용자의 UX측면에서는 다른 두 부분도 중요하다. 고객들 (그리고 경영진, 심지어는 개발자까지)은 대개 네트워크 처리 부에 비하여 UI, 설정 처리부가 중요하지 않으며 개발 및 설계 난이도도 낮다고 생각하는 경향이 있는데, 이는 물론 사실과는 다르지만 이것이 의미하는 것은 난이도가 높은 부분에서 오류가 발생하면 일정 부분 수긍하지만 난이도가 낮(다고 생각하는)은 부분에서 오류가 발생하면 제품 자체의 신뢰성을 의심 받게 된다는 것이다. 최근의 아이폰 등의 성공은 어플리케이션 로직보다는 완벽한 UX를 제공하고 있다는 것에 기인한다. 네트워크 소프트웨어의 사용자라면 껍질을 보기보다는 열매를 볼 것이라고 기대하는 것은 경험상 섣부른 판단이다.
따라서 UI, 설정 처리부, 네트워크 처리부는 모두 테스트가 가능하여야 한다. 여기서부터는 제품의 특성에 따라 테스트의 방식이 달라지지만, 공통적으로 고려해야 할 사항들을 기술한다.
UI
- UI는 테스트 가능해야 한다.
- UI는 기본적으로 CLI가 기반이 되어야 한다. 그 이유는 CLI만이 용이하게 테스트를 할 수 있기 때문이다. (네트웍쟁이들이 CLI를 선호하니까, CISCO IOS가 편하니까 이런 이유는 절대 아니다. 오직 Testability 때문이다.) GUI도 용이하게 (자동으로 반복화 된) 테스트를 할 수 있는 메카니즘이 있다면 GUI를 기반으로 해도 좋다.
- 이러한 가정 하에서 GUI의 가장 바람직한 형태는 CLI를 Wrapping하는 형태이다. 여건이 안되면 GUI는 제공 안 하면 그만이다. 왜냐하면 품질 때문이다.
- CLI에 특정 Action을 했을 경우 그것이 제대로 반영 되었는 지를 검사할 수 있어야 한다.
- CLI는 현재의 설정 상태를 파싱 가능한 텍스트로 출력할 수 있어야 한다.
- CLI는 특정 설정 작업을 한 후 설정 화일을 쓰기 전, 혹은 쓰고 난 다음, 이전 상태와의 차이를 출력할 수 있어야 한다. 그리고 행해진 작업이 그 차이와 정확히 동일한 것 인지를 판단할 수 있어야 한다.
- CLI 설계 시에 CLI의 주된 사용자 중의 하나는 Test Software라는 사실을 명심한다.
설정 처리부
- 설정 화일은 비교가 가능해야 한다. 세부 설정들은 total-order를 가지고 있어야한다. 즉 정렬이 가능해야, 정렬 후에 비교할 수 있다는 것.
- 소프트웨어의 운영 상태, 즉 설정 상태를 설정 화일로 export하는 기능이 구현되어야 한다.
- 위의 두 가지 조건이 만족 되면, 설정 화일을 읽어 들인 후 다시 export하여 두 결과를 비교함으로써 설정 처리 과정의 검증이 가능하다.
- 설정 화일이 고 수준이어서 실제 운영되는 소프트웨어에서는 그것을 바로 생성해 낼 수 없다면, 소프트웨어가 덤프한 상태를 설정 화일로 변환할 수 있는 tool을 개발한다.
네트워크 처리부 - I
- 네트워크 소프트웨어는 입력, 즉 패킷이나 요청세션에 대해 기설정된 여러 설정값이 대응되어 운영된다. 즉 event(입력)가 특정 condition(설정조건)을 만족하면 action (설정작업)을 하는 형태의 반복인 것이다. 소프트웨어에 따라 중점이 event가 condition을 만족하는 것인지 (detection, measurement), 아니면 특별한 action을 하는것에(manipulation) 있는지가 다르지만 단계가 구분되어 지는 것은 공통적이다.
- 이 단계에서는 event가 발생하는 경우 특정 condition이 올바르게 mapping되는지를 확인 할 수 있어야 한다. event가 발생하는 경우 특정 action을 올바르게 하는지를 확인하는 것은 (1) 틀린 condition에 match되었는데, 거기서 특정 action과 동일한 action이 설정되어 있어서 오류가 발견되지 않을 수 있거나 (2) 특정 condition에 대해서는 detectable한 action이 없는것이 정상일 수도 있기 때문에 바람직한 확인 방법이 아니다.
- 다음의 일부 접근 방법들을 고려한다.
- match된 condition에 대한 식별자가 존재해야한다. (어찌되었든 최종적으로 condition이 맞는지 검증해야하므로)
- condtion 식별자를 검증할 수 있어야 한다.
- req/resp구조이며, resp에 optional field가 가능하면 그를 이용하여 condition 식별자를 제공한다.
- 위에 해당 하지 않는 경우
- 디버그/테스트 로그를 이용한다.
- probing/diagnosis 용도의 별도 네트워크 포트를 이용한다.
- 네트워크 소프트웨어가 충분히 복잡하다면 설정을 이리저리 바꿔가면서 테스트를 해야 하는 상황이 발생한다. 이 경우 간편하게 특정 설정을 변경할 수 있는 기능이 있다면 편리하다.
- req/resp 구조이며, req에 optional filed가 가능하면 그를 이용하여 inline configuration을 전송한다.
- 그렇지 않으면 probing/diagnosis용도의 별도 네트워크 포트를 이용한다.
- CLI와 소프트웨어의 연동이 충분히 효율적이라면, 실 사용면을 보다 더 충실히 재현한다는 면에서 원격 CLI제어를 이용하는 방법이 권장된다.
네트워크 처리부 - II
- 이 단계에서는 (event, condition)의 조합에 의해 생성되는 결과인 action이 의도한 대로인지를 검증할 수 있어야 한다. 예를 들자면 다음과 같다.
- 패킷을 라우팅 해야 했으면 실제로 라우팅 했는지
- 접근 로그를 기록해야 했으면 실제로 기록 했는지
- 데이터를 압축해서 전송해야 했으면 데이터를 압축 했는지
- 다음의 일부 접근 방법들을 고려한다.
- 수행한 행동, 생성 및 전송한 데이타에 대한 식별자 (action 식별자)가 존재해야 한다.
- action식별자를 검증할 수 있어야 한다.
- condtion 식별자와 유사한 접근 방법을 사용할 수 있다.
소프트웨어가 이들 요건을 만족하였다면 다음과 같은 장점을 기대할 수 있다. (1) 테스트 수트test suite를 용이하게 만들 수 있다. (2) 만들어진 테스트 수트는 개발 단계에서 개발을 보조하기 위하여 사용된다. (3) 또한 회귀테스트regression test로 사용된다. (4) 제품이 프로덕션production 사이트에 배치되었을 때에 발생한 최악의 상황을 풀어나갈 수 있다.
Measurable Software
테스트 가능testable하도록 소프트웨어를 만들고, 충분히 테스트를 거듭한다면 훌륭한 품질의 소프트웨어가 탄생한다. 그러나 프로덕션 사이트에서는 (아무리 기획과 설계가 완벽했다 할 지라도) 항상 예기치 못했던 상황이 발생하여 문제가 나타난다. 기획이나 설계에서 세운 가정에 들어맞지 않는 상황을 맞이한다던가, 각 기능들이 복잡하게 서로 연관되는 가운데 오류가 나타난다던가, 많은 연결 혹은 과도한 양의 트래픽을 처리함에 있어서 성능 상의 문제를 겪게 된다던가 하는 것은 드문 일이 아니다. 일단 이러한 이상 상황이 발생하는 것을 알 수 있다면 개발자는 적절한 조사 및 조치를 통해 상황을 해결 할 수 있다. 특히 테스트 가능한 소프트웨어라면 큰 난관을 겪지 않을 수도 있다. 그러나 이상 상황이 발생한 것을 쉽게 알 수 없다면 해결도 할 수 없다. 이것은 큰 문제가 아닐까?
이상 상황이 발생했는데 어떻게 모를 수가 있는가 하고 반문할 수 있으나, 이상 상황이 발생했는데 꼭 알 수 있다는 보장은 없다. (1) 이상 상황인지 아닌지 모르는 경우: 설계에 의하면 100km/h를 내야 하는 자동차가 실제로 70km/h밖에 나오지 않을 때. 운전자가 설계자가 아닌 다음에야, 아 이 자동차는 70km/h밖에 안 나오는구나… 라고 생각하기 마련이다. (2) 이상 상황이 불규칙하게 발생하는 경우: 키를 돌렸을때 가끔씩 시동이 안걸리는 경우. 운전자가 차에 대한 기대치가 높지 않으면 원래 세상의 이치는 그럴 수도 있다.. 라고 생각 할 수 있다. 물론 브레이크를 밟을 때 가끔 제동이 안 되는 경우는 예외지만. (3) 이상 상황이 제품 탓인지 주위 환경 탓인지 알 수 없는 경우: 키를 돌려도 시동이 안 걸리는데 창문을 열고 보니 다른 사람들도 다 시동을 못 걸고 있다. “어떻게 된 일이죠?” “이 근처는 가끔 전자 펄스 충격파EMP shockwave가 터지는 데라서 그런가 봐요.” 자동차는 정상일까? (4) 이상 상황이 발생해도 이용에 지장이 없는 경우: 가솔린 엔진이 운행중 정지해도, 하이브리드 자동차는 배터리의 힘으로 운행을 계속한다. 계기판을 특별히 주의 깊게 보지 않는 운전자는 가솔린 엔진이 정지한 것을 전혀 모른다.
중요한 것은 이상 상황을 파악하는 것, 대처는 그 다음 문제이다. 네트워크 소프트웨어의 운용에 있어 검토하여야 할 사항을 다음과 같이 제시한다.
운영 환경
- 처리를 위해 들어오는 입력 패킷/요청 세션
- 회선이 물리적으로 빠지지는 않았는가. 패킷의 경로가 변하여 입력으로 아무것도 들어오지 않아 아무 처리도 하고 있지는 않은가. 해당 장비가 아무 처리를 하고 있지 않게 될 경우 전체 솔루션 상으로는 장애일 수 있기 때문이다. 유해 사이트 차단 장비의 경우를 예로 들 수 있다.
시스템 부하
- CPU 부하, 디스크 부하, 시스템 부하load average, NIC 패킷 처리량. NIC 패킷 드롭 상태.
- 시스템 과부하는 바로 이상 상태이다. 90%를 상회하는 부하이거나, 특정 값을 넘는 부하를 넘는 경우 바로 알 수 있어야 한다.
컴퍼넌트 상태
- 컴퍼넌트 별 구동 상태
- 시스템이 여러 세부 컴퍼넌트로 이루어지는 경우, 각 컴퍼넌트가 정상 작동을 하고 있는 지를 알 수 있어야 한다. 각 컴퍼넌트 별로 구분되는 기능을 하는 경우, 그를 확인할 수 있는 메카니즘을 제공한다. 예를 들자면 각 컴퍼넌트 별 MRTG를 일목요연하게 제공하여 시스템이 전반적으로 정상 작동 하는 지를 한 눈에 파악할 수 있게 하거나, 아니면 각 컴퍼넌트가 주기적으로 동작 상태를 한 곳에 저장하여, 그를 운영자에게 알려주거나 하는 방법이 있을 수 있다.
- 컴퍼넌트는, 분산 시스템에서는 하나하나가 소프트웨어가 될 수도 있고(map-reduce 시스템에서 워커worker와 어그리게이터aggregator 각각), 단일 시스템에서는 시스템을 구성하는 모듈이 될 수도 있다(방화벽에서 세션 송수신부, 프로토콜 해석부, 데이터베이스 탐지부 각각).
세션 처리 시간
- 세분화된 세션 처리 시간
- 강한 실시간성이 요구hard realtime constraint되는 상황이라면 요구되는 세션 처리 시간의 절대값을 넘어가는 지를 확인하여야 한다.
- 그러나 굳이 강한 실시간성이 요구되지 않고 베스트에포트best effort로 동작하는 경우라면, 즉 처리 성능의 향상을 하드웨어를 향상시켜 달성하게 되는 경우라면 시스템 과부하 상태를 제외하고 처리 성능이 저하될 수 있는 요소로는 (1) 예상치 못한 많은 처리 혹은 (2) 외부 네트워크 상황의 병목이 있을 수 있다.
- 세션 처리 시간을 측정하고, 그것을 또 다시 혼자 처리하는 시간과 외부 시스템과 상호 작용하는 시간으로 구분하여 확인한다. 상호 작용하는 외부 시스템이 많을 경우 각각에 대해 구분한다.
- 프로덕션 사이트에서 세션 처리 시간을 측정하는 경우 성능의 저하가 우려된다면 샘플링을 통해 측정한다.
제약 조건 확인
- 미리 설정된 제약 조건constraint의 확인
- 각 소프트웨어의 특성에 맞는 제약 조건을 확인 할 수 있어야 하단. 주로 특정 성능 파라메타, 통계 값이 어떤 값을 가져야 한다라는 형태이다.
- constraint는 일반적인 경우 기획 단계에서 도출되어야 한다.
위에 기술한 다섯 가지 종류의 정보를 파악할 수 있어야 만이 안정적인 운영이 가능하다. 각 정보는 운영자가 요청하는 즉시 파악될 수 있어야 하며, 과거의 값이 로그나 MRTG 형태로 저장되어 어떠한 시점에 운영 상황의 변화가 있었는지 알 수 있어야 한다. 이렇게 과거의 정보들이 저장되는 경우 베이스라인에 기반한 이상 변화를 감지하여 알려주는 방법 또한 적용을 검토할 수 있다.
위의 다섯 가지에 다음의 추가도 검토해 볼 수 있다.
기능 확인
- 기능 검사를 실제 프로덕션 사이트를 대상으로 수행
- 상기 다섯 가지 종류는 전반적인 상태 검사 만을 다루고 있다. 소프트웨어가 복잡해 질 수록 전반적인 검사가 아닌 세부적인 검사가 필요하다.
- 소프트웨어를 테스트하는 방법을 그대로 이용한다. 이 경우 실제 서비스에 영향을 미치면 안 되기 때문에 설정의 영속적인 변경이 있어서는 안되며 테스트를 하는 세션은 실제 운영과는 격리된 세션이어야 한다.
- 확인하는 방법은 확인 대상의 소프트웨어와 별도의 모니터링 프로그램의 형태가 된다. 즉, 네트워크 클라이언트/서버를 시뮬레이션하는 것이다.
- 일반적인 경우 이 단계까지 필요할 것이라고는 예상되지 않는다.
안정적인 소프트웨어의 운영을 위해서는 위에 기술된 여러가지 정보를 소프트웨어가 제공할 수 있어야 한다. (1) 즉, 기획/설계 단계에서 어떠한 운영 정보를 제공할 것인가를 결정하여야 한다. (2)그 다음에는 결정된 정보를 과거 이력을 용이하게 검토할 수 있는 형태, 즉 RRDTOOL이나 MRTG형태로 제공 해야 한다. (3) 만약 이미 구축되어 있는 시스템에서 해당 정보가 부족하다면 해당 정보를 신규로 제공하도록 하거나, 기존에 기록하고 있는 로그를 주기적으로 처리하여 분석이 용이한 형태로 이차 가공하여 사용할 수도 있다. (4) 일단 분석이 가능한 형태로 데이타가 수집된다면, 그를 운영자에게 제공하여야 한다. 제공하는 방법은 여러가지가 있을 수 있겠으나 중요한 것은 한 화면, 테이블, 혹은 리포트 형태로 _일목요연_하게 제공하여야 한다는 것이다. 알람, 베이스라인 분석등 형태 자체는 부차적인 결정 사항일 뿐이다. 운영자가 항상 띄워 놓는 웹 페이지 하나에서 시스템의 정상/비 정상만 알 수 있다면 충분한 것이다.