JSON Phaser 개발기

김현수·2022년 9월 20일
0

출판기행

목록 보기
5/8
post-thumbnail

9월 16일 당시의 코드

import os 
import sys         //os 함수 처음 써보네..

fd=open("./a.log")

for i in fd:
	print(fd.readline().strip())

9월 21일의 코드 (진짜 핵심부분만 발췌)

import time
import json
import copy
import pandas

f = open('C:/Users/rover0811/Desktop/숭실대 폴더/밸리언트데이터 인턴/Paper/사전 검증 데이터/사전 검증 데이터/a.log', 'r')
dict = {}

start_time = time.perf_counter()

keyClass = {}
for i in range(100):
    tempKey = "{}-{}".format(5*i, 4+5*i)
    keyClass[tempKey] = 0
key = list(keyClass)

for i in f:  # 2652823 개의 데이터
    line = f.readline().strip().rstrip('",').rstrip()
    if line.startswith("---------- A.LOG"):
        continue
    elif line == "":
        continue

    if line.startswith("----------"):
        tempJson = line.lstrip('---------- ').rstrip("\n")
        dict[tempJson] = copy.deepcopy(keyClass)
    tempNum = line.count(" ")
    tempIndex = tempNum//5
    dict[tempJson][key[tempIndex]] += 1

with open('./data.json', 'w') as hi:
    json.dump(dict, hi, ensure_ascii=False, indent=4)

df_result = pandas.read_json("data.json")

with pandas.ExcelWriter('output.xlsx', engine='openpyxl', mode='a') as writer:
    df_result.to_excel(writer, sheet_name='WORD DISTRIBUTION')

end_time = time.perf_counter()
print(f"time elapsed : {int(round((end_time - start_time) * 1000))}ms")

난 이번 짧은 스프린트를 통해 무엇을 배웠을까?

진짜 스트레스 받는 며칠이었다. 프로그램 자체의 난도는 높지는 않았지만, 만약 내가 틀릴 시에 사측에게 피해가 갈 수 있다는 점이 스트레스였다.

짧은 기간 동안 많은 것에 대한 정보를 찾아봤어야했었다. 이는 기존 프로그램에 하나의 피처를 넣어 최종 엑셀본에 어절 수를 카운트해서 추가하는 일이었기 때문이다. 수요일까지 정확성 검사 용역을 위해 빠르게 프로그램을 만들어야 했으며, 매일 대표님께 전화가 오고 추가 업무를 지시 받는 상황에서 많은 수업과 회의들을 감당해가면서 개발하는 것은 계획적으로 그날 하루를 설계하고 살아가는 나로서는 큰 고통이었다.

특히 개발 사항이 점점 많아지는 것이 고역이었는데, 그럼에도 불구하고 돌아보니 재미있고 많은 것을 배운 경험이라고 생각한다.

솔직히 말해 단순히 어절 수를 세는 알고리즘은 이 한 줄이다. 그러나 그 한 줄의 동작을 위해 JSON 파일을 예쁘게 깎고 그것을 저장할 공간을 생각하며 output으로 엑셀로 만들어야 하는 일은 참 다른 일이다.

물론 비동기 처리등으로 프로그램의 속도를 높일 수 있겠지만, 일단 급한 일이 아니라 미루어두었다.

WORD_NUM=input.strip().count(" ")+1

목차를 나눠 정리하자면 이러하다

  1. 디렉토리 구조
  2. glob에 대한 이해
  3. json schema
  4. 파이썬 dictionary에 대한 문법
  5. 파이썬 라이브러리
    1. openpyxl
    2. pandas
  6. 자료구조에 대한 고민
  7. 깊은 복사를 써야하는 이유
  8. 파일 처리
  9. 타인과의 협업 방식
  10. 성능 측정 방법

1. 디렉토리 구조

import os

os.walk()
glob_result = glob.glob(test_path+'/**/*', recursive=True)

glob의 성능이 훨씬 좋다는 사실을 깨달았다. 해당하는 함수는 그러하다.

2. glob에 대한 이해

[python package] glob

3. json schema

스키마는 DB의 구조와 제약 조건에 관한 전반적인 명세를 정의한 메타데이터의 집합입니다.다시 말하면 데이터베이스에서 자료의 구조, 자료의 표현 방법, 자료 간의 관계를 형식 언어로 정의한 구조입니다.

