The Hierarchy of Controls (or how to stop devs from dropping prod)

Hillel Wayne

위험 통제 계층 (혹은 개발자가 프로덕션을 날리지 못하게 하는 방법)

원문은 Hillel Wayne님이 에 게재했습니다. 이 블로그 구독하기

얼마 전 한 기계 공학자가 내게 위험 통제 계층(Hierarchy of Controls, HoC)이라는, 작업장 안전 분야에서 중요한 개념을 소개해 줬다. 1

(출처)

위험으로부터 사람들을 보호하기 위해 시스템 설계자는 가능한 한 가장 효과적인 통제 수단을 사용해야 한다. 즉, 대체보다 제거가, 공학적 통제보다 대체가 우선한다는 뜻이다.

위험 통제 계층을 소프트웨어 엔지니어링에 적용할 수 있을까? 소프트웨어 환경은 물리적 환경과 다르지만, 끌어낼 만한 아이디어가 있을지도 모른다. 주니어 개발자 시절 내가 일으켰던 프로덕션 장애라는 예시 문제에 HoC를 적용해 보는 연습을 해 보자.

문제

지금으로부터 10년쯤 전, 나는 프로덕션에서 발생한 이슈를 디버깅하고 있었다. 프로덕션에 SSH로 접속한 셸과 로컬 개발용 셸을 나란히 띄워 놓고, 탭을 잘못 눌러 엉뚱한 셸에서 잘못된 쿼리를 실행해 버렸다.

그때 나는 새로운 교훈을 얻었다. 백업으로부터 프로덕션 데이터베이스를 복구하는 방법이었다.

공정 안전에서 위험(hazard)이란 부상을 가능하게 하는 모든 것이다. 사다리는 추락을 가능하게 하고, 유압 프레스는 손가락이 짓눌리는 사고를 가능하게 한다. 내 사례에서 위험은 아마도 무제한 권한을 가진 프로덕션 셸이었을 것이다. 그것이 프로덕션 데이터의 손실을 가능하게 했다. 구체적인 부상(injury)에는 내가 그랬던 것처럼 데이터베이스 자체를 날려버리는 경우와 데이터베이스에 delete 쿼리를 실행하는 경우가 모두 포함된다. 나는 이 두 가지 부상을 예로 들어 다양한 위험 통제 수단을 논해 보겠다.

먼저, 몇 가지 단서

HoC는 사람을 기계로부터 보호하기 위해 만들어진 것이지, 기계를 사람으로부터 보호하기 위해 만들어진 게 아니다! 대부분의 개념은 꽤 잘 옮겨온다고 느꼈지만, 개인 보호구(PPE) 부분에서는 좀 난관에 부딪혔다.

또한 무언가를 어느 범주에 넣어야 하는지를 두고 따질 여지도 많다. 이 글을 쓰면서 나 역시 범주들 사이의 질적인 차이를 파악하려 애쓰고 있다. 이 글은 완전 초보자인 내 개인적인 해석이다.

마지막으로, HoC는 부상 이후의 회복이 아니라 부상 예방에 관한 것이다. “정기적으로 데이터베이스를 백업하라”나 “장애 후 포스트모템을 수행하라” 같은 소프트웨어 모범 사례는 이 계층 구조에 들어맞지 않는다.

통제 수단들

모든 인용문은 OSHA HoC 워크시트에서 가져왔다.

제거

제거(Elimination)는 위험이 더 이상 존재하지 않도록 하는 것이다.

제거는 사고를 예방하는 가장 직접적인 방법이다. 위험 자체를 없애 버리는 것이다. OSHA의 각종 자료는 모두 같은 예를 든다. 추락 부상을 줄이고 싶다면 높은 곳에서 작업하는 일 자체를 없애라는 것이다. 우리 사례에 대입하면 프로덕션 환경을 없애거나 데이터베이스 자체를 없애는 것이 제거에 해당한다.

