데이터베이스를 사용하는 프로그램을 개발하고 배포하다보면 내 데이터베이스에 다른학교 학생들의 정보가 들어오는 구조가 생깁니다. 개인정보 수집하는 걸 많이 꺼려하는 저의 입장에서는 최대한 타학교의 정보는 수집하지 않는 구조로 개발하려고 했는데요, 수프림과 차곡 두 앱 모두 그 고민의 결과로 구조를 잡았습니다. 수프림은 고등학생용 수학 공부 앱이고, 차곡은 담임 선생님이 반 아이들 자기주도학습을 관리하는 앱입니다. 둘 다 여러 학교, 여러 반이 같은 서비스를 쓰지만, 만들 때부터 정한 원칙이 하나 있었습니다. 다른 학교, 다른 반 아이들의 이름과 성적을 우리 서버 한군데에 다 모아두지 않는다. "권한 체크 잘 해두면 되지 않아?"라고 생각할 수도 있는데, 저는 매우 쫄보라 갖고있는 것도 싫었습니다.. 권한 체크는 결국 코드에 실수가 없을 때만 지켜지는 약속이거든요. 저는 실수가 있어도 애초에 볼 수 있는 방법 자체가 없는 구조를 원했습니다. 미성년자 정보를 다루는 거라 더 그랬고요. 그래서 두 앱 모두 "데이터는 우리 서버가 아니라, 그 데이터의 진짜 주인 손 안에 둔다"는 같은 결론에 도착했습니다. 다만 방법은 서로 달랐습니다. 🍀방법 1 — 학교가 직접 자기 데이터베이스를 만든다 (수프림) 수프림에서는 학교마다 완전히 다른 Supabase 프로젝트를 씁니다. 즉 A고등학교 학생 정보와 B고등학교 학생 정보는 처음부터 물리적으로 다른 데이터베이스에 들어있습니다. 저희 서버는 그중 어디로 가야 하는지 아는 "주소록"만 하나 갖고 있고요.

여기서 중요한 건 데이터베이스를 저희가 만들어주지 않는다는 점입니다. 저희가 만들어주면 결국 그 데이터베이스의 진짜 주인도 저희가 되어버리거든요. 그래서 순서를 이렇게 뒤집었습니다. 1️⃣ 코드만 하나 발급 학교 이름을 받고 "아직 주소는 모르는" 코드를 하나 만듭니다. 이 시점엔 그 코드가 어디로 연결될지 아무도 모릅니다. 2️⃣ 코드를 학교에 전달 이메일이나 오픈채팅으로 코드를 알려줍니다. 코드 자체는 비밀번호가 아니라 "어느 학교인지" 구분하는 이름표라 로그인 화면에 그대로 써도 괜찮습니다. 3️⃣⭐ 학교가 자기 데이터베이스를 직접 만듦 ⭐ (제일 복잡해서 가이드를 설정했습니다.) 학교 담당자가 Supabase에서 새 프로젝트를 만들고(가이드를 따라하면 쉽게 할 수 있게 했습니다.), 제대로 실행되기 위한 setup.sql(데이터베이스 테이블과 관계 설정)를 한 번 실행합니다. 표(테이블)와 잠금 규칙이 이 한 번으로 다 세팅됩니다.

설정가이드로 supabase 가입 및 생성 절차를 따라할 수 있게 했습니다.