라고 말하는 주로 스켈레톤이라는 말로도 하고, 내가 생각하기엔 뼈대이다. DB의 개괄이라고도 할 수 있다. DB 구조이기도 하고, 주관적인 내 상황에서는 json의 구문적 정확성을 따지는 잣대이다.

4. 파이썬 dictionary에 대한 문법

개인적으로 직접적으로 인덱스 접근이 배열과 같은 방식으로 안되는 지 처음 깨닫게 되었다. 그럼에도 불구하고 그것을 우회할 방법은 항상 존재하니, 딕셔너리를 리스트화하면 그 리스트의 data는 딕셔너리의 key이다. 딕셔너리의 key값에 변동이 없는 한 리스트화 한 딕셔너리를 인덱스로
쓸 수 가 있다는 것이다. 왜 그래야만 했는가? 나는 하나의 json 파일에 있는 수많은 value값들의 어절 수의 분포를 셀 필요가 있었다. 따라서 0~5개 부터 495~500개까지의 분포도를 만들어야만 했다. 이에 적합한 자료구조는 딕셔너리였다. 그래서 딕셔너리를 사용했지만, 인덱스 접근(배열처럼 숫자로)이 되지 않아 골머리를 앓았다. 즉 JSON 파일(3만개) 마다 가지고 있는 문자열(보통 파일 당 600개가 넘음)의 어절 수 분포를 알아야할 필요가 있었다.

그래서 이중 딕셔너리가 필요했다. key는 json 파일의 이름, value는 분포도

분포도는 동시에 딕셔너리, key는 0-4같은 범위(0-4 ~ 495-500 총 100개의 key값이 필요)

그것이 무엇을 의미하는가? 조금씩 잘려있어 이해하기 쉽지 않을 수 있다.

keyClass = {} 
for i in range(100):
    tempKey = "{}-{}".format(5*i, 4+5*i)
    keyClass[tempKey] = 0
key = list(keyClass) # 리스트는 0-4, 5-9, 10-14,... 이런 식
dict[tempJson] = copy.deepcopy(keyClass)

이것을 이해했다면 다음도 충분히 이해할 수가 있다.

for i in f:  # 2652823의 행 파일 전체를 행마다 뒤지기!
    a = f.readline().strip().rstrip('",').rstrip() #파일 이쁘게 하기
    if a.startswith("---------- A.LOG"): #시간 복잡도 고려
        continue
    elif a == "": #예외 고려
        continue

    if a.startswith("----------"): #전체 파일구조를 보여주면 좋으련만 대외비라서 ㅠㅠ..
        tempJson = a.lstrip('---------- ').rstrip("\n") 
        dict[tempJson] = copy.deepcopy(keyClass)  #이게 JSON 파일 이름을 긁어온 것이다. 그리고 딕셔너리에 넣고 새로운 키클래스를 깊은 복사한다.
    tempNum = a.count(" ")
    tempIndex = tempNum//5  #여기서 배열의 5의 제수를 알고 싶은 것!!
    dict[tempJson][key[tempIndex]] += 1 # 리스트는 0-4, 5-9, 10-14,... 이런 식

#그러니까 딕셔너리에 꼭 해쉬(프로그래머가 문자열을 입력해주는 것)를 직접 안하고도 들어갈 수 있게끔 하는것

결론은 이런 느낌의 xlsx이다. xlsx로 결과를 빼는 것은 뒤에서 말하고자한다.

5. 파이썬 라이브러리

5.1 판다스

sqllite3 만지고 놀때 한 참쓰고 안만지다가 다시 만지게 되었다. 이번에는 json to xlsx를 쓰게 되었다.

with pandas.ExcelWriter('output.xlsx', engine='openpyxl', mode='a') as writer:
    df1.to_excel(writer, sheet_name='WORD DISTRIBUTION')

# output.xlsx에 추가하기 df1이라는 객체를 쓰기모드로 해서 sheetname은 Word Distribution으로

청크 사이즈를 두고, method를 multi로 두었더니 data 16만개를 처리하는 데, 30분에서 2초로 줄였다. method="multi" 매우 중요. 덕분에 회사에서 납품이 가능할 정도의 속도를 맞추게 되었다!!


import sqlite3;
import pandas as pd



temp_data = pd.read_csv('../양불예시.csv', chunksize=1000)

temp_data = list(temp_data)
conn = sqlite3.connect('testdata.db', isolation_level=None)

