MessagePack 라이브러리 사용해보기

MessagePack은 JSON의 바이너리 프로토콜입니다. 구글의 ProtoBuf 와 함께 대중적으로 사용되며 특히 Unity에서 게임 데이터를 직렬화 하는데 대중적으로 쓰입니다. 대표적인 MessagePack 라이브러리로는 neuecc/MessagePack-CSharp 입니다.

MessagePack-CSahrp에서 제공하는 퍼포먼스 비교표를 보면 protobuf-net 보다 5배 이상 빠름을 알 수 있습니다. 게임에서 적극적으로 사용하는 직렬화 라이브러리인 만큼 상용 프로그램에 적용하는데 부족함이 없습니다.

본 시간에는 MessagePack-CSharp를 설치하고 사용법을 간략히 숙지해서 라이브러리를 쓸 수 있도록 진행해봅시다.

MessagePack-Chsarp 설치

NuGet에서 MessagePackMessagePackAnalyzer를 설치합니다.

image

설치하는 패키 중 MessagePackAnalyzerMessagePack관련 속성이 제대로 부여되지 않았을 경우 컴파일 오류가 발생하도록 Analyzer 기능이 추가됩니다.

간단히 코딩 해보기

여러분이 바로 프로젝트에 MessagePack을 활용해볼 수 있게 MessagePack의 기본 동작을 간단히 확인해보겠습니다. (코드는 .NET 6 Preview 7에서 테스트 하였습니다.)

using MessagePack;

// 더미 정보 생성
var user1 = new UserInfo
{
    Id = "user1",
    Name = "사용자1",
    DummyValue = 312
};

// 직렬화. 반환값은 바이트 배열
var binUser1 = MessagePackSerializer.Serialize(user1);
// 직렬화된 데이터를 JSON으로 확인할 수 있음
var jsonUser1 = MessagePackSerializer.ConvertToJson(binUser1);
Console.WriteLine(jsonUser1);
// 복호화 한 다음 출력 (feat. 클래스 대신 레코드를 이용하면 출력이 이쁘게 나옴)
var newUser1 = MessagePackSerializer.Deserialize<UserInfo>(binUser1);
Console.WriteLine(newUser1);

[MessagePackObject]
public record UserInfo
{
    [Key(0)]
    public string? Id { get; set; }
    [Key(1)]
    public string? Name { get; set; }

    [IgnoreMember]
    public int DummyValue { get; set; }
}
  • 직렬화 대상은 [MessagePackObject] Attribute를 적용해야 합니다. 그리고 대상은 반드시 public이어야 합니다.
  • 직렬화 속성은 키를 부여해야 합니다. 자동으로 키를 부여하는 옵션도 있지만 속성명으로 부여되므로 추천하지 않습니다. 키의 인덱스는 직렬화된 정보의 위치가 됩니다(JSON 출력 결과 참조)
  • 속성 중 직렬화 대상이 아니어야 하는 것은 [IgnoreMember] Attribute를 부여합니다.

출력

["user1","사용자1"]
UserInfo { Id = user1, Name = 사용자1, DummyValue = 0 }

상속 및 사용자유형 코딩하기

var binUser2 = MessagePackSerializer.Serialize(detailUser);
var jsonUser2 = MessagePackSerializer.ConvertToJson(binUser2);
Console.WriteLine(jsonUser2);
var newUser2 = MessagePackSerializer.Deserialize<UserDetailInfo>(binUser2);
Console.WriteLine(newUser2);

[MessagePackObject]
public record UserDetailInfo : UserInfo
{
    [Key(2)]
    public DateTime Birthday { get; set; }

    [Key(3)]
    public UserAuthInfo? Auth { get; set; }
}

[MessagePackObject]
public record UserAuthInfo
{
    [Key(0)]
    public string? Code { get; set; }
    [Key(1)]
    public AuthKind Auth { get; set; }
}

public enum AuthKind
{
    None,
    User,
    Admin
}

출력

["user2","사용자2","1978-08-23T00:00:00.0000000Z",["ADMIN",2]]
UserDetailInfo { Id = user2, Name = 사용자2, DummyValue = 0, Birthday = 1978-08-23 오전 12:00:00, Auth = UserAuthInfo { Code = ADMIN, Auth = Admin } }

상속시 주의해야 할 점은 Key가 부모의 속성과 인덱스가 중복되면 안된다는 점입니다. 속성이 계속 증가할 수 있어서 이 부분이 좀 까다로웠는데요, 저 같은 경우 (0, 1, 2, …) → (100, 101, 102, …) 이런식으로 키를 상속받을 때 백자리 정도로 띄워서 부여했는데, 키의 인덱스가 곧 배열의 인덱스이므로 비워진 위치에 null값이 자동으로 들어가면서 직렬화 데이터의 사이즈를 키우는 문제가 있었는데요, 현재로서는 다음처럼

            var lz4Options = MessagePackSerializerOptions.Standard.WithCompression(MessagePackCompression.Lz4BlockArray);
            var bin = MessagePackSerializer.Serialize(model, lz4Options);

압축 옵션을 줘서 해결하고 있습니다. 다른 좋은 방안을 알고계신 분은 댓글로 알려주시면 감사하겠습니다.

정리

간단히 MessagePack을 이용해 직렬화 하는 방법을 알아보았습니다. 이 외에도 Union Attribute로 abstract class에 대한 정확한 복호화도 지원합니다. MessagePack-CSharp에서 좀더 상세한 내용을 살펴보실 수 있습니다.

4개의 좋아요

Newtonsoft.Json 하고 비교된 밴치마크는 없군요… 처음에 .NET Core 3.1이 나오고 부터 Json.NET이 속도가 그렇게 빠르다고 해서 한 번 써보려고 시도했는데, 기존에 Newtonsoft.Json 처럼 쉽고 빠르게 되는 줄 알았는데 이것 저것 많이 설정해야 되더라구요 ㅠㅠ

고성능 .NET 프로그래밍 보면서도 느꼈지만 역시 편리함과 성능은 반비례…ㅠ

Json.NET이 아마 Newtonsoft.Json 일겁니다.

이정도면… 간단한 사용 수준이 아닌가 생각하는데… 아무래도 제가 글을 잘 못썼나 봅니다 T_T

1개의 좋아요

아… Json.NET 이 System.Text.Json 일 줄 알았는데 아니었군요. 저는 System.Text.Json이 NewtonSoft.Json에 비해 쓰기가 어렵다고 말씀드린 것이었습니다. System.Text.Json이 속도는 더 빠르겠지만, 그 때 당시 기억으로는 직렬화하는 것을 제가 정의해야했던 것으로 기억납니다. Newtonsoft.Json은 JsonConvert.SerializeObject 와 JsonConvert.DeserializeObject면 다 되다보니…

제 댓글이 말씀하신 MessagePack에 대한 내용은 아니었네요…제가 혼란을 드린 것 같습니다. 사용법은 Attribute를 붙여야하니 굳이 비교하면 Newtonsoft.Json보다는 손이 가지만 상당히 쉬운 사용법이라 생각합니다. 추가적으로는… json이 아닌 데이터도 직렬화, 역직렬화가 가능하니 이 부분을 본다면 Newtonsoft.Json보다 좋다고 생각합니다 ㅎㅎ

1개의 좋아요

이런. 제가 잘못봤군요^^;;;;;;;

1개의 좋아요