📚 엔지니어 · 세션 · 프로젝트 이해 (수정중) · L02

L02 — Django 프로젝트와 데이터베이스(PostgreSQL) 연결 (수정중)

작성일자 2026-06-14  ·  수정일자 2026-06-14  ·  강의차수 L02  ·  예상소요 30분

목표 이 강의가 끝나면 학습자가 settings 의 DATABASES 설정을 한 줄씩 읽고, 우리 서버의 PostgreSQL 에 psql 로 직접 접속해 테이블·값을 확인하고, dev/prod·로컬/원격(RDS) DB 가 설정에서 어떻게 갈리는지 설명할 수 있다.

1. settings 의 DATABASES — popax 로 보기

L01 에서 settings 가 base / local / production 으로 갈린다고 했다. 그중 DB 연결 설정을 본다. popax 프로젝트의 local.py:

# popax/settings/local.py:18
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'popax',
        'USER': 'ubuntu',
        'PASSWORD': os.environ.get('DB_PASSWORD', ''),
        'HOST': 'localhost',
        'PORT': '5432',
    }
}

이 한 덩어리가 곧 "이 Django 프로젝트는 어떤 DB에, 누구로, 어디로 붙는다" 는 선언이다.


2. Django 가 붙을 수 있는 데이터베이스

Django 는 PostgreSQL 전용이 아니다. 엔진만 바꾸면 다른 DB도 붙는다.

  • 공식 지원: PostgreSQL · MariaDB · MySQL · SQLite
  • SQLite 는 Django 프로젝트를 처음 생성할 때 기본값이다. 우리는 standarda-template 을 통해 PostgreSQL 이 기본으로 설정되도록 해놨다.
  • 서드파티 패키지로 지원: MS SQL Server, MongoDB 등
  • 클라우드 기반 DB: AWS RDS, Google Cloud SQL, Azure Database 등 (→ §8)

→ ENGINE 값만 그 DB에 맞게 바꾸면 같은 Django 코드가 다른 DB 위에서 돈다.


3. DATABASES 한 줄씩 — 이게 무슨 뜻인가

키 popax 값 의미
ENGINE django.db.backends.postgresql PostgreSQL 을 쓰겠다 (DB 종류 선택)
NAME popax 붙을 데이터베이스 이름 (PostgreSQL 안의 DB 하나)
USER ubuntu 그 DB에 접속할 PostgreSQL 롤(계정)
PASSWORD os.environ.get('DB_PASSWORD', '') 비밀번호는 코드가 아니라 .env 에서 읽는다 (L01 §6)
HOST localhost DB가 같은 시스템(서버) 에 있다는 뜻
PORT 5432 PostgreSQL 이 듣고 있는 포트
  • HOST = localhost 가 핵심 포인트다. "DB가 Django 와 같은 OS 위에 있다" 는 뜻이다.
  • 거꾸로 말하면 원격(remote) DB도 가능하다는 얘기 — 다른 서버/다른 OS의 PostgreSQL에 접속해 그 DB를 쓸 수 있다 (→ §7).
  • PASSWORD 는 절대 코드에 박지 않는다. .env 에 DB_PASSWORD=... 로 두고 os.environ.get 으로 읽는다. (그래서 강의노트에도 실제 비밀번호는 적지 않는다.)

4. 우리 시스템의 PostgreSQL 직접 보기 — psql

설정만 보지 말고 실제 DB를 들여다본다. 서버(dev 박스)에 SSH 로 접속한 상태에서, 나는 ubuntu OS 유저다.

그냥 psql 만 치면 에러가 난다

$ psql
psql: error: ... FATAL:  database "ubuntu" does not exist

인자 없이 psql 만 치면 OS 유저 이름(ubuntu)과 같은 이름의 DB에 붙으려 한다. 그런데 ubuntu 라는 DB가 없어서 나는 에러다. 소켓(같은 서버 안에서 프로그램끼리 잇는 통로)엔 이미 붙었으니, 서버가 안 떴거나 권한이 없는 게 아니라 "붙을 DB를 안 골라준 것" 뿐이다. → DB 이름을 주면 된다.

DB 목록 보기 — psql -l

psql -l

현재 이 서버 PostgreSQL에 깔려 있는 DB들이 쭉 나온다. 실제 출력(일부):

                                  List of databases
        Name        |  Owner   | Encoding | Collate |  Ctype  | Access privileges
