오늘은 문제가 많았던 출석관련 기능을 수정했다. 기존에 사용하던 SQLite 데이터베이스의 한계와 불편함을 느꼈다. 그럼 이번 수정사항과 여러 시행착오를 아래에서 자세히 설명해보겠다.
기존에 사용하던 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가지 변경사항을 하나씩 자세히 알아보자!
먼저 asyncpg 라이브러리를 사용하여 비동기 데이터베이스 연결을 구현했다.
requirements.txt에 추가해주고 라이브러리를 설치해주었다.
asyncpg==0.28.0
pytz==2023.3
db 연결 설정은 database.py 파일에서 PostgreSQL 연결을 설정했다.
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: 사용자별 출석 통계 저장.기존의 문제점이었던 한국 시간대 미 반영을 드디어 적용했다.
ptyz를 통해 다음과 같이 한국의 시간으로 설정할 수 있다.
import pytz
kst = pytz.timezone('Asia/Seoul')
today = datetime.now(kst).strftime('%Y-%m-%d')
또한 연속 출첵 로직도 어제 출석했는지 확인해 연속 출석일을 계산한다. 그 후 최대 연속 출석일수를 갱신한다. 스트릭이 이어지면 1을 더하고 아니면 1로 초기화한다.
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
기존 main.py에 모든 함수를 담아두었던 함수형 프로그래밍에서 기능별 모듈화를 진행했다.
main.py: 봇 초기화 및 명령어 등록.
database.py: 데이터베이스 연결 및 쿼리 실행.
cogs/attendance.py: 출석체크, 출석 정보 조회, 월간 출석 현황 등의 명령어 구현.
cogs/voice.py: 음성채널 입출력 등의 명령어 구현.
docker-compose.yml을 다음과 같이 구성해주었다.
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에서 환경 변수 탈출 처리
그리고 환경변수도 .env 에 업데이트해주었다
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가 포함되어 있어야 합니다. 예를 들어
nowalex322 adm dialout cdrom floppy audio dip video plugdev netdev lxd ubuntu google-sudoers docker
이후 postgreSQL 잘 됐으면 Datagrip도 연결해볼 수 있다.
그 전에 방화벽 규칙을 만들어야한다.

여기서 방화벽 규칙 만들기를 누르고


다음과 같이 설정해준다.
이제 Datagrip에서 새로운 Data Source에서 PostgreSQL 선택하고

연결하기위한 정보 입력해주면 (외부 IP 주소는 VM 인스턴스에서 확인가능)

잘 연결된 것을 볼 수 있다!
빨리 음악 재생까지 완성하고싶다. 그리고 봇을 디벨롭 하면 할수록 욕심이 더 커져 생각보다 스케일이 커지는 것 같다.