Ruby on Rails로 명리학 데이터베이스 시스템 구축하기 - 1

소개



시도하고자 했던 것

명리학(한국 전통 사주) 데이터 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, ...>

# 관계 확인

profile.name

=> "사주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 프로젝트 구조 참고

데이터베이스 마이그레이션 베스트 프랙티스



2
2개의 답글

뉴스레터 무료 구독

👉 이 게시글도 읽어보세요