둘 다 그다지 현실적이지는 않다. 대부분의 HoC 자료도 제대로 된 제거는 종종 불가능하다고 바로 짚는다. 우리는 그 위험 물질이 공정에 필수적이기 때문에 다루는 것이다. “제거”는 “이 위험한 것이 정말 필요한가?”를 점검하는 용도로 가장 유용해 보인다. 필수적인 위험은 제거할 수 없지만, 비필수적인 위험은 제거할 수 있다. 2020년에 지원이 종료되기 전까지 Adobe Flash는 보안 취약점의 가장 큰 원인 중 하나였다. 자신을 보호하는 가장 쉬운 방법은? Flash를 삭제하는 것이었다.

대체

대체(Substitution)란 물질이나 공정을 교체해 위험을 줄이는 것을 의미한다.

제거와 달리 대체는 위험 자체는 유지하되 그 위험도를 낮춘다. 위험이 독성 농약이라면 독성이 더 낮은 농약으로 바꿀 수 있다. 시끄러운 기계가 위험이라면 더 조용한 기계로 교체할 수 있다. 위험이 메모리 안전성이 보장되지 않는 언어라면 Rust를 쓰는 식이다.

우리 문제에서는 몇 가지 가능한 대체 방안을 생각해 볼 수 있다. 프로덕션 셸을 더 권한이 약한 셸로 대체하는 것이다. 예를 들어 하나의 “프로덕션” 서버가 데이터베이스의 읽기 전용 복제본(read replica)에만 접근할 수 있도록 한다고 해 보자. 그러면 delete 쿼리는 아무 일도 일으키지 못하고, 데이터베이스를 날려도 데이터가 손실되지 않는다. 다른 방법으로는 이벤트 소싱(event source) 모델 같은 불변(immutable) 기록 시스템을 쓰는 것이다. 그러면 “데이터 삭제”는 “데이터베이스에 삭제 기록을 추가하는” 형태가 된다. 실수로 삭제하더라도 그 위에 “삭제 취소” 기록을 추가하는 것만으로 간단히 되돌릴 수 있다.

공학적 통제

공학적 통제(Engineering controls)는 위험이 작업자에게 접촉하지 못하도록 막아 노출을 줄인다. 다만 작업자가 여전히 업무를 수행할 수 있도록 한다.

공학적 통제는 위험 자체의 위험도는 그대로 두되, 추가적인 물리적 설계를 통해 사고의 위험과 심각도를 완화한다. 이는 여러 가지 방법으로 할 수 있다. 작업자가 위험에 노출될 필요 자체를 줄이거나, 위험이 사고로 이어질 가능성을 낮추거나, 사고가 부상으로 이어질 가능성을 낮추는 식이다.

SawStop™ (출처)

공학적 통제는 제거나 대체보다 창의성을 발휘할 여지가 훨씬 많다. 공학적 통제에 대한 몇 가지 아이디어는 다음과 같다.

  • 모니터링과 가시성(observability)이 더 좋았다면 애초에 프로덕션에 로그인할 필요가 없었을 것이다.
  • 권한 정책을 더 잘 갖췄다면 데이터베이스를 날리는 행위 자체를 환경에서 금지하거나, 그런 작업을 하려면 특수한 개발자 키를 요구하도록 할 수 있었을 것이다.
  • 혹은 주니어 엔지니어는 애초에 프로덕션 환경에 대한 접근 권한이 없어야 했을지도 모른다. 무언가를 디버깅하고 싶다면 더 경험 많은 사람에게 부탁해야 하는 식으로 말이다.
  • 자동 로그아웃 기능이 있었다면, 실수로 Alt-Tab으로 넘어갈 때까지 방치된 채 남아 있는 유휴 프로덕션 터미널을 없앨 수 있었을 것이다.

공학적 통제 중에서도 효과에 차이가 있다. 유명하게 “약한” 통제로는 확인 대화상자가 있다.

$ ./drop_db.sh

This will drop database `production`.
To confirm, type y: [y/N]

이 방식의 문제는 로컬 환경에서 이 작업을 자주 실행하다 보면 y를 누르는 것이 몸에 배어, 프로덕션에서 똑같이 행동했을 때 하루를 망치게 된다는 점이다.

