![[실전 서버리스 07] 엣지의 SQLite, D1, 스키마 설계와 바인딩](https://img.thenullpage.com/posts/5609/5609_1_5a79ff.webp)
사이트에는 데이터가 필요하다. 글, 댓글, 사용자, 추천 같은 것들을 어딘가 저장하고 꺼내 써야 한다. 서버리스에서 이 역할을 하는 게 데이터베이스인데, 내가 쓴 건 D1이다. 엣지에서 도는 SQLite 기반 데이터베이스다. 이번 편은 D1이 무엇이고 어떻게 스키마를 설계하고 워커에 연결하는지를 다룬다.
데이터베이스는 사이트의 심장이다. 여기서 다룰 D1은 뒤에 이어질 여러 편, 특히 비용과 성능 최적화의 무대이기도 하니 기초를 확실히 잡고 가자.
1. D1은 SQLite다
![[실전 서버리스 07] 엣지의 SQLite, D1, 스키마 설계와 바인딩 (2)](https://img.thenullpage.com/posts/5609/5609_2_5273d3.webp)
D1은 SQLite를 기반으로 한다. SQLite는 오랫동안 널리 쓰인 가볍고 안정적인 관계형 데이터베이스다. 스마트폰 앱부터 브라우저까지 곳곳에 들어 있는, 검증된 기술이다. D1은 이 SQLite를 클라우드플레어 엣지 환경에서 쓸 수 있게 만든 것이다.
SQLite 기반이라는 건 익숙한 SQL을 그대로 쓸 수 있다는 뜻이다. 테이블을 만들고, 질의를 날리고, 조인을 하는 표준 SQL 문법이 통한다. 새로운 질의 언어를 배울 필요 없이, 기존 SQL 지식을 그대로 쓴다. 관계형 데이터베이스를 다뤄본 사람이라면 진입 장벽이 낮다.
동시에 D1은 서버리스의 특성을 따른다. 서버를 프로비저닝하거나 관리하지 않고, 쓴 만큼만 과금된다. 쿼리를 안 날리면 과금이 없고, 용량에 대한 고정비도 없다. 전통적인 데이터베이스 서버를 운영하던 것에 비하면 관리 부담이 확 준다. SQLite의 단순함과 서버리스의 편함을 합친 것이다.
D1이 SQLite 기반이라는 게 주는 안심이 있다. SQLite는 수십 년간 검증된 기술이라, 새로 나온 불안정한 데이터베이스가 아니다. 그 안정성 위에 서버리스의 편함을 얹은 것이라, 믿고 쓸 수 있다. 검증된 기술을 새로운 환경에서 쓴다는 조합이 D1의 강점이다.
조금 더 배경을 주면, D1을 고른 데는 통합 생태계라는 이유도 컸다. 데이터베이스를 별도 서비스로 두면 워커와의 연결, 인증, 지연을 다 신경 써야 하는데, 같은 생태계 안의 D1은 바인딩 하나로 매끄럽게 붙는다. 코드 실행과 데이터가 한 플랫폼에 있으니 그 사이가 빠르고 단순하다.
한 가지 더, D1은 엣지에서 돈다는 게 지연 시간에도 영향을 준다. 데이터베이스가 코드와 같은 플랫폼, 가까운 위치에 있으니 쿼리 왕복이 빠르다. 데이터베이스가 멀리 있으면 매 쿼리마다 그 거리를 왕복해야 하는데, D1은 그 거리가 짧다. 앞서 다룬 엣지의 거리 이점이 데이터베이스에도 적용되는 것이다.
2. 스키마 설계, 테이블 구조
데이터베이스를 쓰려면 먼저 스키마를 설계한다. 어떤 데이터를 어떤 테이블에 어떤 컬럼으로 담을지 정하는 것이다. 커뮤니티라면 글 테이블, 댓글 테이블, 사용자 테이블 같은 게 기본이다. 각 테이블에 필요한 컬럼을 정의한다.
스키마 설계에서 중요한 건 데이터의 관계를 잘 잡는 것이다. 댓글은 어느 글에 속하는지, 글은 누가 썼는지 같은 관계를 외래 키로 연결한다. 관계형 데이터베이스의 강점이 이 관계 표현이라, 이걸 잘 설계하면 데이터를 효율적으로 다룰 수 있다.
또 하나 중요한 게 처음에 잘 설계하는 것이다. 데이터가 쌓인 뒤 스키마를 크게 바꾸는 건 부담이 크다. 컬럼을 추가하는 정도는 괜찮지만, 구조를 갈아엎는 건 데이터 이전이 필요해 위험하다. 그래서 초기에 충분히 고민해 설계하되, 나중에 확장할 여지도 남겨두는 균형이 필요하다.
스키마 설계에서 관계를 잘 잡는 게 왜 중요한지는 데이터가 쌓이면 드러난다. 관계가 어설프면 데이터가 중복되거나 일관성이 깨지고, 조회가 복잡해진다. 반대로 관계를 깔끔하게 설계하면 데이터가 정돈되고 쿼리가 단순해진다. 초기 설계의 품질이 이후 개발 내내 영향을 미친다.
3. 워커에 연결하기
D1을 만들었으면 워커에 연결한다. 앞서 다룬 바인딩을 통해서다. 설정 파일에 데이터베이스 바인딩을 적으면, 워커 코드 안에서 그 이름으로 데이터베이스를 부를 수 있다. 코드는 이 바인딩을 통해 쿼리를 날리고 결과를 받는다.
쿼리를 날릴 때는 값을 직접 문자열에 넣지 말고 파라미터로 바인딩하는 게 중요하다. 사용자 입력을 그대로 쿼리 문자열에 붙이면 SQL 인젝션이라는 심각한 보안 취약점이 생긴다. 대신 값을 파라미터로 따로 넘기면 데이터베이스가 안전하게 처리한다. 이건 보안의 기본이라 반드시 지켜야 한다.
쿼리 결과는 코드에서 다루기 좋은 형태로 돌아온다. 여러 행을 조회하면 배열로, 한 행이면 객체로 받는다. 이걸 가공해 API 응답으로 만들거나 페이지에 렌더링한다. 데이터베이스에서 꺼낸 데이터가 사용자 화면까지 가는 흐름이 여기서 이어진다.
쿼리에 값을 파라미터로 바인딩하라는 게 단순한 권장이 아니라 필수다. 사용자 입력을 쿼리 문자열에 그대로 붙이면 SQL 인젝션이라는 치명적 취약점이 열린다. 공격자가 입력을 통해 데이터베이스를 조작할 수 있게 되는 것이다. 파라미터 바인딩은 이걸 원천 차단한다. 예외 없이 지켜야 하는 보안 규칙이다.
쿼리 파라미터 바인딩을 다시 강조하는 건 그만큼 중요해서다. 이건 편의가 아니라 보안의 최전선이다. 사용자 입력이 쿼리에 직접 섞이는 순간 사이트 전체가 위험해진다. 그래서 어떤 쿼리든 값은 파라미터로 넘기는 걸 예외 없는 습관으로 만들어야 한다. 한 번의 예외가 큰 사고를 부른다.
4. 마이그레이션 관리
스키마는 시간이 지나며 바뀐다. 컬럼을 추가하거나 인덱스를 만들거나 테이블을 새로 만든다. 이런 변경을 마이그레이션이라고 하는데, 이걸 체계적으로 관리해야 한다. 그때그때 수동으로 바꾸면 개발 환경과 실제 환경의 스키마가 어긋나기 쉽다.
마이그레이션을 파일로 관리하면 변경 이력이 남는다. 어떤 순서로 스키마가 바뀌어왔는지가 기록되고, 새 환경에서도 그 파일들을 순서대로 적용하면 같은 스키마가 만들어진다. 이 체계가 있으면 스키마 변경이 안전하고 추적 가능해진다.
대량 데이터를 다루는 마이그레이션은 특히 조심해야 한다. 뒤에서 자세히 다루겠지만, D1은 한 번에 너무 많은 행을 바꾸려 하면 실행 한도에 걸린다. 그래서 대량 변경은 작은 배치로 쪼개 적용한다. 이건 D1의 단일 스레드 특성과 관련이 있어 뒤 편에서 깊게 짚는다.
마이그레이션을 파일로 관리하면 협업과 재현에 강하다. 스키마 변경 이력이 파일로 남으니, 새 환경에서도 같은 스키마를 재현할 수 있고, 언제 무엇이 바뀌었는지 추적된다. 수동으로 그때그때 바꾸는 방식은 환경 간 불일치를 낳기 쉬운데, 파일 기반 마이그레이션이 이걸 막는다.
5. 로컬과 원격 데이터베이스
D1도 로컬 개발용과 실제 배포용이 나뉜다. 로컬에서 개발할 때는 로컬 데이터베이스에 대고 테스트하고, 배포 환경은 원격 데이터베이스를 쓴다. 이 둘을 헷갈리면 로컬에서 잘 되던 게 배포하면 데이터가 없어 안 되는 혼란이 생긴다.
그래서 명령을 실행할 때 로컬인지 원격인지를 명확히 구분하는 습관이 필요하다. 스키마를 적용하거나 데이터를 확인할 때, 지금 대상이 로컬인지 원격인지 늘 의식해야 한다. 나도 초기에 이걸 헷갈려서, 원격에 반영한 줄 알았는데 로컬만 바뀌어 있던 적이 있다.
정리하면 D1은 익숙한 SQL을 쓰는 SQLite 기반 서버리스 데이터베이스이고, 스키마를 설계해 바인딩으로 워커에 연결하며, 마이그레이션으로 변경을 관리한다. 다음 편에서는 D1을 쓸 때 가장 중요하면서도 초보가 놓치기 쉬운 것, rows read가 곧 비용이라는 이야기를 실전 경험으로 다룬다.
로컬과 원격 데이터베이스를 구분하는 습관은 사고 예방에 필수다. 원격에 반영한 줄 알았는데 로컬만 바뀌어 있거나, 반대로 실수로 원격 데이터를 건드리는 일이 생긴다. 그래서 명령을 실행할 때마다 지금 대상이 로컬인지 원격인지 의식하는 게 중요하다. 이 작은 주의가 데이터 사고를 막는다.
로컬과 원격 구분을 습관화하면, 데이터베이스를 다루는 손이 신중해진다. 특히 원격, 즉 실제 서비스 데이터를 건드릴 때는 한 번 더 확인하게 된다. 실수로 실제 데이터를 지우거나 잘못 바꾸는 건 되돌리기 어려운 사고다. 그래서 대상을 늘 의식하는 이 습관이, 데이터를 다루는 사람의 기본기가 된다.
덧붙이면, D1 스키마를 설계할 때 나는 앞으로의 확장을 늘 염두에 뒀다. 지금 필요한 것만이 아니라, 나중에 붙을 기능도 담을 수 있게 여지를 남기는 것이다. 물론 오지 않을 미래를 위해 과하게 복잡하게 만들 필요는 없다. 지금을 단순하게 풀되 확장의 문은 열어두는 균형이, 오래 가는 스키마의 조건이다.