--------------------+----------+----------+---------+---------+-------------------
 agentv             | ubuntu   | UTF8     | C.UTF-8 | C.UTF-8 |
 chain              | ubuntu   | UTF8     | C.UTF-8 | C.UTF-8 |
 knowclaw           | ubuntu   | UTF8     | C.UTF-8 | C.UTF-8 |
 popax              | ubuntu   | UTF8     | C.UTF-8 | C.UTF-8 |
 popax_prod         | ubuntu   | UTF8     | C.UTF-8 | C.UTF-8 |
 ...                |          |          |         |         |

프로젝트마다 DB 하나씩. Owner 가 전부 ubuntu 인 게 보인다(§6 참고). popax(dev) 와 popax_prod(Production) 가 둘 다 있는 것도 확인.

특정 DB 접속 → 테이블 보기

psql -d popax        # popax DB 로 접속

접속되면 프롬프트가 popax=# 로 바뀐다. \conninfo 로 지금 상태를 확인할 수 있다:

popax=# \conninfo
You are connected to database "popax" as user "ubuntu" via socket in "/var/run/postgresql" at port "5432".

이 상태에서 백슬래시 명령으로 테이블 목록을 본다:

\dt                              -- 이 DB의 테이블 목록
                     List of relations
 Schema |              Name               | Type  | Owner
--------+---------------------------------+-------+--------
 public | accounts_user                   | table | ubuntu
 public | auth_group                      | table | ubuntu
 public | auth_permission                 | table | ubuntu
 public | meeting_chat_chatsession        | table | ubuntu
 public | meeting_chat_organizationmemory | table | ubuntu
 public | meeting_chat_usermemory         | table | ubuntu
 ...
  • accounts_user, auth_group, auth_permission … 은 Django 가 기본으로 만들어주는 테이블.
  • 단 accounts_user 는 Django 기본 User 그대로가 아니라 우리가 AbstractBaseUser 로 커스텀한 User 다.
  • meeting_chat_chatsession, meeting_chat_organizationmemory, meeting_chat_usermemory … 는 popax(팝업 회의) 를 만들며 생성한 테이블.

테이블 이름 = 앱_모델 규칙. meeting_chat 앱의 OrganizationMemory 모델 → meeting_chat_organizationmemory.

특정 테이블의 구조(스키마) 보기 — \d

meeting_chat_organizationmemory(조직 단위 메모리) 테이블의 구조를 보자:

\d meeting_chat_organizationmemory
                    Table "public.meeting_chat_organizationmemory"
      Column       |           Type           | Nullable |             Default
-------------------+--------------------------+----------+----------------------------------
 id                | bigint                   | not null | generated by default as identity
 kind              | character varying(20)    | not null |
 key               | character varying(200)   | not null |
 content           | text                     | not null |
 confidence        | double precision         | not null |
 active            | boolean                  | not null |
 created           | timestamp with time zone | not null |
 updated           | timestamp with time zone | not null |
 source_session_id | bigint                   |          |
 organization_id   | bigint                   | not null |
Indexes:
    "meeting_chat_organizationmemory_pkey" PRIMARY KEY, btree (id)
    "..._organization_id_kind_key_..._uniq" UNIQUE CONSTRAINT, btree (organization_id, kind, key)
Foreign-key constraints:
    ... FOREIGN KEY (organization_id) REFERENCES organizations_organization(id) ...
    ... FOREIGN KEY (source_session_id) REFERENCES meeting_chat_chatsession(id) ...

컬럼 이름·타입·nullable·인덱스·외래키가 다 나온다.

  • not null 은 그 컬럼(= Django 모델의 필드)이 항상 값이 있어야 로우가 만들어진다는 제약이다. 여기선 source_session_id 만 NULL 허용, 나머지는 전부 not null.
  • Type: character varying(20) = 최대 20자 문자열(Django CharField(max_length=20)), text = 길이 제한 없는 문자열, timestamp with time zone = 시각, bigint = 큰 정수(기본키·외래키에 쓰는 타입).
  • Foreign-key constraints: organization_id 가 organizations_organization(id) 를 가리킨다 — Django 의 ForeignKey 가 DB 레벨에선 이렇게 외래키로 박힌다.
  • (참고) 예전엔 이미 데이터가 있는 테이블에 not null 필드를 새로 추가하면 곤란을 자주 겪었다. 기존 로우엔 그 값이 없는데 "비면 안 된다"고 막으니, 모델 변경을 DB에 반영하는 작업(마이그레이션)이 중간에 터진 것이다. 지금은 Claude Code 가 알아서 처리해준다.