4️⃣ 주소만 등록 학교가 등록 화면에서 코드와 자기 데이터베이스의 "공개해도 되는 열쇠(anon key)"만 입력합니다. anon key가 아닌 service role key를 받으면 결국 개발자가 접근이 가능해지기 때문에 anon key만을 받습니다. 개발자가 학교 데이터베이스에 접근할 수 있는 부분을 원천 차단했습니다. 5️⃣ 진짜인지 확인하고 연결 입력한 주소가 진짜로 정상 설정된 데이터베이스가 맞는지 한 번 확인한 다음에야 그 학교를 "연결 완료" 상태로 바꿉니다. Dev DB에 실제로 뭐가 들어있는지 정리하면 이렇습니다. AI와 미리 구현하고자 하는 내용들과 권한구조를 정리하면서 어느 데이터베이스에 무슨 데이터가 들어갈 지 미리 정하는게 좋습니다.
- schools — 학교 코드, 프로젝트 주소, 공개 키(anon key), 연결 상태
- problems — 문제은행 (지문, 보기, 정답, 난이도, 단원 등 문제 콘텐츠)
- mathmons — 학생들이 코인으로 뽑는 "수학자 도감" 카탈로그입니다. 캐릭터 이름, 단계, 희귀도, 이미지·움짤 URL이 들어있는데, 이건 "이런 카드가 존재한다"는 공용 정보일 뿐입니다. "누가 몇 장 가졌는지"는 학생이 속한 학교 데이터베이스의
student_mathmons에 따로 저장됩니다. - textbook_subjects / publishers / chapters / units— 교과서별 단원 목록 같은 커리큘럼 메타데이터
- problem_reports — 학생이 신고한 문제 오류입니다. 문제 ID, 신고 사유, 어느 학교인지까지는 남지만, 신고한 학생이 누구인지는 아예 남기지 않습니다.
전부 앱을 운영하는 데 필요한 공용 콘텐츠뿐이고, 학생 한 명 한 명을 특정할 수 있는 값은 하나도 없습니다. 마스터키(service role 키)는 한 번도 물어보지 않습니다. 로그인 화면에도, 등록 화면에도 그런 값을 넣는 칸 자체가 없습니다. "공개해도 되는 열쇠(anon key)"만으로 로그인과 조회가 다 되도록 만들어놨거든요. 그런데 딱 하나, 마스터키가 꼭 필요한 순간이 있습니다. "학생 계정을 한꺼번에 만들기"나 "비밀번호 초기화" 같은 기능은 구글이나 Supabase 같은 회사들이 원래 그렇게 설계해놔서, 마스터키 없이는 아예 할 수 없는 작업입니다. 그래서 학교 관리자 화면에는 실제로 마스터키를 붙여넣는 칸이 하나 있습니다. "어? 그럼 결국 받는 거 아니야?"라고 생각할 수 있는데, 사실 이 부분 설계하면서 제일 오래 고민했습니다.

화면에서 "설정 완료"를 누르면, 그 요청은 저희 서버를 거치지 않고 브라우저에서 그 학교 자신의 데이터베이스로 곧바로 전달됩니다. 데이터베이스 안에는 그 데이터베이스만 열어볼 수 있는 별도의 금고(Vault)가 있는데, 마스터키는 딱 거기에만 저장됩니다. 이후에 "학생 계정 만들기" 버튼을 누르면, 그 데이터베이스 안에서 실행되는 작은 프로그램이 금고에서 마스터키를 스스로 꺼내 자기 자신에게 "계정 하나 만들어줘"라고 요청합니다. 이 모든 과정에서 저희 서버는 단 한 번도 그 경로에 끼지 않습니다. 애초에 지나가지 않는 요청이니, 로그로 남기려 해도 남길 방법이 없습니다. 결국 "마스터키를 넣는 칸이 화면에 있다"는 사실과 "그 키를 우리가 수집한다"는 건 서로 다른 이야기입니다. 열쇠를 만드는 사람이 열쇠를 자기 집 금고에 넣는 걸 옆에서 지켜보지도 않는 것과 비슷합니다. 로그인은 좀 더 단순합니다. 학생이 학교 코드 + 아이디 + 비밀번호를 입력하면, 저희 서버는 그 코드로 "어느 데이터베이스로 가야 하는지" 주소만 찾아보고, 비밀번호가 맞는지는 그 학교의 데이터베이스 쪽에 그대로 넘겨서 확인하게 합니다. 저희 서버는 비밀번호를 직접 판단하지 않습니다. 그래서 만약 제 코드에 실수가 있어서 A고 계정으로 B고 정보를 요청하는 일이 생겨도, B고 데이터베이스 주소 자체를 A고 로그인 세션이 갖고 있지 않아서 애초에 갈 곳이 없습니다. 코드가 완벽해서 안전한 게 아니라, 코드가 실수해도 안전한 구조입니다. 🍀방법 2 — 이미 갖고 있던 계정을 그대로 쓴다 (차곡) 차곡은 상황이 좀 다릅니다. 사용하는 분들은 각 담임 선생님인데, 이분들한테 "Supabase 프로젝트를 만들고 SQL을 실행하세요"라고 하면 그건 너무 큰 벽입니다. 개발자가 아니니까요. 선생님이 이미 갖고 있는 구글 계정, 그 안의 구글 드라이브를 그대로 데이터베이스로 씁니다. 선생님은 딱히 뭘 새로 만들거나 가입할 필요 없이, 평소 쓰던 구글 계정으로 로그인만 하면 됩니다. 그러면 앱이 그 선생님의 구글 드라이브 안에 스프레드시트 파일 하나를 만들어드리고, 그 반 학생들의 데이터는 전부 그 파일 안에 쌓입니다. * 그렇다면 수프림도 구글 스프레드시트를 활용하면 되지 않을까? 하는 생각이 들지만, 수프림은 차곡에 비해 데이터간의 관계 구조가 복잡한 시스템이라 스프레드시트만으로 구현하긴 어렵습니다. 반면, 차곡은 단순히 학생아이디, 학생의 목표 기록, 피드백 정도의 간단한 기능이기에 데이터 간의 관계가 복잡하지 않습니다.

