행 수준 보안
Row Level Security
방법
누가 어떤 행을 볼 수 있는지를 데이터베이스에 규칙으로 걸어 두어 질의마다 자동으로 걸러지게 하는 방법입니다.
등장 배경
여러 고객사가 한 시스템을 나눠 쓰는 구조에서는 한 표 안에 여러 회사의 데이터가 나란히 들어앉습니다. 문서 표에 A 회사의 문서와 B 회사의 문서가 행으로 섞여 있고, 구분은 각 행에 적힌 팀 번호뿐입니다.
이 구조에서 데이터를 꺼낼 때마다 팀 번호 조건을 붙여야 합니다. 조건 하나만 빠져도 다른 회사의 데이터가 화면에 나옵니다. 페이지덱에서 실제로 그런 일이 있었습니다. 문서 목록을 최근 수정 순으로 보여 주는 화면을 새로 만들면서 태오가 정렬 조건을 고치다 팀 번호 조건을 지웠습니다. 테스트 계정에서는 데이터가 한 팀뿐이어서 아무 이상이 없었고, 운영에 올린 지 40분 만에 다른 팀 문서 제목이 목록에 섞여 나온다는 문의가 들어왔습니다.
문제는 같은 조건을 코드 수십 곳에 손으로 붙여야 한다는 구조 자체에 있습니다. 한 곳만 빠지면 뚫리는 규칙은 언젠가 반드시 뚫립니다. 그래서 규칙을 코드에서 데이터베이스로 내려 한 번만 적어 두는 방법이 쓰입니다.
사용 절차
표마다 정책을 적어 두고, 질의를 보내는 사람이 누구인지를 데이터베이스가 알 수 있게 전달합니다. 그다음부터는 모든 질의에 그 조건이 자동으로 붙습니다.
- 걸 대상을 고릅니다. 고객사나 사용자를 가리키는 열이 있는 표 전부가 대상입니다. 공통 설정처럼 모두가 봐도 되는 표는 제외합니다.
- 기능을 켜고 기본을 막힘으로 둡니다. 이 기능은 켜는 순간 정책이 없는 표를 아무에게도 보이지 않게 만듭니다. 허용을 하나씩 적어 나가는 방향이라, 정책을 빼먹으면 데이터가 새는 대신 화면이 비게 됩니다.
- 요청하는 사람을 전달합니다. 데이터베이스 연결마다 지금 이 질의가 누구의 것인지를 값으로 설정해 둡니다. 로그인한 사용자의 팀 번호를 연결 단위 값으로 심어 두는 식입니다.
- 정책을 씁니다. 조회와 삽입과 수정과 삭제를 각각 적습니다. 조회는 행의 팀 번호가 현재 값과 같을 때만 보이게 하고, 삽입은 다른 팀 번호로 행을 만들 수 없게 막습니다. 조회만 걸고 삽입을 빼면 남의 팀 이름으로 데이터를 넣을 수 있습니다.
- 정책을 지나갈 수 있는 길을 따로 둡니다. 집계 작업이나 고객 지원 도구는 여러 팀을 함께 봐야 하므로 정책을 우회하는 역할로 돕니다. 그 역할을 쓰는 코드는 한곳에 모아 두고 누가 언제 썼는지 기록을 남깁니다.
- 시험으로 확인합니다. 두 팀의 데이터를 넣어 두고 한 팀으로 조회해 다른 팀 행이 0건인지 봅니다. 표마다 이 시험을 두 줄씩 만들어 두면 정책을 빼먹은 표가 배포 전에 드러납니다.
- 조건이 붙는 열에 인덱스를 둡니다. 정책은 모든 질의에 조건 하나를 더하는 일이라, 팀 번호 열에 인덱스가 없으면 표 전체를 훑는 질의가 늘어납니다.
적합한 상황과 한계
여러 고객사가 한 시스템을 나눠 쓰는 멀티테넌시 구조에 가장 잘 맞습니다. 데이터를 꺼내는 경로가 여럿일 때 값이 더 커집니다. 웹과 앱과 집계 작업과 관리 도구가 같은 표를 읽는다면 조건을 네 곳에 붙이는 대신 한 곳에 적어 둘 수 있습니다. 화면 없이 데이터베이스에 바로 붙는 도구를 쓰는 경우에는 사실상 이 방법뿐입니다. 그런 도구에는 조건을 붙일 코드가 없기 때문입니다.
한계는 넷입니다.
- 정책이 늘면 읽기 어려워집니다. 표 스무 개에 동작별 정책 넷씩이면 규칙이 여든 개이고, 데이터가 안 보일 때 어느 정책 때문인지 찾는 일이 만만치 않습니다. 정책 이름에 표와 동작과 조건을 그대로 적어 두는 것이 이 자리에서 도움이 됩니다.
- 연결을 돌려 쓰는 구조에서 값이 남을 수 있습니다. 연결을 빌려 쓰고 반납하는 방식에서 사용자 값을 지우지 않고 반납하면, 다음 요청이 앞사람의 값으로 질의를 돌립니다. 반납 전에 값을 비우는 처리를 반드시 함께 만듭니다.
- 기능 권한까지 대신하지는 않습니다. 이 방법은 어느 행을 볼 수 있는지만 정합니다. 같은 팀 안에서 누가 문서를 삭제할 수 있는지는 역할에 권한을 묶는 역할 기반 접근 제어가 맡습니다.
- 모든 데이터베이스가 지원하지 않습니다. 관계형 데이터베이스 쪽에는 대체로 있지만 제품마다 쓸 수 있는 조건의 범위가 다르고, 표 모양을 미리 정하지 않는 계열에는 같은 기능이 없는 경우가 많습니다.
실무 용례
"팀 번호 조건 하나 빠져서 다른 팀 문서가 나왔습니다. 이건 데이터베이스에서 막죠."
같은 사고가 코드에서 반복되니 규칙을 아래로 내리자는 제안입니다.
"조회 정책만 있고 삽입 정책이 없어요."
읽기는 막았는데 쓰기가 열려 있다는 지적입니다.
"집계 작업은 우회 역할로 도니까 그쪽 코드는 따로 봐야 합니다."
정책이 적용되지 않는 경로가 있다는 확인입니다.
태오가 사고 뒤에 문서와 댓글과 첨부 표 셋에 정책을 걸었습니다. 표마다 동작 넷을 적으니 정책이 열두 개가 됐고, 두 팀 데이터로 돌리는 시험을 표마다 두 줄씩 붙였습니다. 그 뒤로 새 화면을 만들 때 조건을 빼먹는 일이 사고로 이어지지 않았습니다. 대신 배포 직후 화면 하나가 비어 보이는 일이 두 번 있었는데, 정책을 안 적은 표를 새로 만든 경우였습니다. 새는 방향이 아니라 막히는 방향으로 틀린다는 점이 이 방법을 고른 이유이기도 합니다.
흔한 오해
이것을 켜면 애플리케이션에서 권한을 확인할 필요가 없다고 읽는 경우가 가장 많습니다. 데이터베이스가 걸러 주니 위에서 또 볼 이유가 없어 보입니다. 하지만 걸러 주는 것은 어느 행을 볼 수 있는지까지입니다. 같은 팀 문서를 볼 수 있는 사람과 그 문서를 지울 수 있는 사람은 다르고, 그 구분은 위에서 해야 합니다. 정책을 우회하는 역할로 도는 코드가 있다는 점도 남습니다. 집계와 지원 도구는 그 역할을 쓰므로 그 경로의 권한 확인은 여전히 애플리케이션의 몫입니다.
성능 때문에 쓸 수 없다고 보는 것도 흔합니다. 모든 질의에 조건이 붙는다는 말을 들으면 느려질 것 같습니다. 실제로 붙는 것은 조건 하나이고, 그 열에 인덱스가 있으면 손으로 조건을 붙였을 때와 차이가 크지 않습니다. 문제가 되는 것은 조건 자체가 아니라 정책 안에서 다른 표를 뒤져야 하는 경우입니다. 권한 표를 세 단계 거쳐 판정하는 정책을 쓰면 그 질의가 모든 조회에 따라붙습니다. 이때는 판정에 필요한 값을 미리 행에 적어 두어 정책이 한 열만 보고 끝나게 만듭니다.
같이 보면 좋은 페이지
- 데이터베이스
이 기능이 어느 갈래의 일인지 먼저 봅니다
- 관계형 데이터베이스
이 기능을 실제로 제공하는 쪽입니다
- 멀티테넌시
이 방법이 필요해지는 구조입니다
연관 용어
마지막 업데이트 2026-09-16