[입 개발] 오픈 소스로 문서의 텍스트 추출 서비스 만들기 #2

해당 포스트는 KT olleh ucloud biz의 지원을 받고 작성되었습니다.

지난주에도 살짝 언급하기는 했지만, 지난 번의 아키텍처는 사실 분산이 어려운 구조입니다. 하나의 서버안에 웹서버와 Celery가 함께 있어야만 데이터에 접근이 가능합니다. 즉 하나의 서버에서 남는 자원을 활용하는 형태가 되겠죠. 다음과 같은 형태입니다.

text_extraction_2_1

그러나 이러라고 만들어진 Celery가 아닌데, 과연 그렇다면 어떤 방법이 있을까요?

그런데 이런 고민이 사실 확장 가능한 시스템을 고민할 때 항상 하게 되는 고민입니다. 특히 현재의 일반적인 클라우드 서비스의 구조가 뒷단의 데이터 스토리지 영역에서 데이터를 동기화거나 저장해두고, 앞단에서는 공통적으로 이 데이터를 사용하자라는 개념으로 접근하게 됩니다. 즉, 다음과 같은 구조가 됩니다.

text_extraction_2_2

그런 연고로, 확장성을 위해서는 아래의 Data Storage Layer도 분산 시스템이 되어야 합니다. 그럼 다시 문제를 한정시켜서, 문자 추출 서비스를 할 때 여러 서버가 동시에 접근할 수 있는 파일 서비스가 필요합니다.

이럴 때 쉽게 사용할 수 있는것이 Object Store 이고 여기에 가장 유명한 것이 아마존의 S3입니다. 수 많은 기업들이 아마존 S3를 이용해서 서비스를 만드는 경우도 많습니다. 대표적인 케이스가 Dropbox 입니다. 물론, 여기서는 싱크를 잘 맞추는 게 더 어렵겠지만, 파일의 관리를 S3를 통해서 간소화 시켰습니다. 더 핵심에 집중하게 된거죠.

여기서도 마찬가지입니다. 텍스트 추출과 확장성이 목표라면, 이런 서비스를 안 쓸 이유가 없겠죠. KT ucloud 에서 제공하는 ucloud storage 는 기본적으로 openstack의 swift 서비스를 사용중입니다.

사용을 위해서는 다음과 같이 해당 페이지에서 API Key를 받아야 사용이 가능합니다.

text_extraction_2_3

그렇다면 이제 어떻게 아키텍처가 변경이 되게 될까요? 웹 서버는 swift나 S3 같은 곳에 등록된 파일을 저장하게 됩니다. 그리고 그쪽 파일 uri를 celery에 전달하게 되면, celery는 자신의 local이 아닌 분산 오브젝트 스토어에서 해당 파일을 읽고 이를 읽어서 처리하게 되는 것입니다.

다음과 같이 중간의 Data Storage가 하나라면 SPOF가 되지만, 분산 파일 시스템이라면, 장애에 안정적이고 확장성도 높아지는 아키텍처가 구성이 되는 것입니다.

text_extraction_2_4

물론, 장단점이 다 존재합니다. 추가적인 레이어를 사용하기 위해서 각각 네트웍을 통해서 파일을 쓰고 읽는 과정이 필요합니다. 이제 여기서 고민할 것은 각각의 비용이 얼마나 쌀지 생각해서 비용이 더 싼 곳으로 결정하는 지혜(?)가 필요합니다.(이건 뭐, 알아서 ㅎㅎㅎ)

여기에서 추가로 더 확장을 한다고 하면, 각각의 클라우드 서비스가 제공하는 API를 이용해서 자동으로 장비를 부하에 따라서 확장을 하는 것도 가능합니다.

ucloud storage 나 S3 말고도, 직접 ceph 나 grusterfs를 설치해서 직접 관리하는 방법도 있습니다.

여러가지 말들을 주절주절 적었지만, 결론은, 자신의 요구사항에 맞는 형태에서 가장 효과적인 아키텍처를 고민해야 한다는 것입니다. 특히 클라우드의 특성을 잘 알아야 안정적인 서비스를 구축할 수 있습니다. 특히 클라우드로 넘어가면서 속도보다는 안정성/확장성을 더 높이는 방향으로 설계를 하는 것이 클라우드 환경에서는 유리합니다.