여기서 핵심은 구글이 제공하는 drive.file이라는 권한 범위입니다. 말 그대로 "이 앱이 직접 만든 파일만" 볼 수 있는 권한이라, 선생님의 다른 사진이나 문서, 이메일에는 앱이 손댈 방법 자체가 없습니다. 그리고 반이 30개면 스프레드시트도 30개, 완전히 독립된 파일이라 서로 섞일 일도 없습니다.
학생들한테까지 구글 로그인을 시키긴 어렵습니다. 학교 계정 정책 때문에 막혀있는 경우도 많고, 개발자가 관리할 계정에 학생도 포함되거든요. 그래서 학생은 담임 선생님이 발급해준 학급코드/아이디/비밀번호로 로그인합니다.
그래서 "이 아이디가 어느 반인지" 찾아보는 중앙 명단 같은 게 아예 필요 없습니다. 아이디를 보자마자 어느 선생님의 시트로 가야 하는지 바로 알 수 있습니다. 비밀번호도 마찬가지로 그 선생님의 시트 안에만 (그대로가 아니라 알아볼 수 없게 암호화해서) 저장됩니다. 중앙에 학생 비밀번호를 모아두는 곳 자체가 없습니다.
🍀 그래서 뭐가 다른가요
두 방법 다 "데이터는 진짜 주인 손에 둔다"는 목적은 같은데, 상대가 누구냐에 따라 방법이 완전히 달라졌습니다.
수프림 — 새 데이터베이스를 만든다
상대는 학교입니다. IT 담당자가 있거나, 적어도 설정 몇 단계는 따라올 여력이 있습니다.
- 잠금 규칙을 아주 세밀하게 걸 수 있습니다
- 설정이 몇 단계 필요합니다
- 규모가 크고 오래 쓸 조직에 맞습니다
차곡 — 있던 계정을 빌린다 상대는 담임 선생님 한 명입니다. 새로 뭘 배우거나 설정할 여유가 없습니다.
- 로그인 한 번으로 바로 시작합니다
- 구글시트라는 도구의 한계는 감수해야 합니다
- 개인, 소규모 단위에 잘 맞습니다
즉 "얼마나 튼튼하게 잠그느냐"와 "얼마나 쉽게 시작하느냐"는 좀 반비례하는 관계입니다. 상대방이 새 시스템을 배울 여유가 있는지 없는지를 보고 방법을 고르면 될 것 같습니다. 🍀 단점도 있습니다.
- 전체 통계를 내기 어렵습니다. "전체 학교 평균 몇 점" 같은 걸 내려면 데이터베이스를 하나씩 다 들여다봐야 합니다. 애초에 그런 통계를 안 내는 쪽으로 방향을 잡았습니다.
- 문제가 생기면 그 데이터베이스만 문제입니다. 좋게 말하면 피해가 안 번지는 거고, 나쁘게 말하면 저희가 원격으로 대신 고쳐줄 방법이 별로 없습니다.
- 시작이 한 번에 안 끝납니다. "가입하자마자 바로 시작"이 아니라 몇 단계를 거치니까, 자동화된 가입보다는 느립니다.
- (차곡의 경우) 구글시트가 도구로서 가진 한계도 그대로 따라옵니다. 여러 사용자가 몰리면 느려질 수 있고, 세밀한 권한 제어는 어렵고, 로그인한 사람 한 명만 콕 집어 강제 로그아웃시키는 것도 안 됩니다.
그래도 이 정도는 감수할 만하다고 생각했습니다. 미성년자 정보를 다루는 서비스라면, "우리는 여러분 정보를 안 가져갑니다"라는 말이 약관에 적힌 문장이 아니라, 눈으로 확인 가능한 구조 자체이길 바랐거든요.