SQL #3 스키마 분리와 AI 분석 #
#2025-07-29
생각 정리
- AI 분석이 들어갈 때 왜 별도 스키마로 나누는 것이 유리할까요?
- 스키마 vs. 테이블 분리, 어떤 방식이 어떤 상황에 적합할까요?
- 향후 pgvector 또는 AI 모델 결과를 넣기 위해 어떻게 테이블을 확장할 수 있을까요?
#
- AI 분석이 들어갈 때 왜 별도 스키마로 나누는 것이 유리할까요?
AI 분석이 포함된 시스템에서 데이터를 다룰 때, 별도 스키마로 나누는 것이 유리한 이유는 (1) 데이터의 사용 목적이 다르기 때문이고, (2) 데이터의 구조와 속성이 근본적으로 다르기 때문입니다.
먼저 데이터의 사용 목적이 다르면 별도의 스키마로 운영하는것이 유리합니다. 예를 들어, 우리가 학교에서 수업을 들을 때 쓰는 노트와, 친구와 놀러 갈 계획을 적는 다이어리는 서로 내용도 다르고, 사용 목적도 다르기 때문에 같은 공책에 막 섞어 쓰면 나중에 헷갈리고 찾기 어렵습니다. 데이터베이스도 마찬가지인데, 예를 들어 수강생(Student), 강사(Instructor), 수강신청(Enrollment) 같은 테이블은 대부분 운영 데이터를 담고 있고 실제 서비스가 돌아가기 위해 실시간으로 쓰이고 조회됩니다. 그런데 AI 분석에 사용되는 데이터, 예를 들면 수강 리뷰의 임베딩 값(Embedding)이나 학생 행동 로그에서 추출한 패턴 정보는 운영 목적이 아니라 분석 목적입니다. AI 분석 데이터는 실시간보다는 주기적으로 갱신되고, 대량의 수학적 계산을 거쳐 만들어지며, 사용자가 직접 보는 게 아니라 모델이 참고합니다. 그러니까 이 두 데이터를 같은 공간에 두는 건, 수업 노트 옆에 여행계획을 적는 것과 같이 찾기도 어렵고, 실수도 생기고, 결국 혼란을 유발할 수 있습니다.
두 번째로 데이터의 구조와 속성이 다른 경우 별도의 스키마로 운영하는것이 유리합니다. 운영 데이터는 일반적으로 사람이 입력한 명확한 값으로 구성됩니다. 예를 들어 이름, 전화번호, 수강과목 등은 짧고, 일정한 형식을 갖고 있다. 그런데 AI 분석을 위한 데이터는 길고 복잡한데, 예를 들어 학생이 쓴 리뷰를 BERT 모델로 임베딩하면 768차원의 벡터로 바뀌고, 이런 벡터는 숫자 덩어리이기 때문에 일반 SQL 쿼리로는 다루기 어렵습니다. 또 AI 분석에서는 반복 실험을 하거나 다양한 모델 결과를 저장해야 하므로, 새로운 컬럼이 자주 생기고 스키마 구조도 자주 바뀔 수 있습니다. 예를 들어 ‘학생 행동을 기반으로 예측된 이탈 위험 점수’나 ‘추천 과정 리스트’ 같은 컬럼은 운영 데이터에서는 필요 없지만, 분석에서는 매우 중요한데, 이처럼 구조적으로 유연하고 실험적인 데이터를 기존 운영 스키마에 억지로 끼워 넣으면, 전체 시스템이 복잡해지고 에러도 많아집니다. 그래서 아예 AI 분석용 스키마를 따로 만들어 거기에 AI 전용 테이블을 모아두면, 운영 시스템은 안정성을 유지하면서도 분석팀은 자유롭게 데이터를 다룰 수 있습니다. 예를 들어 서울캠퍼스 학생 데이터를 담은 테이블이 seoul.students
이고, AI 분석 결과로 얻은 학생 행동 임베딩이 analytics.student_embeddings
에 저장돼 있으면, 운영 데이터는 수업 등록이나 점수 관리에 집중하고, 분석 스키마는 AI 모델의 입력 및 출력 데이터 저장에 집중합니다. 운영 스키마는 변경이 거의 없지만, 분석 스키마는 새로운 모델이 생길 때마다 컬럼이 추가되거나 테이블이 생길 수 있는데 서로 독립적이기 때문에 안정성과 유연성을 동시에 확보할 수 있습니다.
#
- 스키마 vs. 테이블 분리, 어떤 방식이 어떤 상황에 적합할까요?
스키마와 테이블 분리는 둘 다 데이터베이스를 논리적으로 구분하고 정리하기 위한 방법인데, 접근 제어가 필요한 경우와 같은 구조이지만 맥락(도메인)이 다를 경우에 스키마 분리가 적합하고, 같은 도메인 안에서 구조나 의미가 다른 데이터를 함께 관리할 때는 테이블 분리가 적합합니다.
먼저 스키마는 일종의 ‘공간’입니다. 예를 들어, 회사에서 부서마다 각기 다른 문서를 관리한다고 가정해봤을때, 경영팀은 예산 파일, 인사팀은 사원 평가표, 개발팀은 코드 문서를 관리합니다. 이걸 한 폴더에 몰아넣으면 각 부서가 실수로 다른 부서 문서를 건드릴 수 있습니다. 대신 부서별 폴더를 따로 만들어 놓고 권한을 설정하면, 인사팀은 인사 폴더만 접근 가능하고 경영팀은 경영 폴더만 볼수있습니다. 데이터베이스에서 이 ‘부서별 폴더’가 바로 스키마입니다. 예를 들어 seoul.students
, jeju.students
처럼 캠퍼스별 학생 데이터를 스키마로 구분하면, 서울 캠퍼스 운영자는 seoul
만 접근할 수 있게 만들고, 제주 캠퍼스 운영자는 jeju
만 다루게 할 수 있습니다. 테이블 분리만으로는 특정 테이블에만 접근을 제한하기 어렵고, 관리가 복잡해질 수 있습니다.
두 번째로, 같은 구조이지만 맥락(도메인)이 다를 경우에도 스키마 분리가 좋다. 예를 들어, 대학의 학사 시스템이 있을 때 서울과 제주 두 캠퍼스가 있는데, 학생, 수강, 강의 리뷰 등의 테이블 구조는 동일하지만 운영은 독립적입니다. 이럴 때는 seoul.enrollments
와 jeju.enrollments
처럼 스키마로 구분하면 같은 종류의 데이터를 혼동 없이 관리할 수 있습니다. 반면, 만약 ‘서울학생’과 ‘제주학생’을 한 테이블 students
에 campus
열을 추가해서 구분한다면, 운영이나 통계 측면에서 실수하기 쉽고, 특정 캠퍼스의 데이터만 쿼리하려면 매번 조건문을 붙여야 합니다. 즉, 스키마 분리는 구조는 같지만 실질적으로 분리된 독립 단위를 표현하는 데 유리합니다. 테이블로만 나누면 이런 맥락 구분이 흐릿해지고, 유지보수가 어려워질 수 있습니다.
반면 테이블 분리는 같은 도메인 안에서 구조나 의미가 명확히 다른 데이터를 함께 관리할 때 적합합니다. 예를 들어, 한 학사 시스템 안에서 courses
테이블은 개설된 과목 정보를 담고, course_descriptions
는 그 과목에 대한 상세한 설명을 담는다고 하면 이 둘은 같은 “강의"라는 도메인에 속해 있지만, 정보의 종류와 목적이 다르기 때문에 하나의 테이블에 섞지 않고, 테이블을 분리해서 관리하는 것이 바람직합니다.
courses
와 course_descriptions
처럼 성격이 다른 데이터를 스키마 분리로도 독립성 확보가 가능하긴 합니다. 그러나 둘다 둘 다 “강의"라는 도메인에 속하며 도메인은 동일하되 데이터의 성격만 다른, 이러한 경우에는 기능적 분리만 필요하지, 운영 주체나 보안 경계까지 분리해야 하는 것은 아닙니다. 성능 및 관리 효율성 측면에서 테이블 분리가 더 실용적이므로 테이블 분리 수준에서 멈추는 것이 더 적합합니다.
#
- (스키마가 이와 같을 때) 향후 pgvector 또는 AI 모델 결과를 넣기 위해 어떻게 테이블을 확장할 수 있을까요?
Schemas:
- public
└── instructors
- id (integer, PK)
- name (varchar)
- email (varchar)
- created_at (datetime)
└── courses
- id (integer, PK)
- title (varchar)
- instructor_id (integer, FK → public.instructors.id)
- created_at (datetime)
└── course_descriptions
- course_id (integer, PK, FK → public.courses.id)
- description (text)
- seoul
└── students
- id (integer, PK)
- name (varchar)
- email (varchar)
- created_at (datetime)
└── enrollments
- student_id (integer, FK → seoul.students.id)
- course_id (integer, FK → public.courses.id)
- enrollment_date (varchar)
- Primary Key: (student_id, course_id)
└── reviews
- id (integer, PK)
- student_id (integer, FK → seoul.students.id)
- course_id (integer, FK → public.courses.id)
- comment (text)
- created_at (datetime)
- jeju
└── students
- id (integer, PK)
- name (varchar)
- email (varchar)
- created_at (datetime)
└── enrollments
- student_id (integer, FK → jeju.students.id)
- course_id (integer, FK → public.courses.id)
- enrollment_date (varchar)
- Primary Key: (student_id, course_id)
└── reviews
- id (integer, PK)
- student_id (integer, FK → jeju.students.id)
- course_id (integer, FK → public.courses.id)
- comment (text)
- created_at (datetime)
- analytics
└── student_embeddings
- campus (varchar)
- student_id (integer)
- embedding (vector)
- updated_at (datetime)
└── course_vectors
- course_id (integer)
- vector (vector)
- updated_at (datetime)
향후 pgvector를 도입하거나 AI 모델의 예측 결과를 넣기 위해 테이블을 확장하려면 2가지를 고려해서 확장해야 합니다.
첫째, 벡터 임베딩과 같은 AI 결과물은 원본 데이터와 별도로 관리되도록 전용 테이블을 분리해 저장해야 합니다. pgvector는 고차원 벡터 데이터를 다루기 위한 PostgreSQL 확장 기능인데, 이건 보통 텍스트나 이미지, 수강 패턴 등과 같은 복잡한 데이터를 수치화한 결과물이다. 그런데 이걸 기존 테이블, 예를 들어 courses
나 students
테이블 안에 vector(768)
같은 컬럼으로 그냥 넣어버리면, 일단 저장은 가능하지만 문제가 생기는데 하나의 테이블이 너무 많은 역할을 하게 되면 데이터의 의미가 혼재되고 AI 모델이 바뀔 때마다 갱신도 어렵고 이전 값과 새 값을 비교하기도 어려워집니다. 예를 들어 courses
테이블에 있는 title
이 바뀌지는 않았는데, AI 임베딩 벡터만 업데이트하려면 전체 레코드를 다시 수정해야 하므로 비효율적입니다.
그래서 AI 관련 벡터는 따로 관리하는것이 효율적입니다. 예를 들어 analytics.course_vectors
라는 테이블을 만들고 여기에 course_id
, vector
, updated_at
이라는 컬럼만 두면 AI 임베딩의 저장과 갱신이 훨씬 단순해집니다 즉, 벡터 데이터를 넣고 싶다면 기존 테이블을 확장하는 것이 아니라, 벡터만 따로 저장하는 전용 테이블을 만들고, 필요한 ID만 외래키로 연결하는 게 가장 안정적이고 관리가 쉬운 방법입니다.
둘째, AI 결과와 원본 데이터를 명확히 연결해주는 참조 구조가 중요합니다. AI 분석은 결국 원본 데이터를 바탕으로 나온 결과물입니다. 그러니까 이 결과가 어떤 데이터에 기반해서 나왔는지를 명확히 추적할 수 있어야 합니다. 예를 들어 student_embeddings
라는 테이블이 있다고 하면, 벡터값은 단순히 512차원짜리 수치 덩어리일 뿐인데, 이걸 어떤 학생을 표현한 것인지, 서울 캠퍼스인지 제주 캠퍼스인지, 언제 생성된 것인지 명확히 기록하지 않으면 나중에 분석이나 추천 시스템에 쓸 수 없습니다.
그래서 실제 테이블 구조는 이렇게 설계 가능합니다: student_id
, campus
, embedding
, updated_at
. 여기서 student_id
는 기존의 seoul.students
혹은 jeju.students
의 기본키와 매칭되고, campus
는 데이터 출처를 명확히 하기 위한 메타데이터 역할을 합니다. 즉, 단순히 벡터만 저장하는 게 아니라, AI 결과가 어느 테이블의 어느 엔티티에 대응되는지를 명시적으로 외래키 또는 참조 메타데이터로 남겨야 한다는 점이 매우 중요합니다. 그래야만 벡터 기반 검색이나 추천 알고리즘을 구현할 때 “어떤 학생의 임베딩”인지, “어떤 리뷰의 감성 점수인지” 등을 정확히 추적할 수 있습니다. 예를 들어, 학생 리뷰(comment)를 기반으로 감정 분석 점수와 벡터를 저장한다고 하면 analytics.review_embeddings
라는 테이블을 만들고, 여기에 review_id(FK)
, embedding
, sentiment_score
, updated_at
컬럼을 만들면 이 구조는 리뷰와 AI 결과를 연결할 뿐 아니라 AI 분석이 언제 수행되었고 어떤 데이터를 기반으로 했는지를 명확히 하고 이는 향후 모델이 바뀌거나 벡터를 다시 계산해야 할 때 매우 중요한 기준이 됩니다.