ProtoBuf 성능 테스트

3 minute read

ProtoBuf 성능 테스트

지난 포스팅에서 ProtoBuf이 무엇이며 특징에 대해서 학습했었다. 특징에서 전송 속도를 향상 시킨다 하였는데, 얼마나 빠른지 직접 눈으로 확인하기 위해 포스팅을 작성한다.

테스트 시나리오

우선 Student 및 Course 관련 도메인을 ProtoBuf와 일반 Pojo로 생성해서 각각 1만건 정도의 데이터를 생성한다. 이렇게 생성한 데이터를 Test Unit에서 천건, 이천건 씩 늘려가면서 성능 테스트를 해본다.

Trainning.proto 정의하기

우선 ProtoBuf를 사용해서 메시지를 전달하기 위해서 Trainning.proto를 정의 해준다.

syntax = "proto3";

package cwh;

option java_package = "com.cwh.protobuf";
option java_outer_classname="Trainning";

message Courses {
    repeated Course courses = 1;
}

message Course {
    int32 id = 1;
    string course_name = 2;
    repeated Student student = 3;
}
message Student {
    int32 id = 1;
    string first_name = 2;
    string last_name = 3;
    string email = 4;
    repeated PhoneNumber phone = 5;
    message PhoneNumber {
        string number = 1;
        PhoneType type = 2;
    }
    enum PhoneType {
        MOBILE = 0;
        LANDLINE = 1;
    }
}

Trainning.proto에서는 수업과 학생,폰번호,폰타입에 대해서 정의를 하였다. 이렇게 정의한 뒤, ${PROJECT_LOCATION_PATH}/src/main 파일로 접근 한 뒤,

protoc –java_out=java resources/Trainning.proto

명령어를 사용하면 컴파일 된 후 Trainning.java 가 생성된다. 이전 포스팅에선 본 그대로이다.

Trainning.proto에 정의한 message 및 enum을 Class 및 enum으로 정의하기

Trainning.proto에는 Courses와 Course, Student, PhoneNumber 4개의 message와 1개인 PhoneType enum이 존재한다. 해당 message와 enum을 CoursesPojo, CoursePojo, StudentPojo, PhoneNumberPojo class와 PhoneTypePojo enum으로 생성한다.

Application 정의

Application 클래스가 main함수가 존재하며, 데이터를 생성하는 부분이다.
우선 proto를 이용한 데이터 생성 부분을 구현한다.

 @Bean
public CourseRepository createTestCourses() {
    Map<Integer, Course> courses = new HashMap<>();

    for(int i=0; i<10000; i++) {
            Course course = Course.newBuilder().setId(i).setCourseName("REST with Spring" + i).addAllStudent(createTestStudents()).build();
            courses.put(course.getId(), course);
    }

    return new CourseRepository(courses);
}

private List<Student> createTestStudents() {
    PhoneNumber phone1 = createPhone("123456", PhoneType.MOBILE);
    Student student1 = createStudent(1, "조", "우현", "aq3aq4@gmail.com", Arrays.asList(phone1));

    PhoneNumber phone2 = createPhone("234567", PhoneType.LANDLINE);
    Student student2 = createStudent(2, "아", "이언맨", "ironman@gmail.com", Arrays.asList(phone2));

    PhoneNumber phone3_1 = createPhone("345678", PhoneType.MOBILE);
    PhoneNumber phone3_2 = createPhone("456789", PhoneType.LANDLINE);
    Student student3 = createStudent(3, "토", "", "tor@gmail.com", Arrays.asList(phone3_1, phone3_2));

    return Arrays.asList(student1, student2, student3);
}

private Student createStudent(int id, String firstName, String lastName, String email, List<PhoneNumber> phones) {
    return Student.newBuilder().setId(id).setFirstName(firstName).setLastName(lastName).setEmail(email).addAllPhone(phones).build();
}

private PhoneNumber createPhone(String number, PhoneType type) {
    return PhoneNumber.newBuilder().setNumber(number).setType(type).build();
}

다음으로 Pojo의 클래스를 사용해서 데이터를 만든다.

 @Bean 
