[DE프젝]두 번째 회고

포동동·2023년 3월 2일
0

[유튜브 프로젝트]

목록 보기
2/13
post-thumbnail

👾 엘라스틱에서 MySQL로

1월 한 달을 es 공부와 구축에 쏟았다. 그러던 중 2월 초, 백엔드분으로부터 es로만 구축하면 서버가 뻗을 것이다 라는 얘기를 들었다. 그래서 처음 DB였던 MySQL로 회귀하였다.....

처음엔 간단하게 MySQL이 RDB니까 더 간단할 거라 생각했다. 하지만, 살제 데이터들을 넣고 빼보고 하면서 왜 괜히 "관계형"이고 왜 괜히 "레퍼런스가 많은지" 뼈저리게 느끼게 되었다.

실제로 남자친구가 "오 맨날 구사일생하는거야? (구글링은 사년차든 일년차든 생활이다)" 라고 말했다.. 하지만 난 요새 대기만성이다(대체로 기획자를 만나면 성격 단호박 된다).

컬럼을 추가하거나 연산해서 넣는 코드가 들어가면 부하가 생길까 노심초사하게 되고, 좀 더 잘 된 정규화, 속도가 빠른 역정규화는 없을까 찾는 시간이 늘어갔다. es를 나중에 사용한다고는 해도 당장에 MySQL에 모든 데이터를 집어넣고 시각화를 위해 es로 넘길때까지는 시간이 더 지나야하니 개인적으로 조오오오금 속상했다.

그래도 회사에서는 postgresql, 프젝에서는 mysql을 쓰니 여기다 oracle만 하면 나 좀 괜찮은 주니어일지도....? 라고 위안을 했다.



🚈 그래서 어떤 걸 했나

  • (날아갔지만) ES 구축
  • 크롤링 최적화
    • 크롤링 할 사이트 조건 코드 작성
  • MySQL 테이블 설계
  • 크롤링 한 데이터를 AI 학습 후 DB에 insert 하는 작업
  • AI 모델 학습용 데이터 수집

적고 나니 한 게 별로 없는 것 같아 속이 좀 상하지만 그래도 코드가 무사히 잘 돌아가는 것만 봐도 마음이 놓인다. 그 과정에서 수많은 에러들을 만났고 속상해서 죽을 뻔도 했지만 그래도 무사히 마쳤다는 게 위로가 되었다. 회사 마치고 쪼끔씩 진행한 결과 아래와 같이 팀 노션에 기여를 할 수 있게 되었다😂 초록색이 나다...!

사실 이런것보다 내가 가장 잘 했다고 생각한 점은 크롤링 플로우 수정이였다. 기존에는 소스코드 4개를 순서대로 실행해야 끝나는 로직이었으나, 그 소스코드 2,3에서 중복으로 체크하는 코드가 포함되어있었고, 소스코드1번 앞에 새로운 로직이 들어가야해서 이건 좀 수정할 필요가 있겠다 싶어서 PO님께 말씀 드렸고, 그 결과 소스코드 2개를 실행하면 되도록 과정을 수정하였다.

이걸 수정하면서 로직은 쉽네. 금방 하겠지? 했지만 세상살이 언제나 쉽지 않다고 하지 않았는가. 넘겨주는 자료형을 맞추고 속도를 계산해보고 ray와 mysql을 달래가며 거의 3일을 썼던 것 같다.

그 과정에서 AI팀에서 전달받은 코드가 있었는데 이걸 또 연결하는 게 쉽지 않았다. 우선, ML을 공부해본적은 있으니 뭔진 알겠는데, AI 엔지니어분이 커스텀 하신 파라미터나 불용어 등을 넘겨주고 넘겨받는 것이 서툴렀다. 좀 더 좋은 코드 개선 방향이 있을 것 같아 이 부분도 리팩토링 할 때 꼭!! 신경써서 잘 해내고 싶다.



👿 에러 조사버리기

인간은 멍청해서 같은 실수를 반복한다고 하지 않는가. 나는 두 번 반복하는 게 제일 싫다. 그래서 어떤 에러들을 만났었는지 적어두려 한다.

1. MySQL 적재

진짜 NoSQL 쓰다가 RDB 쓰니까 처음에 오잉....? 하는 점이 많았다. 그 중에 가장 귀찮았던게 character setting 이었다. 나는 회사컴, 집 데스크탑, 오며가며 m1을 쓰는데 그 때마다 DB 설정을 맞춰줘야 하는 게 넘모 귀찮았다. 그래서 이젠 처음부터 모든 세팅을 맞추고 시작하는 게 좋다는 걸 깨달았다. 우리 데이터 특성상 이모지가 많아서 일반적인 utf-8로는 누락되는 데이터가 너무 많았다. 그래서 charset을 utf8bm으로 설정하기로 했다. 이거 제대로 안 하면 계속 뻑나서 기껏 크롤링 한 데이터 DB에 못 넣어서 다 날아간다.