ps. python으로 swift 관련 작업을 한다면, 다음 문서를 참고해서 swift-tool을 사용해보는 것도 좋습니다.(http://developer.ucloudbiz.olleh.com/blog/swift/ucloud-storage—Swift-Tool/)

[입 개발] 오픈 소스로 문서의 텍스트 추출 서비스 만들기 #1

해당 포스트는 KT Olleh Ucloud Biz의 지원을 받고 작성되었습니다.

갑자기 Apache Tika를 보다가 문서에서의 텍스트 추출 서비스를 간단히 만들 수 있겠다라는 생각이 들어서 시도를 해보게 되었습니다.  이에 대한 개발을 위해 사용된 기술들은 Python, Python Flask, Apache Tika(java), Celery(Python), Redis(c) 등입니다. 파일을 등록하면 여기서 텍스트만 추출해서 보여주는 것입니다.  간단한 UI는 다음과 같습니다.

text_extraction_1_1

예전 같았다면 문서에 대한 텍스트 변환 코드를 다 작성했어야 했지만(실제로 5~8년 전에는 문서에서 텍스트 추출을 직접 구현했던 경험이… 그때는 한글 포맷도 없던 시절에 T.T) 이제는 실제로 이런 것들을 해주는 오픈소스들이 대부분 존재합니다. 여기서 사용하는 Apache Tika의 경우도 그런 라이브러리 중에 하나이고, 꽤 많은 문서를 지원해주고 있습니다.

참고로 모든 소스는 https://github.com/charsyam/celery_with_tika/ 에서 받으실 수 있습니다.

 

Apache Tika는 http://tika.apache.org 에서 받을 수 있고, 라이브러리 형태로도 사용할 수 있고, tika-app 을 이용하면 그냥 커맨드 쉘 형태로도 사용이 가능합니다.
Python Flask 는 Python에서 사용할 수 있는 web framework 입니다.(django가 더 유명하지만, 전 웬지 반창고파라 ㅋㅋㅋ, 이유는 없습니다. 그냥 Flask를 먼저 배워서 ㅋㅋ)
Python Celery 는 Python 기반의 분산 작업 처리 솔루션입니다. 사실 여기서 핵심은 Tika(문서 추출), Celery(분산 작업) 입니다.

왜냐하면, 궁극적인 이 프로젝트의 목적은 text extraction as a service 형태인데, 최대한 간단하게 작업 자체를 확장하거나, 줄일 수 잇는게 목적입니다. 그런데 이런 작업을 잘 만들려면
많은 노력이 들지만, Celery를 통해서 누워서 떡먹기식으로 해결이 가능합니다. 텍스트 추출은 tika가 처리를 해주고요.

그래서 시스템의 간단한 아키텍처는 다음과 같습니다.
text_extraction_1_2

구조를 보면 매우 단순합니다. 하지만, 조금만 생각해보면, 아주 확장에 자유로운 구조가 될 수 있습니다.
text_extraction_1_3

여기서 다시 웹서버만 늘리면 다음 구조가 되게 됩니다.
text_extraction_1_4

굉장히 확장이 쉬워보입니다.(물론, 여기에는 함정이 있습니다. 마지막 아키텍처 처럼 될려면, 그 후방이 분산 파일 시스템처럼 구성이 되어야 합니다. 즉, 웹서버에서는 파일을 분산 파일 시스템에 저장하고, Celery에서 이를 읽어서 처리하는 방법이 되어야 합니다. 그러나 그림은 더 이상은 생략)

최초에 이를 만들때 생각한 것은 최대한 쉽게 만들 수 있도록 하자였습니다. 그래서 결론적으로 tika를 이용하는 방법은 os.system 이나 subprocess를 이용해서 그냥 커맨드를 실행시키고 이 결과를 이용해서 보여주는 방식입니다.(중간에 pyjnius 등의 이야기도 하겠지만…)

그리고 이를 구현하는데 든 코드는 몇줄 되지 않습니다. 일단 웹 페이지를 보여주는 python flask 관련 코드는 다음과 같습니다.

@app.route('/', methods=['GET', 'POST'])
def index():
    if request.method == 'POST':
        file = request.files['file']
        if file:
            workid = id_generator();
            filename = secure_filename(file.filename)
            file.save(os.path.join(app.config['UPLOAD_FOLDER'], workid))
            result = textextractor.delay(workid)
            return render_template('result.html',task_id=result.task_id)
        else:
            return render_template('index.html')
    else:
        return render_template('index.html')

if __name__ == '__main__':
    app.run(host="0.0.0.0", debug=True)

Celery 를 사용하기 때문에(그것도 비동기로) celery 큐에 집어넣는 작업만 하면 끝납니다.
이 때, KT Ucloud를 사용하신다면 Flask가 기본적으로 5000번 포트를 이용하는데(지정가능)
Port Forwarding에서 다음과 같이 5000번 포트를 추가하셔야 정상적으로 서비스가 가능합니다.

text_extraction_1_5

그리고 실제 Celery 를 이용한 부분은 다음과 같습니다.

@celery.task
def textextractor(workid):
    filename = "%s/%s"%(Config.UPLOAD_FOLDER, workid)
    builds = "%s/%s"%(Config.BUILD_FOLDER, workid)
    status = 0

    command = "/usr/bin/java -jar %s -t %s"%(Config.TIKA_CLASSPATH,filename)

    args = shlex.split(command)
    try:
        contents = subprocess.Popen(args, stdout=subprocess.PIPE).communicate()[0]
    except:
        contents = "tika error"
        status = -1

    os.remove(filename)
    return (contents, status)

실제로 tika를 실행시켜주고 그 결과를 가져오는 것이기 때문에, 아주 간단합니다. 다만, 눈에 보이는 버그도 숨어있습니다.(귀찮아서 아직 처리를 못한…)

즉, tika만 익숙하면 구현하는데는 거의 1시간도 채 걸리지 않습니다. 그렇다면, 그렇게 쉽게 걸릴일을 왜 이렇게 오래걸렸냐라고 물어보면, 딴 삽질들에 대해서 얘기를 해야합니다. 비동기 작업(Celery가 해주지만, 결과를 polling 하는 방법), 좀 더 깔끔하게 하기 위한 방법, 뒤에 hwp를 추가하기 위한 간단한 삽질등은 다음번에 올리도록 하겠습니다. 다만, 위에 코드만으로도 아주 간단히 -_- 문서에서의 text 추출을 만들 수 있다는게, 오픈소스의 대단함이지 않을가 싶습니다.