유명하게 “강력한” 통제로는 “전체 이름” 확인 대화상자가 있다.2

$ ./drop_db.sh

This will drop database `production`.
To confirm, type `production`:

몸에 밴 습관이 있더라도 그건 local이라고 입력하는 습관일 테니, 이 방식은 동작을 차단해 준다. GitHub에서 리포지토리 삭제를 시도해 보면 실제 사례를 볼 수 있다.

OSHA가 든 공학적 통제 사례 중 일부는 내 눈에는 대체처럼 보이고, 그 반대도 마찬가지다. 둘을 구분하는 내 나름의 기준은 공학적 통제는 실패할 수 있지만 대체는 실패할 수 없다는 것이다. 권한 설정이 잘못되어 실제로는 데이터베이스 삭제를 막지 못한다면 어떻게 될까? 반면 환경이 읽기 전용 복제본에만 노출되어 있다면, 쓰기 복제본을 마법처럼 날려버릴 수는 없다. 아마도.

물론 이 기준도 완벽하지는 않다! C를 Rust로 바꾸는 것은 대체에 해당하지만, Rust의 일부 보장은 unsafe를 쓰면 우회할 수 있기 때문이다.

관리적 통제

관리적 통제(Administrative controls)는 작업 방식을 바꾸거나 작업자에게 관련 절차, 교육 또는 경고를 제공해 더 많은 정보를 주는 것이다.

공학적 통제는 기술을 바꾸고, 관리적 통제는 사람을 바꾼다. 사람이 기술과 상호작용하는 방식을 바꾸는 통제는 어느 범주에든 속할 수 있다. “전체 이름” 확인 대화상자가 관리적 통제인지 공학적 통제인지를 두고 나는 길게 토론한 적도 있다. 어느 쪽으로든 충분히 논리적으로 주장할 수 있다.

관리적 통제에 대한 몇 가지 아이디어는 다음과 같다.

  • 주니어는 프로덕션에 접속해서는 안 된다는 회사 정책
  • 데이터베이스를 날리는 것이 얼마나 쉬운지를 보여주는 교육 영상 시청
  • 한 번에 하나의 터미널만 열어 두기
  • 다른 개발자와 페어 프로그래밍할 때만 프로덕션에 접속할 수 있도록 하기
  • 근무 시간과 크런치 타임을 줄여 엔지니어의 수면 부족을 완화하기
  • 운영 문제를 정기적으로 워게이밍하기.

OSHA는 “자동화된 경고”도 관리적 통제의 한 종류로 분류한다. 예를 들어 “프로덕션에 로그인하면 Slack에 메시지가 게시되는” 식이다.

계층 구조에서 관리적 통제가 공학적 통제보다 낮은 이유는 몇 가지가 있다. 첫째, 공학적 통제는 시스템에 내장되어 있지만 관리적 통제는 사회적이다. 모두가 이를 따르도록 교육받아야 한다. 둘째, 내가 본 모든 HoC 사례에서 공학적 통제는 깨뜨리는 데 노력이 필요하지만, 관리적 통제는 따르는 데 노력이 필요하다. 급하거나, 깜빡하거나, 주의가 산만하면 이를 지키지 않을 수도 있다.

일부 관리적 통제는 공학적 통제로 끌어올릴 수 있다. 주니어 엔지니어는 절대 프로덕션에 SSH로 접속해서는 안 된다는 회사 정책은 관리적 통제이며, 모두가 규칙을 준수하는 것에 의존한다. 주니어에게 해당 SSH 키 자체를 주지 않는 설정은 공학적 통제다.

개인 보호구

개인 보호구(Personal protective equipment, PPE)는 작업자를 보호하기 위한 의복과 장비를 포함한다.

이는 가장 낮은 단계의 통제다. 위험으로부터 사람들을 보호하기 위해 장비를 제공하는 것이다. PPE는 부상 위험을 줄이거나(반사 조끼를 입으면 지게차에 치이는 사고를 당할 가능성이 낮아진다) 부상의 심각도를 줄일 수 있다(안전모가 물체가 내게 떨어지는 것을 막아주지는 않지만 충격을 완화해 준다).