우선 my.ini에서 아래와 같이 추가해준다.

[mysqld]
character-set-server=utf8mb4
collation-server=utf8mb4_unicode_ci
init_connect=SET collation_connection=utf8mb4_unicode_ci
init_connect=SET NAMES utf8mb4

[client]
port=3306
plugin-dir=C:/Program Files/MariaDB 10.4/lib/plugin
default-character-set=utf8mb4

[mysql]
default-character-set=utf8mb4

[mysqldump]
default-character-set=utf8mb4

그 다음 만약을 대비해 mysql 콘솔에서 아래와 같이 입력한다.

mysql> ALTER DATABASE [DB명] CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci;             
mysql> ALTER TABLE [테이블명] CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

그리고 마지막으로 mysql과 연결할 때 아래와 같이 입력한다.

db = pymysql.connect(
                      user=[유저명],                  
                      passwd=[비밀번호],                         
                      host=[DB주소],                      
                      db=[DB명], 
                      charset='utf8mb4'
                    )

이렇게 하면 이모지가 잔뜩 들어간 데이터도 무리없이 넣을 수 있다.


2. MySQL bulk insert

ES로 넣을 때는 ES에서 bulk_insert를 제공했기 때문에 큰 문제가 아니었지만 MySQL은 bulk insert를 특별히 제공하고 있지는 않았다. 그래서 찾아보니 아래와 같은 방법들이 있었다.

  • execute 대신 executemany 활용하기
  • 데이터프레임으로 만들어 sqlalchemy에서 to_sql 활용하기
  • INSERT INTO [테이블명] VALUES (값1, 값2, 값3), (값4, 값5, 값6) ...

처음에는 3번을 시도하고 싶었다. 그러나 가장 빠르게 구현 가능한 1번 방법을 채택해서 구현하였다. 결과적으로 조금 아쉬워서 나중에 이 블로그를 참고로 리팩토링을 하고 싶다.

# 현재 코드
while len(results):
        done_id, results = ray.wait(results)
        result = ray.get(done_id[0])
        try :
            cur.executemany([테이블1], result[0]) # 중첩 리스트로 된 데이터를 insert 할 때 
            cur.executemany([테이블2], result[1]) # 적용해주었다. 
            cur.execute([테이블3], result[2])
            cur.execute([테이블4], result[3])
            db.commit()
            inserted += 1
        except :
            print("Nothing to save to db")
            not_inserted += 1
            pass

3. Pymysql과 SQLAlchemy

파이썬으로 mysql에 접속하기 위해 가장 많이 사용되는 것이 Pymysql일 것이다. 그러나 난 이번에 크롤링 한 데이터 중에서 중복제거를 해 줄 것들이 있었기 때문에 데이터프레임을 썼는데, 이 데이터프레임과 mysql을 찰떡마냥 붙여주는 게 SQLAlchemy라는 것을 알고 바로 적용 시켜주었다. 데이터프레임 다루는 건 맨날 까먹어서 구글링 하면서 적용하느라 시간 꽤나 썼다🤣

total = []
while len(results):
        done_id, results = ray.wait(results)
        try :
            result = ray.get(done_id[0])
            total = pd.concat((total, pd.DataFrame(result)))
        except ray.exceptions.RayTaskError as e :
            print(e)
    
    # 중복제거
    total.columns = [컬럼명1, 컬럼명2]
    total.reset_index(drop=True, inplace=True)
    print(f"중복제거 전 : {len(total)}")
    total.drop_duplicates(keep='first', ignore_index = True, inplace=True)
    print(f"중복제거 후 : {len(total)}")
    ray.shutdown()

    # db 연결 
    db_connection_str = 'mysql+pymysql://[db유저이름]:[db password]@[host address]:[port]/[db name]?charset=utf8mb4'
    db_connection = create_engine(db_connection_str)
    conn = db_connection.connect()

    total.to_sql(name=[테이블명], con=db_connection, if_exists='append', index=False)
    conn.close()

4. 에러 파티

크롤링을 하다보니 같은 페이지여도 네트워크의 속도나 채널마다 설정이 다른지 자꾸 크롤링 중간마다 에러가 나서 멈추는 것이다. 가뜩이나 주말에 집 컴으로(cpu 6개) 돌리느라 속도 때문에 속 터지는데 그것까지 말썽이니 너무 짜증나서 try, except문 공부한다셈치고 많은 부분에 예외처리를 손봤다. 이것도 더 좋은 방법이 있을텐데 네트워크 쪽을 좀 더 공부한 뒤 리팩토링 하고 싶다.



▶ 다음 스텝

1. Docker 도입

