L02 — Django 프로젝트와 데이터베이스(PostgreSQL) 연결 (수정중)
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자 문자열(DjangoCharField(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 에
popaxDB를 먼저CREATE해두고 USER 를ubuntu로 지정해야 한다. (지금 우리 서버는 사람별 계정 대신 공통으로ubuntu롤을 쓴다. PostgreSQL 설치 시postgres슈퍼유저가 있고, 셋업 때ubuntu롤을 따로 만들어 비밀번호를 부여해뒀다 — 그 값이.env의DB_PASSWORD.)
포트 정리(L01 §8 연장): 한 OS에서 프로그램마다 포트가 다르다 — PostgreSQL
5432, Django dev 서버8000/8023…, SSH22, 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_hbaIP 허용을 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)