소개
시도하고자 했던 것
명리학(한국 전통 사주) 데이터 310건을 체계적으로 관리하고,
향후 110만 건 이상으로 확장 가능한 전문적인 데이터베이스 시스템을 구축하고자 했습니다.
그 이유
기존에는 HTML/JavaScript + localStorage 기반의 프로토타입으로 작업하면서
여러 버전(v10, v17, ULTIMATE 등)을 반복적으로 만들었습니다.
이는 전형적인 "Vibe Coding" 패턴으로,
구조적 설계 없이 감각에 의존한 개발이었습니다.
문제점:
버전 관리 혼란 (여러 ULTIMATE, MEGA 버전 난립)
데이터 무결성 보장 어려움
확장성 부족 (localStorage 한계)
코드 재사용성 낮음
해결 방안:
구조화된 개발 방법론 도입
Ruby on Rails 프레임워크 사용
PostgreSQL 정규화 데이터베이스 설계
체계적인 모델-뷰-컨트롤러(MVC) 패턴 적용
진행 방법
3.1 Ruby on Rails 환경 구축
사용 도구
Ruby 3.4.7 : Windows 네이티브 설치
Rails 8.1.1 : 최신 안정 버전
PostgreSQL 18 : 엔터프라이즈급 데이터베이스
PowerShell : Windows 명령어 환경
프로젝트 생성
```bash
# Rails 프로젝트 생성 (PostgreSQL 사용)
rails new backend-rails -d postgresql
# 데이터베이스 설정
cd backend-rails
default: &default
adapter: postgresql
encoding: unicode
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
development:
<<: *default
database: backend_rails_development
username: postgres
password: your_password
host: localhost
port: 5432
test:
<<: *default
database: backend_rails_test username: postgres
password: your_password
host: 127.0.0.1
port: 5432
production:
primary: &primary_production
<<: *default
database: backend_rails_production
username: backend_rails
password: <%= ENV["BACKEND_RAILS_DATABASE_PASSWORD"] %># config/database.yml 수정
```
database.yml 설정
default: &default
adapter: postgresql
encoding: unicode
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
development:
<<: *default
database: backend_rails_development
username: postgres
password: your_password
host: localhost
port: 5432
test:
<<: *default
database: backend_rails_test
username: postgres
password: your_password
host: 127.0.0.1
port: 5432
production:
primary: &primary_production
<<: *default
database: backend_rails_production
username: backend_rails
password: <%= ENV["BACKEND_RAILS_DATABASE_PASSWORD"] %>---
3.2 정규화된 데이터베이스 설계
테이블 구조 (8개 테이블)
기존 방식: 185개 필드를 가진 평면 구조
새로운 방식: 정규화된 관계형 구조
profiles - 사주 기본 정보 (310건)
wonkuks - 원국 해석 (310건)
daeuns - 대운 정보 (312건)
daeun_groups - 대운 그룹
sewuns - 세운 정보
jeolgis - 24절기 데이터 (향후 2,568건)
ilju_masters - 60갑자 마스터 (향후 60건)
ilju_educations - 일주 교육 콘텐츠 (향후 60건)
마이그레이션 파일 생성
```bash
rails generate migration CreateHanbadookTables
```
마이그레이 션 코드
class CreateHanbadookTables < ActiveRecord::Migration[8.0]
def change
# 1. 프로필 테이블 (기본 사주 정보)
create_table :profiles do |t|
t.string :name, null: false
t.integer :birth_year, null: false
t.integer :birth_month, null: false
t.integer :birth_day, null: false
t.integer :birth_hour
t.integer :birth_minute
t.boolean :is_lunar, default: false
t.string :gender
t.date :solar_date
t.string :cheongan_year
t.string :jiji_year
t.string :cheongan_month
t.string :jiji_month
t.string :cheongan_day
t.string :jiji_day
t.string :cheongan_hour
t.string :jiji_hour
t.text :notes
t.timestamps
end
# 2. 원국 테이블 (사주 해석)
create_table :wonkuks do |t|
t.references :profile, null: false, foreign_key: true
t.text :general_interpretation
t.text :personality
t.text :career
t.text :wealth
t.text :health
t.text :relationship
t.timestamps
end
# 3. 대운 테이블
create_table :daeuns do |t|
t.references :profile, null: false, foreign_key: true
t.integer :sequence_number
t.integer :start_age
t.integer :end_age
t.string :cheongan
t.string :jiji
t.text :interpretation
t.timestamps
end
# 4. 대운 그룹 테이블
create_table :daeun_groups do |t|
t.references :daeun, null: false, foreign_key: true
t.string :group_name
t.text :group_interpretation
t.timestamps
end
# 5. 세운 테이블
create_table :sewuns do |t|
t.references :profile, null: false, foreign_key: true
t.integer :year
t.string :cheongan
t.string :jiji
t.text :interpretation
t.timestamps
end
# 6. 절기 데이터 테이블 (1920-2026, 107년)
create_table :jeolgis do |t|
t.integer :year, null: false
t.integer :month, null: false
t.string :jeolgi_name, null: false
t.datetime :jeolgi_time, null: false
t.integer :day
t.integer :hour
t.integer :minute
t.timestamps
end
# 7. 일주 마스터 테이블 (60 갑자)
create_table :ilju_masters do |t|
t.string :gapja_code, null: false
t.string :cheongan, null: false
t.string :jiji, null: false
t.integer :sequence_number, null: false
t.string :element_heaven
t.string :element_earth
t.string :yinyang_heaven
t.string :yinyang_earth
t.timestamps
end
# 8. 일주 교육 콘텐츠 테이블
create_table :ilju_educations do |t|
t.references :ilju_master, null: false, foreign_key: true
t.text :personality
t.text :career
t.text :wealth
t.text :health
t.text :relationship
t.text :advice
t.text :famous_people
t.integer :quality_score
t.timestamps
end
# 인덱스 추가
add_index :profiles, [:birth_year, :birth_month, :birth_day]
add_index :jeolgis, [:year, :month]
add_index :ilju_masters, :gapja_code, unique: true
end
end```ruby
class CreateHanbadookTables < ActiveRecord::Migration[8.0]
def change
# 1. 프로필 테이블 (기본 사주 정보)
create_table :profiles do |t|
t.string :name, null: false
t.integer :birth_year, null: false
t.integer :birth_month, null: false
t.integer :birth_day, null: false
t.integer :birth_hour
t.integer :birth_minute
t.boolean :is_lunar, default: false
t.string :gender
t.date :solar_date
t.string :cheongan_year
t.string :jiji_year
t.string :cheongan_month
t.string :jiji_month
t.string :cheongan_day
t.string :jiji_day
t.string :cheongan_hour
t.string :jiji_hour
t.text :notes
t.timestamps
end
# 2. 원국 테이블 (사주 해석)
create_table :wonkuks do |t|
t.references :profile, null: false, foreign_key: true
t.text :general_interpretation
t.text :personality
t.text :career
t.text :wealth
t.text :health
t.text :relationship
t.timestamps
end
# 3. 대운 테이블
create_table :daeuns do |t|
t.references :profile, null: false, foreign_key: true
t.integer :sequence_number
t.integer :start_age
t.integer :end_age
t.string :cheongan
t.string :jiji
t.text :interpretation
t.timestamps
end
# ... (나머지 테이블들)
# 인덱스 추가
add_index :profiles, [:birth_year, :birth_month, :birth_day]
add_index :jeolgis, [:year, :month]
add_index :ilju_masters, :gapja_code, unique: true
end
end
```
마이그레이션 실행
```bash
rails db:create
rails db:migrate
```
결과:
== 20251117092219 CreateHanbadookTables: migrating ============================
-- create_table(:profiles)
-> 0.0057s
-- create_table(:wonkuks)
-> 0.0048s
-- create_table(:daeuns)
-> 0.0041s
-- create_table(:daeun_groups)
-> 0.0037s
-- create_table(:sewuns)
-> 0.0046s
-- create_table(:jeolgis)
-> 0.0024s
-- create_table(:ilju_masters)
-> 0.0023s
-- create_table(:ilju_educations)
-> 0.0043s
-- add_index(:profiles, [:birth_year, :birth_month, :birth_day])
-> 0.0012s
-- add_index(:jeolgis, [:year, :month])
-> 0.0011s
-- add_index(:ilju_masters, :gapja_code, {unique: true})
-> 0.0010s
== 20251117092219 CreateHanbadookTables: migrated (0.0368s) ===================/==================-- create_table(:profiles)
-> 0.0057s
-- create_table(:wonkuks)
-> 0.0048s
[...]
== 20251117092219 CreateHanbadookTables: migrated (0.0368s) ===================
```
---
3.3 Rails 모델 및 관계 설정
모델 생성
```bash
rails generate model Profile --skip-migration
rails generate model Wonkuk --skip-migration
rails generate model Daeun --skip-migration
rails generate model DaeunGroup --skip-migration
rails generate model Sewun --skip-migration
rails generate model Jeolgi --skip-migration
rails generate model IljuMaster --skip-migration
rails generate model IljuEducation --skip-migration
```
Profile 모델 (예시)
```ruby
class Profile < ApplicationRecord
# 관계 설정
has_one :wonkuk, dependent: :destroy
has_many :daeuns, dependent: :destroy
has_many :sewuns, dependent: :destroy
# 유효성 검증
validates :name, presence: true
validates :birth_year, presence: true, numericality: { only_integer: true }
validates :birth_month, presence: true, numericality: {
only_integer: true,
greater_than_or_equal_to: 1,
less_than_or_equal_to: 12
}
validates :birth_day, presence: true, numericality: {
only_integer: true,
greater_than_or_equal_to: 1,
less_than_or_equal_to: 31
}
end
```
Wonkuk 모델
```ruby
class Wonkuk < ApplicationRecord
# 관계 설정
belongs_to :profile
# 유효성 검증
validates :profile_id, presence: true
end
```
Daeun 모델
```ruby
class Daeun < ApplicationRecord
# 관계 설정
belongs_to :profile
has_many :daeun_groups, dependent: :destroy
# 유효성 검증
validates :profile_id, presence: true
validates :sequence_number, numericality: {
only_integer: true,
greater_than: 0
}, allow_nil: true
end
```
---
3.4 데이터 임포트 스크립트
기존 JSON 데이터 구조
파일명: myeongli_185fields_v6_310건_변환.json
크기: 6.6MB
구조: 185개 필드의 평면(flat) 구조
레코드 수: 310건
db/seeds.rb 작성
require 'json'
# JSON 파일 읽기
file_path = Rails.root.join('db', 'myeongli_data.json')
json_data = JSON.parse(File.read(file_path))
puts "총 #{json_data.length}건의 데이터를 임포트합니다..."
# 기존 데이터 삭제 (선택사항)
puts "기존 데이터를 삭제합니다..."
Profile.destroy_all
success_count = 0
error_count = 0
json_data.each_with_index do |record, index|
begin
# 1. Profile 생성
profile = Profile.create!(
name: "사주#{index + 1}",
birth_year: record['birth_year'].to_i,
birth_month: record['birth_month'].to_i,
birth_day: record['birth_day'].to_i,
is_lunar: record['calendar_type'] == '음력',
gender: record['gender'],
notes: record['myeongsik']
)
# 2. Wonkuk 생성
if profile
Wonkuk.create!(
profile: profile,
general_interpretation: record['wonkuk_seongpum'],
personality: record['wonkuk_seongpum'],
career: record['wonkuk_jinro'],
wealth: record['wonkuk_jaemul'],
relationship: [
record['wonkuk_bumo'],
record['wonkuk_baeuja'],
record['wonkuk_jasik']
].compact.join("\n\n")
)
end
# 3. Daeun 생성 (최대 10개까지 확인)
(1..10).each do |i|
period_start = record["daeun#{i}_period_start"]
period_end = record["daeun#{i}_period_end"]
daeun_name = record["daeun#{i}_name"]
break if period_start.nil? || period_start.empty?
Daeun.create!(
profile: profile,
sequence_number: i,
start_age: period_start.to_i,
end_age: period_end.to_i,
cheongan: daeun_name.to_s.chars.first,
jiji: daeun_name.to_s.chars.last,
interpretation: [
record["daeun#{i}_summary"],
"재물: #{record["daeun#{i}_jaemul"]}",
"건강: #{record["daeun#{i}_health"]}",
"환경: #{record["daeun#{i}_environment"]}",
"개인: #{record["daeun#{i}_personal"]}"
].compact.join("\n\n")
)
end
success_count += 1
puts "#{index + 1}/#{json_data.length} 완료: #{profile.name}"
rescue => e
error_count += 1
puts "오류 발생 (레코드 #{index + 1}): #{e.message}"
end
end
puts "\n임포트 완료!"
puts "성공: #{success_count}건"
puts "실패: #{error_count}건"
puts "\n통계:"
puts "- Profile: #{Profile.count}건"
puts "- Wonkuk: #{Wonkuk.count}건"
puts "- Daeun: #{Daeun.count}건"`
임포트 완료!
성공: 310건
실패: 0건
통계:
- Profile: 310건
- Wonkuk: 310건
- Daeun: 312건
```
---
3.5 데이터 검증
Rails 콘솔에서 확인
```bash
rails console
```
```ruby
# 레코드 수 확인
Profile.count
=> 310
# 첫 번째 프로필 조회
profile = Profile.first
=> #<Profile id: 1, name: "사주1", birth_year: 1990, ...>
# 관계 확인
=> "사주1"
profile.birth_year
=> 1990
# 원국 해석 확인
profile.wonkuk.personality[0..200]
=> "호기심이 많은 성품으로 궁금한 것이 있으면 찾아내고 탐구하는 모습이 많습니다..."
# 대운 개수 확인
profile.daeuns.count
=> 1
# 대운 해석 확인
profile.daeuns.first.interpretation[0..200]
=> "이 대운은 노력한 것에 비해 더 좋은 성과와 결실을 거둘 수 있는 시기입니다..."
```
결과와 배운 점
최종 결과
구축 완료
✅ Ruby on Rails 프로젝트 생성
✅ PostgreSQL 데이터베이스 8개 테이블 설계
✅ Rails 모델 8개 생성 및 관계 설정
✅ 310건 데이터 100% 성공 임포트 (실패 0건)
✅ 데이터 무결성 검증 완료
데이터 통계
Profile: 310건
Wonkuk: 310건
Daeun: 312건 (일부 프로필에 복수 대운)
총 처리 시간: 약 2분
---
배운 점
1. "Vibe Coding"의 한계 인식
Before (Vibe Coding):
감각적으로 코드 작성
버전 난립 (v10, v17, ULTIMATE, MEGA)
구조 없이 기능 추가
디버깅 어려움
After (구조화된 개발):
설계 우선 접근 (Database Schema → Models → Logic)
단일 프로젝트 버전 관리
MVC 패턴 준수
명확한 데이터 흐름
2. 정규화의 중요성
평면 구조 (185필드)의 문제:
데이터 중복
확장성 부족
복잡한 쿼리
정규화 구조의 장점:
데이터 무결성 보장
유연한 확장 (대운 개수 제한 없음)
효율적인 쿼리
관계형 데이터베이스의 강점 활용
3. Rails의 Convention over Configuration
Rails의 규칙을 따르니:
보일러플레이트 코드 최소화
명확한 프로젝트 구조
빠른 개발 속도
커뮤니티 모범 사례 활용
---
시행착오
1. database.yml 들여쓰기 오류
문제:
```yaml
development:
database: backend_rails_development
username: postgres # ← 들여쓰기 없음!
password: han03120!
```
해결:
```yaml
development:
database: backend_rails_development
username: postgres # ← 공백 2개
password: han03120!
```
교훈: YAML은 들여쓰기에 매우 민감. 반드시 일관성 유지.
---
2. test 데이터베이스 생성 실패
문제:
```
connection to server at "localhost" (::1), port 5432 failed:
fe_sendauth: no password supplied
```
원인: IPv6(::1) 연결 시 PostgreSQL 인증 설정 문제
해결:
```yaml
test:
host: 127.0.0.1 # IPv4 사용
```
교훈: Windows + PostgreSQL 조합에서는 명시적으로 IPv4 지정.
---
3. 모델 파일에 잘못된 코드
문제:
```ruby
class DaeunGroup < ApplicationRecord
belongs_to :daeun
... # ← 이게 뭐지?
end
```
원인: 복사-붙여넣기 중 설명 텍스트가 코드로 들어감
해결:
각 파일을 메모장으로 열어 수동 확인
정확한 코드만 붙여넣기
교훈:
코드 복사 시 주의 깊게 확인
에러 메시지를 잘 읽으면 파일 위치와 줄 번호 알 수 있음
---
4. PowerShell vs 메모장 혼동
문제:
설명 텍스트를 PowerShell에 입력
```powershell
PS> class Profile < ApplicationRecord # ← Ruby 코드를 직접 입력!
```
해결:
PowerShell: 명령어만 (`rails`, notepad 등)
메모장: Ruby/YAML 코드 작성
교훈: 작업 환경 구분 명확히!
실시간으로 데이터 확인, 쿼리 테스트, 관계 검증 가능!
3. 진행 상황 출력
seeds.rb에서:
```ruby
puts "#{index + 1}/#{json_data.length} 완료: #{profile.name}"
```
긴 작업 시 진행 상황을 눈으로 확인하면 안심!
4. 한 번에 하나씩
복잡한 작업을 한 번에 하려다 실패하면:
테이블 하나만 먼저 만들기
모델 하나만 먼저 테스트
데이터 1건만 임포트 테스트
확인 후 확장!
---
도움이 필요한 부분
1. Windows 환경 최적화
현재는 Windows 네이티브 Ruby를 사용했지만, WSL(Windows Subsystem for Linux)로 이전하면:
더 나은 성능
gem 호환성 향상
Linux 프로덕션 환경과 동일
→ 나중에 WSL 전환 방법 학습 필요
2. API 엔드포인트 설계
데이터를 외부에서 조회할 수 있는 RESTful API 구축:
GET /api/profiles
GET /api/profiles/:id
GET /api/profiles/:id/wonkuk
GET /api/profiles/:id/daeuns
→ Rails API 모범 사례 학습 필요
3. 대용량 데이터 처리
향후 110만 건 확장 시:
페이지네이션
캐싱 전략
인덱싱 최적화
백그라운드 작업 (Sidekiq)
<< 앞으로의 계획 >>
단기 계획 (1-2주)
1. 절기 데이터 임포트
2,568건 (1920-2026, 107년)
24절기 정확한 시각 데이터
사주 계산의 "GPS" 역할
2. 60 갑자 일주 콘텐츠 임포트
일주별 성격, 진로, 재물, 건강 분석
교육용 콘텐츠 체계화
3. 나머지 테이블 활용
Sewun (세운) 데이터 추가
DaeunGroup 활용 방안 설계
도움 받은 글 (옵션)
지피터스 참고 자료
"Vibe Coding의 문제점" 영상: 구조화된 개발의 중요성 깨달음
구조화된 코딩 템플릿 활용
외부 참고 자료
Ruby on Rails 공식 문서
Rails Guides: https://guides.rubyonrails.org/
특히 Active Record 관계 설정 부분
PostgreSQL 문서
Official Documentation: https://www.postgresql.org/docs/
Windows 설치 및 설정 가이드
GitHub 사례
Rails + PostgreSQL 프로젝트 구조 참고
데이터베이스 마이그레이션 베스트 프랙티스