for index, df in enumerate (temp_data) :
    df.to_sql("test1",conn, if_exists="append" , method="multi")

cur = conn.cursor()

5.2 openpyxl

보통 100만개 이상의 json을 검증하고, 많은 데이터를 엑셀에 끼워 넣는 과정이다. 여기서 데이터가 많을 때의 오버헤드가 많이 발생한다는 사실을 깨달았다.

6. 자료구조에 대한 고민

실제로 자료구조를 무얼 써야하나 많은 고민을 한 프로젝트였다. 실제로 성능평가를 해가며 초를 줄일 수 있도록 많은 노력을 가했고, 결론적으로는 30초에서 4초 이내로 많이 줄이게 되었다. 이렇게 NOSQL과 SQL 환경을 왔다갔다 해보는 경험은 참 재미가 있다. 내가 백엔드 개발자로 가는 길에 아주 큰 도움이 될 것이다.

이번에는 이중 딕셔너리를 만들고 그것을 json으로 만들어버리는 형식을 취했다. 사실 json이 아웃풋으로 나와야하는 상황이었기도 하지만 이중 딕셔너리가 또한 매우 효율적인 방식이기도 했다. 딕셔너리와 json간의 매우 큰 유사도를 체감한 좋은 경험이었다.

7 .깊은 복사를 써야하는 이유

        dict[tempJson] = keyClass #옛날 코드
        dict[tempJson] = copy.deepcopy(keyClass) # 바꾼 코드

자료구조 시간에 배우기만 했던 얕은 복사와 깊은 복사를 체감해본 좋은 경험이다. 반복문 안에서 계속해서 같은 keyClass의 레퍼런스를 공유하다 보니 수백개의 json 파일들이 똑같은 keyClass를 사용하고 있는 것이었다. 대표님한테 한 번 깨지고 나서 이것을 고치게 되었다. 사실 구조체를 만들어야하나 싶었는데 여긴 파이썬이니 딕셔너리 쓰면 된다. 자바였다면 이런 실수 안했을 것 같긴 한데, 그래도 참 근본이 중요하다고 느낀 경험이었다.

8. 파일 처리

f = open('C:/Users/rover0811/Desktop/숭실대 폴더/밸리언트데이터 인턴/Paper/사전 검증 데이터/사전 검증 데이터/a.log', 'r')

with pandas.ExcelWriter('output.xlsx', engine='openpyxl', mode='a') as writer:

일단 지금은 절대경로로 저렇게 써두었지만 상대경로로도 작동하는 것을 확인했다. 절대경로는 프로그램 제작시 딱히 좋은 방법은 아니고 또한 저 슬래시를 하나하나 바꿔주어야해서 귀찮다. 왜 경로 복사를 뜨면 역슬래시가 나오는 지는 모르겠다. 아무튼 파일 처리도 별 어려울 것 없다는 사실을 깨달았다. 다만 파일 오프너를 개량할 필요는 있다. 그거 하나만 잘해둬도 백엔드 프로젝트에서 애먹을 필요가 없을 듯하다. 조금 더 숙지가 필요하다.

9. 타인과의 협업 방식

사실 대표님과의 협업이 통화와 카톡이어서 크게 쓸 건 없지만, 시니어의 중요성을 깨달았다. 물론 집에서 간단하게 한 거라 굳이 git으로 할 것까진 없지만 그래도 이제는 나는 git이 더 편하다. 문제는 내가 귀찮아서 커밋 푸쉬를 안해서 실상 집오면 잘 안한다는 게 단점이다. 자동 커밋 푸쉬를 해두게 하고 싶은데 방법이 무엇이 있으려나. 그냥 NAS 구축하는 것이 나으려나.. 팀뷰어를 설치해야하나..?

10. 성능 측정 방법

start_time = time.perf_counter()

end_time = time.perf_counter()
print(f"time elapsed : {int(round((end_time - start_time) * 1000))}ms")

이렇게 시작부분과 끝 부분에 카운터를 걸어두어서 성능 측정을 할 수 있다. 또한 로그를 남기는 함수도 배웠는데 아직은 잘 써먹지는 못하겠다. 데이터 265만개 기준 4.4초가 소요되었다.

profile
숭실대학교 소프트웨어학부생

1개의 댓글

comment-user-thumbnail
2022년 9월 21일

잘 읽고 갑니다~

답글 달기