실제 값 보기 — SELECT

같은 테이블의 실제 값을 본다. 컬럼이 많고 content 가 길어서 보고 싶은 것만 골라 LIMIT 을 건다:

SELECT id, kind, key, left(content, 35) AS content, confidence
FROM meeting_chat_organizationmemory
ORDER BY id LIMIT 6;
 id |  kind   |   key   |               content                | confidence
----+---------+---------+--------------------------------------+------------
  4 | company | default | {"name": "default", "description":    |          1
  6 | company | default | {"name": "default", "description":    |          1
  7 | term    | FDE     | {"definition": "회사 내부 팀 또는      |          1
  8 | person  | 정요천  | {"canonical": "정요천", "org": "팝업   |          1
  9 | person  | 강건    | {"canonical": "강건", "org": "팝업스   |          1
 10 | person  | 김현진  | {"canonical": "김현진", "org": "팝업   |          1

구조가 핵심이다: kind 가 company/term/person 으로 나뉘고, key 는 그 항목의 식별자(회사·용어·사람 이름), content 에는 JSON 으로 상세(canonical·org·role 등)가 들어간다. 회의 챗봇이 조직 단위로 누적해 둔 메모리다.

SELECT 컬럼들 FROM 테이블 ... ; = "그 테이블에서 골라 보여달라". 끝에 세미콜론(;) 을 꼭 붙여야 실행된다. 전체를 다 보려면 SELECT * FROM meeting_chat_organizationmemory; 처럼 * 를 쓴다(행·컬럼이 많으면 화면이 넘치니 \x(§아래) 나 LIMIT 을 쓰는 게 좋다).

\x      -- 컬럼이 많아 가로로 깨질 때: 한 행을 "컬럼 | 값" 세로로 펼쳐 보여줌 (다시 치면 해제)
\q      -- psql 빠져나오기

한 줄로 끝내려면 접속 안 하고: psql -d popax -c "SELECT kind, count(*) FROM meeting_chat_organizationmemory GROUP BY kind;"


5. 이 PostgreSQL 은 언제 깔렸나 / pgAdmin

  • 팝업 서버를 처음 셋업할 때 OS(Ubuntu)에 PostgreSQL 을 설치해뒀다. 버전은 14 (현재 14.23).
  • psql 로 터미널에서 보는 건 솔직히 불편하다. pgAdmin 같은 GUI 도구를 쓰면 스키마·값을 훨씬 편하게 볼 수 있다.
  • 예전엔 pgAdmin 을 많이 썼지만, Claude Code 를 쓰고부터는 거의 안 쓰게 됐다 (Claude 에게 시키면 되니까).

6. dev ↔ prod 는 DB가 갈린다 — popax ↔ popax_prod

L01 §10 에서 DATABASES 가 환경별로 갈린다고 했다. 실제로 production.py 를 보면:

# popax/settings/production.py:17
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'popax_prod',          # ← local 은 popax, prod 는 popax_prod
        'USER': 'ubuntu',
        'PASSWORD': os.environ.get('DB_PASSWORD', ''),
        'HOST': 'localhost',
        'PORT': '5432',
    }
}
  • local.py → popax, production.py → popax_prod. 이름만 다르다.
  • psql -l 에 popax 와 popax_prod 가 둘 다 보이는 이유가 이거다.
  • Django 프로젝트를 처음 셋업할 때, PostgreSQL 에 popax DB를 먼저 CREATE 해두고 USER 를 ubuntu 로 지정해야 한다. (지금 우리 서버는 사람별 계정 대신 공통으로 ubuntu 롤을 쓴다. PostgreSQL 설치 시 postgres 슈퍼유저가 있고, 셋업 때 ubuntu 롤을 따로 만들어 비밀번호를 부여해뒀다 — 그 값이 .env 의 DB_PASSWORD.)

포트 정리(L01 §8 연장): 한 OS에서 프로그램마다 포트가 다르다 — PostgreSQL 5432, Django dev 서버 8000/8023…, SSH 22, Apache(HTTPS) 443.


7. 원격(리모트) 데이터베이스 — 별도 DB 서버

지금은 dev/prod 모두 HOST='localhost' — Django 와 PostgreSQL 이 같은 박스에 있다. 하지만 운영의 정석은 DB를 별도 서버로 분리하는 것이다.

  • 현재 prod(웹) 서버: 52.79.132.206.
  • 정석대로라면 DB 전용 서버(예: 52.79.132.207)를 따로 띄우고 거기에 PostgreSQL 을 설치한다.
  • 그러면 production.py 의 HOST 를 localhost 가 아니라 그 DB 서버 주소(52.79.132.207) 로 바꾼다. (NAME/USER/PASSWORD/PORT 는 그 서버 기준으로.)
# (예시) 별도 DB 서버를 쓸 때 production.py
'HOST': '52.79.132.207',   # localhost 가 아니라 DB 서버 IP
'PORT': '5432',
  • 단, DB 서버는 외부에서 오는 모든 접속을 그냥 열어주지 않는다 (위험). PostgreSQL 의 접근 제어(pg_hba.conf 등)에서 특정 IP(예: 웹 서버 52.79.132.206)에서 오는 접속만 허용하도록 설정해야 한다.

8. AWS RDS — DB 서버를 대신 관리해주는 서비스

별도 DB 서버를 직접 만들려면 AWS 인스턴스를 빌려 PostgreSQL 설치 + 보안그룹 + 방화벽 + OS 셋업까지 다 해야 해서 번거롭다. 그걸 대신해주는 관리형 DB 서비스가 AWS RDS 다.

  • RDS 를 쓰면 우리는 DB OS를 직접 셋업하지 않고, RDS 가 주는 엔드포인트(host)·포트·계정 을 production.py 의 DATABASES 에 그대로 넣어주면 된다.
  • 로컬 개발은 그대로 로컬 PostgreSQL(localhost), prod 만 RDS 엔드포인트를 보게 하면 dev/prod 분리가 자연스럽게 유지된다.

RDS 를 쓸 때 production.py 의 DATABASES 예시 (§1 의 local.py 와 비교해서 보면, 달라지는 건 결국 HOST 와 비밀값들뿐이다):

# (예시) production.py — DB를 RDS로 둘 때
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',          # 엔진은 그대로 PostgreSQL
        'NAME': 'popax_prod',                                # RDS 안에 만든 DB 이름
        'USER': os.environ.get('DB_USER', ''),               # RDS 마스터/전용 계정
        'PASSWORD': os.environ.get('DB_PASSWORD', ''),       # 비밀번호는 .env.production 에
        'HOST': os.environ.get('DB_HOST', ''),               # ← RDS 엔드포인트
        #   예: popax-prod.abcd1234.ap-northeast-2.rds.amazonaws.com
        'PORT': '5432',
    }
}
  • HOST 가 localhost 가 아니라 RDS 엔드포인트 주소가 되는 게 핵심. (localhost → 같은 박스, 엔드포인트 → AWS가 관리하는 원격 DB)
  • 엔드포인트·계정·비밀번호는 코드에 박지 말고 .env.production 에 두고 os.environ.get 으로 읽는다 (L01 §6 와 동일 원칙).
  • RDS 쪽 보안그룹(security group) 에서 우리 prod(웹) 서버 IP/보안그룹만 5432 포트로 들어오는 접속(인바운드) 을 허용하도록 열어줘야 접속된다 (§7 의 pg_hba IP 허용을 AWS가 보안그룹으로 대신하는 셈).

오늘 정리 + 다음

  • 정리: DATABASES 는 "어떤 DB(ENGINE)에, 어떤 이름(NAME)으로, 누구로(USER/PASSWORD), 어디의(HOST/PORT)" 붙을지를 적는 곳이다. 우리는 PostgreSQL 을 쓰고, dev=popax/prod=popax_prod 로 이름만 갈린다(둘 다 현재는 localhost). 비밀번호는 .env 에 둔다. psql 로 같은 DB에 직접 붙어 \dt·\d·SELECT 로 테이블과 값을 볼 수 있다.
  • 흔한 함정: 그냥 psql → database "ubuntu" does not exist. DB 이름을 줘야 한다 (psql -d popax). 그리고 SQL 끝의 세미콜론(;) 을 빼먹으면 실행이 안 된다.
  • 다음 시간: 모델(모델 = 테이블) 정의 → 마이그레이션 → 실제 테이블 생성으로 이어지는 흐름.
  • 자습 권장: 시간 날 때 psql -d <내프로젝트> 로 들어가 \dt / \d <테이블> / SELECT ... LIMIT 5; 직접 해보기. (값이 큰 테이블은 항상 LIMIT)
이 강의를 학습하셨나요?