public CourseRepository2 createTestCourse2() {
        Map<Integer, CoursePojo> courses = new HashMap<>();
        for(int i=0; i<10000; i++) {
            CoursePojo course = new CoursePojo();
            course.setId(i);
            course.setCourseName("REST with Spring" + i);
            course.setStudent(createTest2Student());
            
            courses.put(course.getId(), course);
    }

    return new CourseRepository2(courses);
}

private List<StudentPojo> createTest2Student() {
        
        PhoneNumberPojo phone1 = createPhone2("123456", PhoneTypePojo.MOBILE);
        StudentPojo student1 = createStudent2(1, "조", "우현", "aq3aq4@gmail.com", Arrays.asList(phone1));
        
        PhoneNumberPojo phone2 = createPhone2("234567", PhoneTypePojo.LANDLINE);
    StudentPojo student2 = createStudent2(2, "아", "이언맨", "ironman@gmail.com", Arrays.asList(phone2));
    
    PhoneNumberPojo phone3_1 = createPhone2("345678", PhoneTypePojo.MOBILE);
    PhoneNumberPojo phone3_2 = createPhone2("456789", PhoneTypePojo.LANDLINE);
    StudentPojo student3 = createStudent2(3, "토", "", "tor@gmail.com", Arrays.asList(phone3_1, phone3_2));
    
    return Arrays.asList(student1, student2, student3);
}

private StudentPojo createStudent2(int id, String firstName, String lastName, String email, List<PhoneNumberPojo> phones) {
        StudentPojo student = new StudentPojo();
        student.setId(id);
        student.setFirstName(firstName);
        student.setLastName(lastName);
        student.setPhone(phones);
        
        return student;
}

private PhoneNumberPojo createPhone2(String number, PhoneTypePojo type) {
        PhoneNumberPojo phoneNumber2 = new PhoneNumberPojo();
        phoneNumber2.setNumber(number);
        phoneNumber2.setType(type);
        return phoneNumber2;
}

proto를 사용하거나 pojo를 사용하거나 데이터를 만드는 형태는 비슷하다. 왜냐면 proto도 기본적으로 setter, getter를 사용해 데이터를 만들기 때문이다.

ApplicationTest 작성

@Test
public void protoBufFormatResponseTest() {
    @SuppressWarnings("unused")
    ResponseEntity<Courses> courseResponseEntity = protoBufRestTemplate.getForEntity(getUrl(), Courses.class);
}

@Test
public void jsonFormatResponseTest() {
    HttpHeaders header = new HttpHeaders();
    header.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
    HttpEntity<String> entity = new HttpEntity<>(header);
    
    @SuppressWarnings("unused")
    ResponseEntity<Object> course = restTemplate.exchange(getUrl2(), HttpMethod.GET, entity, Object.class);
}

해당 테스트에서는 응답결과가 오는 시간만 체크 하면 되므로 테스트 케이스에서는 restTemplate을 이용해서 호출에 대한 응답만 가져오는 처리만 하였다.

테스트 환경 구성 및 결과

테스트 진행을 위해 외부 서버에 해당 포스팅 내용을 빌드 후 배포한 후, 나의 로컬에서 호출하는 방식으로 테스트 환경을 구성 하였다. 테스트는 1000건~3000건으로 나눠 건당 5회 호출의 평균 시간을 기록하였다.

데이터건수 json 사용 시 평균 응답 시간 protoBuf 사용 시 평균 응답 시간
1000건 0.288 0.112
2000건 0.306 0.157
3000건 0.378 0.215

테스트 결과를 보면 protoBuf를 사용 했을 경우의 응답 결과 도달 시간이 약 1.7배 정도 빠른 것을 확인 할 수 있다.

결론

테스트를 진행하고 결과를 봤을 경우 protoBuf의 성능이 json보다 성능이 좋다는 것을 확인 할 수 있었다. 하지만 약 1.7배 정도의 퍼포먼스 향상은 성에 차지 않는다. 그래서 gRPC를 사용해 전송 과정 자체에서도 이점을 볼 수 있도록 알아 볼 것이다. 이번 포스팅에서 작성한 소스는 여기서 확인 할 수 있다.

참고 자료

https://www.baeldung.com/spring-rest-api-with-protocol-buffers

Comments