PPE는 소프트웨어에서 가장 적용하기 어려운 통제라고 생각한다. 우선 HoC는 사람을 보호하기 위한 것이지만, 소프트웨어에서는 시스템을 보호하고 싶어 하기 때문이다. 그렇다면 소프트웨어에서의 PPE는 시스템에 대한 손상을 막기 위해 사람이 착용하는 것인가, 아니면 사람으로부터의 손상을 막기 위해 시스템이 착용하는 것인가? “사람용 PPE”의 예로는 프로덕션 터미널은 빨간 배경으로, 개발용 터미널은 파란 배경으로 쓰는 것을 들 수 있다. 내가 떠올릴 수 있는 “시스템용 PPE” 사례들은 모두 사실상 공학적 통제에 해당한다고 볼 수 있다.

둘째, PPE가 공학적 통제가 아닌 이유는 공학적 통제가 위험 자체를 수정하는 반면, PPE는 사람과 위험 사이에 있는 제3의 존재이기 때문이다. 하지만 사람과 소프트웨어 사이에 있는 것은 무엇이든 결국 또 다른 소프트웨어다! “curl 대신 Postman을 쓰라” 같은 것조차 “진정한” PPE라기보다는 공학적·관리적 통제가 혼합된 것에 가깝다.

PPE가 더 말이 되는 경우를 두 가지 정도 생각해 볼 수 있다. 첫 번째는 보안 분야로, 보안 브라우저, 2FA, 비밀번호 관리자 같은 것들이 모두 PPE에 해당한다. 두 번째는 손목 터널 증후군, 허리 통증, 눈의 피로 같은 소프트웨어 개발자에게 흔한 부상을 줄이기 위한 PPE다. 이런 경우에 “통하는” 이유는 애초에 HoC가 설계된 목적대로 사람이 다치는 상황이 관련되어 있기 때문이다.

PPE가 계층 구조에서 관리적 통제보다 낮은 이유는 직원들이 PPE를 효과적으로 사용하려면 규율과 교육이 필요하기 때문이다. 현실에서 PPE는 종종 부피가 크고 불편하며, 90%의 시간 동안은 실제로 보호 역할을 하지 않는다(위험한 상황에 있지 않기 때문이다). 한 논문에 따르면 건설 사고에 관한 연구에서 조사 대상 사고의 65%에서 부상당한 작업자가 PPE를 착용하지 않고 있었다고 한다. PPE의 효과를 극대화하려면 사람들을 교육하고 착용을 강제해야 하며, 이는 결국 이미 관리적 통제가 갖춰져 있어야 한다는 뜻이다.

HoC에 관한 기타 참고 사항

통제 수단은 함께 쓰이도록 고안되었다

상위 단계의 통제는 위험을 제거하는 데 더 효과적이지만 구현하기가 더 어렵다. 하위 단계의 통제는 덜 효과적이지만 더 범용적이고 구현 비용이 저렴하다.

소프트웨어는 본질적으로 공학적 통제에 유리하다

이 생각이 정확히 무엇인지 아직 정리하는 중이지만, 현실 세계에서 사람들은 “자연스러운 인터페이스”, 즉 공간에 놓인 물체로서 위험과 상호작용한다는 것이다. 유압 프레스로 작업할 때, 기본적으로 나는 손을 그 안에 넣을 수 있다. 부상을 막으려면 시스템에 추가적인 물리적 장치를 더하거나, 사람들이 그 물리적 장치를 올바르게 사용하는 법을 교육해야 한다.

소프트웨어에서는 모든 인터페이스가 인위적으로 구성된 것이다. 위험은 우리가 해를 끼칠 수 있는 기능을 추가함으로써 생겨난다. 그래서 우리는 부상을 일으킬 가능성을 줄이는 방식으로, 혹은 관리적 통제를 제약 조건으로 강제하는 방식으로 인터페이스를 다르게 구성하기가 더 쉽다.

