서론

어느덧 3주차가 끝났다. 3주차에는 이것저것 많은 것을 하지 못했고, 채점 서버라는 친구와 많은 시간을 보냈다. 이 친구는 아주 말썽꾸러기였다. 사실 이 친구가 문제가 아니라 내가 문제였을지도 모른다. 채점 서버 이외에도 테스트 케이스 위치 변경이나 설계 오류를 잡는 등 의미 있는 시간을 보냈다.

본론

채점 서버

subprocess

파이썬에서 제공하는 모듈로 코드 내에서 프로세스를 실행시킬 수 있다.

subprocess.run(["javac", file_name], check=True, capture_output=True, text=True)

run() 메소드의 첫 번째 인자로 내가 실행시킬 프로세스의 명령어를 입력한다. 이후 부가적인 설정을 할 수 있다. 현재 여기에서는 다음과 같은 설정을 했다.

  • check : 비정상 종료가 발생했을 때 예외를 발생
  • capture_output : 출력값과 에러를 캡쳐한다. 이를 반환 값으로 받을 수 있다.
  • text : 출력을 문자열 형태로 반환

여기서 사용한 설정 이외에도 많은 설정이 존재한다. 이는 참고 문서 링크에서 확인할 수 있다.

Popen

subprocess에 포함된 클래스로 프로세스 실행 과정에서 더 세부적인 설정을 할 수 있다.

process = subprocess.Popen(
            ["java", "-Xms1024m", f"-Xmx{1024 + (memory_limit * 2 + 1)}m", class_name],
            stdin=subprocess.PIPE,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE
        )
        
process.communicate(input=test_input.encode())

여기서는 표준 입출력과 에러를 subprocess.PIPE 로 설정했다. 프로세스가 실행 결과를 쉘로 출력하기 위해 데이터를 보내는 통로에 파이프를 뚫어서 파이썬에서 프로세스의 입력 전달 및 출력을 확인할 수 있다.

communicate 메소드를 통해 실행 중인 프로세스에 input 데이터를 파이프를 통해 입력하고 있다. 이러한 로직을 통해 실행된 프로세스에 테스트 케이스를 입력했다.

문제점

컴파일 및 실행하는 과정은 해당 프로세스를 통해 충분히 진행할 수 있다. 또한, 입력값에 대한 출력값을 확인할 수 있기 때문에 정답 유무도 확인할 수 있다. 문제는 실행 시간 및 메모리 사용량을 확인해야 한다. 실행 시간의 경우 다음과 같이 구현했었다.

start_time = time.time()
actual_output, error = process.communicate(input = test_input.encode())
end_time = time.time()

정말 코드를 보면 쉽지 않음을 느낄 수 있다. 내가 봐도 쉽지 않다. 실행 시간을 이 정도 선에서 처리했다고 양보하지만, 메모리 사용량이 제일 문제였다.

메모리를 측정할 수 있는 방법은 뭐가 있을까 생각을 하고 구글링을 진행했다. psutil 이라는 모듈이 존재하지만 실시간으로 바로 얻을 수 있지 않아보였다.

그래서 생각한 것이 메모리를 측정하는 프로세스를 돌리는 것이다. psutil를 선택하지 않은 이유는 완전히 다 끝나야지 측정이 되는 것으로 확인을 했고, 적용했을 때 값이 잘 나오지 않았다. (구현한 사람의 문제가 크다)

생각한 로직은 다음과 같다.

  1. Popen으로 실행한 프로세스의 pid를 따고, psutil에서 해당 pid를 통해 메모리 사용량을 추적하는 프로세스를 돌린다.
  2. Popen으로 실행한 프로세스가 끝나면 psutil에서도 자동으로 추적이 끝나고 메모리 사용량을 가져온다.

라고 생각을 했지만, 구현이 문제인지 논리가 문제인지 그냥 내가 문제인지 정상적인 값이 안 나왔다. 이를 확인하기 위해서 백준에 있는 문제를 통해 테스트를 했다. 단순히 Hello World만 출력하면 되기 때문에 메모리 측면에서 크게 차이가 안 날것으로 예상을 했지만 10배 차이가 나는 것을 보고 잘못됐음을 확인했다.

오픈 소스

구현이 문제일까, 애초에 내 개발 환경에서 불가능한 작업인가 등 한 이틀 정도 생각하고 구현했지만 결과를 오픈 소스를 참고하자였다. 해당 오픈 소스를 개발하신 팀도 나와 동일한 도메인을 이용해서 프로젝트를 진행했다.