정확하진 않지만 현재 개발환경이 mac도 있고 windows도 있고 linux도 있고 다들 제각각이며, 나 또한 mac과 windows를 번갈아 쓰고 있기 때문에 조만간 모든 코드들을 도커에 올리지 않을까 싶다. Docker....꼭 배우고 싶지만 꼭 미루고 싶은 스킬이었는데 이렇게 만난다니 반갑다^_^

2. ES 도입

현재는 AWS RDS(MySQL)에 데이터를 모두 저장하고 있지만, 검색엔진은 ES로 구축한다고 한다. 이건 이미 올라온 데이터들만 이관하는 거라 그렇게 어려울 것 같진 않다. 이미 그 전에 ES 서버도 만들어놨고 매핑도 해놨기 때문에 좀 수월할 것 같다.

3. 크롤링 최적화

PO님의 얘기를 들어보면 대충 6개월 이상은 버틸 수 있는 투자금이 있는 것 같은데 역시 요새는 서버비용 계산을 잘 해야 살아남을 수 있지 않은가. 우리 팀에서는 현재 4~5개의 서버를 구축할 예정인데 그 중 가장 많은 리소스를 잡아먹을 게 크롤링 과정이라 크롤링을 최적화 시킬 필요가 있다. 또한, 비용의 문제 전에 내 속이 터진다. 아무리 selenium을 쓴다지만 좀 더 효율적인 방법을 찾아봐야겠다. ray(병렬처리) 개선 혹은 DB INSERT에서 뭔가 답을 찾아내고 싶다.

4. Redis 구축

아마 백엔드분이 구축하실 것 같은데 우리 로직안에서 간단하게 DB 안에 있는 데이터들에서 중복값만 제거해서 어떠한 단어집을 만들어야 하는 과정이 있는데 그 때 인메모리 데이터 구조를 가진 Redis를 사용할 것이라고 했다.

5. 트랜잭션 관리

개인적으로 도전해보고 싶다. 현재는 DB에 접근하는 게 백엔드와 나밖에 없어서 상관이 없지만 다른 데이터 엔지니어분과 AI팀에서도 접속할 일이 생길 것이기 때문에 DB 설정을 공부한 뒤 적용하고 싶다.



🤐 3월이라고...?

어느덧 2023년이 밝은지 2개월이 지났다고 했다. 오늘 아침 출근 전 강아지를 산책시키다가 문득 주위를 둘러보니 학생들이 바글바글 하더라. 그래서 왜지...? 하다가 "아...!!오늘 3월 2일이구나!" 하고 무릎을 탁 쳤다.

근데 뭘 했다고 3월? 이라는 생각이 스쳤다. 어느덧 회사에 들어온지는 3개월이 되었지만, 정작 만진 건 django밖에 없었고 본사와 총책임자가 다른 지역에 있으니 회사에서의 업무의 진행이 굉장히 더뎠다. 중간에 앱에서 에러라도 나는 날엔 총책임자와 백엔드팀이 거기에만 몰두하고, 모델 하나 만들라는 지시가 떨어지면 인공지능팀이 전부 빠져버리는 그런 상황이었다. 그래서 데이터팀이 큰 뿌리가 돼서 중심을 잘 잡으라는 말과는 달리 실제로 업무 지시는 적었고, 나의 답답함은 늘어만 갔다. 다른 주니어들은 일이 엄청 바쁘거나 작은 코드라도 하나씩 뜯어볼 기회도 많아보였고, SI에 간 친구들도 힘들다 힘들다 하지만 결국 코드를 보고 파이프라인을 짜는 일은 하는 것 처럼 보였다. 그 중에 나만 동동 떠다니는 오리마냥 유유자적했다🦢.

주니어가 이렇게 안 바빠도 되나 싶어서 업무시간에 일을 벌려서 일을 했다. 이것도 짜보고 저것도 짜보고. 하지만 결국 누군가에 의해 뒤집어지기 일쑤였고 힘들던 찰나 프로젝트에서 드디어 나의 파트가 끝을 보였다. 그마저도 없었으면 어떻게 했을까 싶다.

업무적으로 재미가 없다가도 프로젝트에서 다시 재미를 느끼는 걸 보면 개발자나 데이터 쪽이 안 맞는 건 아닌 것 같다. 다만, 첫 회사이고 내가 개인적으로 부족한 점이 많다보니 시행착오를 겪고 있는 거라고 생각한다. 억울하고 열받고 짜증나지만 꼬우면 대기업 갈 만한 실력이면 된다.

위에서도 적었지만 사실 크롤링을 하고 DB에 넣는 과정에서 많은 에러랑 싸우면서 HP가 많이 깎여있다. 그래서 이번주까지는 좀 리프레시 하는 시간을 가지고 다음주부터 다시 새출발 할 생각이다.

profile
완료주의

0개의 댓글