예전에 일하던 직장에서 이런 일이 있었다. 우리의 사용 패턴상 오프피크 시간에 새 버전을 배포하는 것이 가장 안전했다. “오프피크에만 배포하라”는 관리적 통제다. 그래서 우리는 배포 스크립트에 실행 시간을 확인하고 피크 시간대에는 에러를 던지는 코드 한 줄을 추가했다. 이는 관리적 통제를 공학적 통제로 바꾼 것이다.3

또한 현실 세계의 공학적 통제는 비용이 많이 들기 때문에 관리적 통제와 PPE를 선택하는 큰 이유가 된다. 하지만 소프트웨어는 물리적 시스템보다 훨씬 빠르고 저렴하게 수정할 수 있어 공학적 통제를 더 효과적으로 만든다.

(대체에 대해서도 비슷한 얘기를 할 수 있다고 생각한다.)

통제 수단 자체가 위험의 원인이 될 수 있다

위에서 링크한 OSHA 워크시트를 살펴봤다면, 여기에 흥미로운 항목이 있다는 것을 알 수 있을 것이다.

(출처)

어떤 통제 방법이라도 잠재적으로 작업장에 새로운 위험을 초래할 수 있다. 관리적 알람이 너무 많으면 알람 피로를 유발해 사람들이 중요한 경고를 놓치게 된다. 창고 출입을 금지하면 다른 위험을 통과해 우회하도록 만들 수도 있다. 반사 조끼는 기계에 끼일 수도 있다. 전체 시스템의 안전은 전체적으로 고려되어야 한다. 국소적인 안전 개선이 다른 곳에서 문제를 일으킬 수 있기 때문이다.

우리는 소프트웨어에서도 같은 현상을 본다. Lorin Hochstein은 강연에서 시스템을 보호하기 위해 만든 소프트웨어 때문에 수많은 Netflix 장애가 발생했다고 이야기한 바 있다.

내가 만든 통제 수단은 어떻게 새로운 위험을 더할 수 있을까?

  • 추가 전용(append-only) 데이터베이스로 대체하려면 상당한 재교육과 소프트웨어 변경이 필요해 실수와 새로운 버그가 생길 여지를 더 만들 수 있다
  • 엄격한 접근 정책은 진행 중인 문제를 해결하려는 시도를 늦춰 문제를 더 심각하게 만들 수 있다
  • 관리적 통제가 너무 많으면 사람들이 가능한 위험에 대해 경계하는 대신 자동 조종 상태로 “형식적으로만” 따르게 만들 수 있다.

나는 통제로 인해 발생할 수 있는 새로운 위험을 식별하는 것이 기존 위험을 식별하는 것보다 더 어렵다고 느낀다.


이것이 HoC의 핵심이다. 좋은 아이디어라고 생각한다!

이 글이 마음에 들었다면 내 뉴스레터에 가입해 보길 바란다! 매주 새로운 에세이를 올린다.

나는 기업을 대상으로 형식 기법(formal methods) 교육을 하며, 소프트웨어 개발을 더 빠르고, 저렴하고, 안전하게 만든다. 더 자세한 내용은 여기에서 확인할 수 있다.


  1. 몇몇 곳에서는 이를 위험 통제 계층(hierarchy of hazard controls, HoHC)이라고 부르지만, NIOSH나 OSHA 같은 곳에서는 HoC라고 부른다. “인터프리터 언어”라고 하지 않고 “인터프리터 프로그래밍 언어”라고 하지 않는 것과 비슷하다고 생각한다. 전문가를 대상으로 글을 쓸 때는 주제가 암묵적으로 드러나지만, 일반 대중을 대상으로 할 때는 명확히 할 필요가 있다. [돌아가기]
  2. 나는 이 통제에 대한 공통된 명칭을 찾지 못했다. Discord에서 물어봤더니 모두가 서로 다른 이름으로 부르고 있었다. [돌아가기]
  3. 네, --force 플래그가 있었다. [돌아가기]

이 글은 muse-spark-1.2-contributor 모델을 사용해 번역했습니다.

댓글