여기서 걸리는 문제는 환경이다. 나는 윈도우 환경에서 개발을 진행하기 때문에 fork() 같은 메소드가 작동하지 않았다. 그래서 우분투를 설치해서 개발을 진행했다. 우분투와 관련된 내용은 학기가 시작하고 시스템프로그래밍 강의를 수강할 때 작성해야겠다.

오픈 소스는 python만 제출을 할 수 있도록 구현을 했기 때문에 C++과 JAVA도 채점할 수 있도록 수정이 필요했다. 여기를 들어갔으면 안됐다.

왜 자바 채점이 안될까

다른 언어도 채점할 수 있도록 수정하는 것은 크게 오래 걸리지 않았다. subprocess에서 입력하는 명령어를 바꾸면 끝이다. 하지만 Java의 출력값이 나오지 않았다. 진짜 여기에서 거진 일주일을 사용한 것 같다. 오픈 소스를 다 뜯어보면서 사용한 메소드가 뭔지, 리눅스가 어떻게 돌아가고 시스템 콜에 대한 공부 등 시스템프로그래밍을 미리 수강한 느낌이였다. 이에 대한 내용도 강의를 수강할 때..

결과적으로는 메모리 제한의 문제였다.

def set_limits(time_limit: int, memory_limit: int) :
    time_limit = (time_limit + 999) // 1000
    resource.setrlimit(resource.RLIMIT_CPU, (time_limit, time_limit + 1))

    memory_limit = memory_limit * 1024 * 1024
    resource.setrlimit(resource.RLIMIT_AS, (memory_limit, memory_limit))

    output_limit = 64 * 1024 * 1024
    resource.setrlimit(resource.RLIMIT_FSIZE, (output_limit, output_limit))

해당 메소드는 실행 시간, 메모리 그리고 출력 제한을 설정하는 메소드이다. setrlimit 를 통해 사용되는 자원의 소프트 제한과 하드 제한을 걸 수 있다.

  • 소프트 제한 : 컴퓨터가 사전에 확보하는 제한이다.
  • 하드 제한 : 해당 용량을 넘어가면 강제 종료가 된다.

어디가 문제인지 확인하는 과정에서 해당 메소드 없이 테스트를 진행하니 결과값이 나왔다. 그리고 제한을 하나씩 지우니 메모리 제한에서 문제가 생겼음을 확인했다. 자바의 경우 JVM를 실행하기 때문에 설정한 메모리 보다 더 많이 잡아먹어서 실행하기 전에 메모리 제한에 걸려 프로세스가 강제 종료가 되는 것으로 예측을 했다. 이에 대한 분석도 한 번 해보는 것도 괜찮아 보인다.

근데 지금 생각해보면 메모리 초과가 되면 에러 메시지가 출력이 되야했는데.. 뭐가 문제일까? 아무도 모른다. 분석해봐야겠지 ? (지금은 시간이 없어요)

테스트 케이스

테스트 케이스에 대해서 많은 고민을 했다.

이와 관련해서 담당 버디님과 이야기를 나누고 진행 상황 공유에서도 방향성에 대한 이야기를 들었다. 당시 내렸던 결론은 메인 서버 DB에 테스트 케이스 테이블을 두고, 메인 서버와 채점 서버에서 이를 사용하는 것으로 결정을 했다. 이렇게 되면 메인 서버에서 테스트 케이스에 대한 업데이트를 바로 진행할 수 있고, 채점 서버의 경우 변경된 테스트 케이스를 바로 적용할 수 있다. 하지만 채점 서버를 구현하면서 다음과 같은 생각이 들었고, 채점 서버에 테스트 케이스를 두는 것으로 결정했다.

  • 문제 채점 횟수가 문제 생성과 문제 업데이트 횟수보다 압도적(?)으로 많을 것이라 예상
  • 그렇다면 테스트 케이스를 채점 서버에 두고, 바로바로 가져다 쓰면 되지 않을까
  • 별도의 DB를 두는 것이 아닌, .in , .out 텍스트 파일 형태로 저장
  • 테스트 케이스 생성 및 업데이트 api를 메인 서버에서 필요할 때 호출

    마무리

    프로젝트의 끝이 점점 보이기 시작한다. 이 프로젝트의 현재 완성도를 측정하면 한 50도 안될 것 같다. 뭔가 나사가 많이 빠진 프로젝트처럼 보인다. 그래도 약 한 달이라는 기간동안 크다면 크고 작다면 작다는 프로젝트를 진행했다는 것에 의의를 두고 싶고, 과정속에서 성장하는 내가 존재했다면 의미있었다고 생각이 든다. 마무리 잘 합시다.