디스코드 뮤직 봇 개발 (4)
오늘은 문제가 많았던 출석관련 기능을 수정했다. 기존에 사용하던 SQLite 데이터베이스의 한계와 불편함을 느꼈다. 그럼 이번 수정사항과 여러 시행착오를 아래에서 자세히 설명해보겠다.
SQLite에서 PostgreSQL로 마이그레이션 및 Docker Compose 배포기
기존에 사용하던 SQLite 데이터베이스를 PostgreSQL로 전환하고, Docker Compose를 통해 배포 환경을 구축한 과정을 기록하려고 한다.
기존 SQLite로 출석을 관리할때의 문제점은 다음과 같았다.
-
확장성 부족: SQLite는 단일 파일 기반의 데이터베이스로, 동시 접속이 많아지면 성능이 급격히 저하된다. 특히, 출석체크 기능을 사용하는 사용자가 많아지면, 데이터베이스 연결이 병목 현상이 발생할 수 있다.
-
비효율적인 출석체크 로직: 매번 데이터베이스 연결을 열고 닫는 방식으로 인해 성능이 저하되었다. 또한, 연속 출석 체크 로직이 복잡하고 비효율적이었다.
-
한국 시간대 미적용:
datetime.now()
가 UTC 기준으로 동작하여, 한국 시간대(KST)를 반영하지 못했다. 이로 인해 출석체크 시간이 매우 불편했다.
또한 극한의 함수형 프로그래밍에 의해 파일 구조 문제도 있었다.
-
단일 파일(main.py)에 모든 로직 집중되었던 점이 문제였다. 모든 코드가 main.py 파일에 작성되어 있어, 유지보수가 어려웠으며, 특히 데이터베이스 연결 로직과 비즈니스 로직이 혼재되어 있어 코드의 재사용성이 떨어졌다.
-
모듈화 부재로 출석체크, 음성 채널, 음악 재생 등의 기능이 모두 한 파일에 작성되어 있어, 코드도 매우 길고 기능 추가나 수정이 어려웠다.
이에 문제점을 개선하고자 목표를 잡았다.
수정사항
1. 데이터베이스 전환: SQLite → PostgreSQL
-
PostgreSQL은 확장성이 뛰어나고, 동시 접속 처리에 적합하다.
-
비동기 처리를 위한
asyncpg
라이브러리 사용했다.
2. 출석체크 로직 개선
-
한국 시간대(KST) 적용: pytz 라이브러리를 사용하여 한국 시간대를 반영했다
-
연속 출석 체크 로직 최적화: 매번 전체 데이터를 조회하지 않고, 사용자별 통계를 별도의 테이블(
user_stats
)로 관리하여 성능을 개선했다.
3. 파일 구조 모듈화
-
main.py: 봇의 메인 로직을 담당. 봇 초기화 및 명령어 등록 처리.
-
database.py: 데이터베이스 연결 및 쿼리 실행을 담당.
-
cogs/attendance.py: 출석체크, 출석 정보 조회, 월간 출석 현황 등의 명령어를 구현
-
cogs/voice.py: 음성 채널 관련 명령어 모듈 파일
4. Docker Compose를 통한 배포
-
PostgreSQL과 봇을 Docker 컨테이너로 구성했다.
-
docker-compose.yml 파일을 작성하여 PostgreSQL과 봇을 컨테이너로 구성했다.
**위 4가지 변경사항을 하나씩 자세히 알아보자! **
1. 데이터베이스 전환: SQLite → PostgreSQL
먼저 asyncpg 라이브러리를 사용하여 비동기 데이터베이스 연결을 구현했다. requirements.txt에 추가해주고 라이브러리를 설치해주었다.
asyncpg==0.28.0
pytz==2023.3
db 연결 설정은 database.py 파일에서 PostgreSQL 연결을 설정했다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import asyncpg
import os
from dotenv import load_dotenv
load_dotenv()
class Database:
def __init__(self):
self.pool = None
async def init_db(self):
self.pool = await asyncpg.create_pool(
host=os.getenv('DB_HOST'),
database=os.getenv('DB_NAME'),
user=os.getenv('DB_USER'),
password=os.getenv('DB_PASSWORD')
)
테이블 구조는 다음과 같다.
attendance
: 출석 기록 저장.user_stats
: 사용자별 출석 통계 저장.
2. 출석체크 로직 개선
기존의 문제점이었던 한국 시간대 미 반영을 드디어 적용했다.
ptyz
를 통해 다음과 같이 한국의 시간으로 설정할 수 있다.
1
2
3
import pytz
kst = pytz.timezone('Asia/Seoul')
today = datetime.now(kst).strftime('%Y-%m-%d')
또한 연속 출첵 로직도 어제 출석했는지 확인해 연속 출석일을 계산한다. 그 후 최대 연속 출석일수를 갱신한다. 스트릭이 이어지면 1을 더하고 아니면 1로 초기화한다.
1
2
3
4
5
6
7
async def mark_attendance(self, user_id, date_str):
# 어제 출석했는지 확인
yesterday = (date_obj - timedelta(days=1))
if last_date == yesterday:
current_streak += 1
else:
current_streak = 1
3. 파일 구조 모듈화
기존 main.py에 모든 함수를 담아두었던 함수형 프로그래밍에서 기능별 모듈화를 진행했다.
main.py
: 봇 초기화 및 명령어 등록.
database.py
: 데이터베이스 연결 및 쿼리 실행.
cogs/attendance.py
: 출석체크, 출석 정보 조회, 월간 출석 현황 등의 명령어 구현.
cogs/voice.py
: 음성채널 입출력 등의 명령어 구현.
4. Docker Compose 설정
docker-compose.yml
을 다음과 같이 구성해주었다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
version: "3"
services:
db:
image: postgres:14
volumes:
- postgres_data:/var/lib/postgresql/data
environment:
- POSTGRES_PASSWORD=${DB_PASSWORD}
- POSTGRES_USER=${DB_USER}
- POSTGRES_DB=${DB_NAME}
ports:
- "5432:5432"
restart: always
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${DB_USER} -d $${DB_NAME}"]
interval: 10s
timeout: 5s
retries: 5
bot:
build: .
depends_on:
- db
restart: always
env_file:
- .env
volumes:
postgres_data:
여기서 $$
는 찾아보니 다음과 같은 설명을 찾았다.
Docker Compose에서 환경 변수 탈출 처리
- Docker Compose 2.x 버전에서는 이중 달러 기호($$)를 사용해 변수가 Docker Compose에 의해 평가되지 않도록 할 수 있습니다:
그리고 환경변수도 .env
에 업데이트해주었다
1
2
3
4
5
DISCORD_TOKEN = 디스코드 토큰값
DB_HOST=db
DB_NAME=meloD_db
DB_USER=nowalex322
DB_PASSWORD=db 비밀번호
트러블 슈팅
1. PostgreSQL 연결 오류
-
문제: FATAL: role “nowalex322” does not exist
-
원인: Docker Compose에서 환경 변수가 제대로 확장되지 않음
-
해결: healthcheck에서 $${DB_USER}와 같이 이중 달러 기호를 사용하여 변수 확장
2. DataGrip 연결 실패
-
문제: DataGrip에서 PostgreSQL에 접속할 수 없음
-
원인: Docker 컨테이너 내부에서 Unix 도메인 소켓을 사용하려고 시도
-
해결: TCP/IP를 통해 연결 (-h localhost 옵션 추가).
docker-compose exec db psql -U nowalex322 -d meloD_db -h localhost
- 접속이 성공하면 다음과 같은 프롬프트가 나타납니다:
psql (14.0 (Debian 14.0-1.pgdg110+1)) Type “help” for help.
meloD_db=#
3. Docker 권한 문제
-
문제: PermissionError: [Errno 13] Permission denied
-
원인: 현재 사용자가 docker 그룹에 속해 있지 않음
-
해결: 사용자를 docker 그룹에 추가하고 재로그인
-
사용자를 docker 그룹에 추가 다음 명령어로 현재 사용자를 docker 그룹에 추가
sudo usermod -aG docker $USER
이 명령어는 현재 사용자($USER)를 docker 그룹에 추가 -
변경 사항 적용 그룹 변경 사항을 적용하려면 로그아웃 후 다시 로그인하거나, 다음 명령어로 세션을 재시작
newgrp docker
-
변경 사항 확인 다음 명령어로 사용자가 docker 그룹에 추가되었는지 확인합니다:
groups
출력 결과에 docker가 포함되어 있어야 합니다. 예를 들어
-
1
nowalex322 adm dialout cdrom floppy audio dip video plugdev netdev lxd ubuntu google-sudoers docker
이후 postgreSQL 잘 됐으면 Datagrip도 연결해볼 수 있다.
그 전에 방화벽 규칙을 만들어야한다.
다음과 같이 설정해준다.
- 트래픽 방향: “인그레스” (외부에서 들어오는 트래픽)
- 작업: “허용”
- 소스 IP 범위: 내 IP 값 검색해서 넣기(특정 IP에서만 접속 허용하려면)
- 프로토콜 및 포트: TCP 포트 (PostgreSQL은 기본적으로 TCP 5432 포트 사용)
- PostgreSQL의 기본 포트 : 5432
이제 Datagrip에서 새로운 Data Source에서 PostgreSQL 선택하고
연결하기위한 정보 입력해주면 (외부 IP 주소는 VM 인스턴스에서 확인가능)
잘 연결된 것을 볼 수 있다!
빨리 음악 재생까지 완성하고싶다. 그리고 봇을 디벨롭 하면 할수록 욕심이 더 커져 생각보다 스케일이 커지는 것 같다.