Redis와 Python을 위한 직관적인 객체 매핑과 유연한 쿼리
오늘 Redis 개발자 여러분께 새로운 가능성을 열어줄 강력한 개발자 중심 라이브러리, Redis OM for Python을 소개합니다. 이 라이브러리는 객체 매핑(object mapping), 데이터 검증(data validation) 등 다양한 기능을 제공합니다.
이번 미리보기(preview) 버전의 Redis OM for Python은 선언적 모델(declarative model) 방식으로 데이터를 모델링할 수 있게 해줍니다. SQLAlchemy, Peewee, Django ORM 같은 ORM(객체 관계 매핑)에 익숙한 개발자라면 누구나 편안하게 적응할 수 있는 사용 경험을 제공합니다.
그뿐만이 아닙니다. Redis OM의 모든 모델은 Pydantic 모델이기도 하므로, Pydantic이 제공하는 견고하고 확장성 있는 데이터 검증 기능을 그대로 활용할 수 있습니다. 또한 Redis OM 모델은 Python 라이브러리가 Pydantic 모델을 요구하는 어디서든 동작합니다. 예를 들어 FastAPI와 함께 사용하면 API 엔드포인트의 자동 검증 및 API 문서 생성까지 손쉽게 구현할 수 있습니다.
개인적으로 특히 마음에 드는 기능은 바로 유연한(fluent) 쿼리 표현식과 보조 인덱스(secondary index) 지원입니다. 더 나아가 Redis OM for Python은 하나의 라이브러리 안에서 비동기(asyncio) 프로그래밍과 동기(synchronous) 프로그래밍을 모두 지원합니다. 훌륭한 기능들이 아직도 계속됩니다!
아래에서는 이 라이브러리를 어떻게 만들었는지, 그리고 핵심 기능 몇 가지의 이면에 숨겨진 이야기를 소개합니다. 코드를 직접 다뤄볼 준비가 되셨다면 시작하기 튜토리얼(getting started tutorial)을 확인해 보세요.
Redis를 위한 선언적 모델
일반적으로 개발자는 클라이언트 라이브러리를 통해 Redis 데이터 구조(예: Hash)를 생성하고, 이후 해당 구조에 대해 명령(command)을 실행하는 방식으로 Redis에 접근합니다.
많은 분들이 이 명령 기반 인터페이스를 좋아합니다. 관계형 데이터베이스에서 SQL 쿼리를 작성하는 것보다 훨씬 단순하기 때문입니다. 하지만 마지막으로 SQL을 직접 작성한 게 언제였나요? 최신 웹 프레임워크를 사용하는 개발자들은 대부분 SQL 대신 ORM, 특히 선언적 모델을 활용하는 추세입니다.
ORM이 주는 가장 큰 매력은 해결하려는 문제와 무관한 복잡성을 대거 제거해 준다는 점입니다. 우리는 Redis OM을 통해 드디어 Redis에서도 동일한 개발 경험을 누릴 수 있도록 만들었습니다.
Hash 또는 JSON, 선택은 자유롭게
Redis OM for Python은 모델을 구축할 때 사용할 수 있는 두 가지 기본 모델 클래스를 제공합니다. 바로 HashModel과 JsonModel입니다.
오픈소스 Redis 사용자는 HashModel을 사용해 데이터를 Hash 형태로 저장할 수 있습니다. 반면 RedisJSON 모듈이 설치된 환경이나 Redis Enterprise Cloud/Software를 사용하는 경우에는 JsonModel을 통해 데이터를 네이티브 JSON 객체로 저장할 수 있습니다. 두 클래스의 차이점은 뒤에서 더 자세히 다루겠습니다. 우선은 HashModel부터 살펴보겠습니다.
간결한 모델 정의
다음은 Customer 모델을 정의하고 이를 사용해 Redis에 데이터를 저장하는 Redis OM 코드 예제입니다.
이처럼 간결한 모델 정의만으로도 `get()`과 `save()` 같은 메서드를 즉시 사용할 수 있습니다. 내부적으로 이 메서드들은 Redis Hash 형태로 데이터를 관리합니다.
하지만 Redis OM의 능력은 여기서 끝나지 않습니다. 전역적으로 고유하며 정렬 가능한 기본 키(primary key)까지 자동으로 생성해 줍니다. 이 부분은 정말 유용하니 자세히 설명해 드리겠습니다.
전역 고유 기본 키
Redis OM은 모델 인스턴스마다 전역적으로 고유한 기본 키를 자동으로 생성합니다. 이 기본 키를 사용해 Redis에 모델 데이터를 저장하고 조회할 수 있습니다.
이 기본 키들은 전역 고유성이 보장될 뿐 아니라, Redis에 별도의 요청 없이 완전히 클라이언트 측에서 생성됩니다. 게다가 정렬 가능하고 컴팩트한 형태입니다. 이 모든 것이 가능한 비결은 ULID(Universally Unique Lexicographically Sortable Identifier) 규격 덕분입니다.
Redis OM의 기본 키는 python-ulid 라이브러리를 통해 제공되는 ULID입니다. ULID 규격에 대한 자세한 내용은 공식 문서에서 확인할 수 있습니다. 정말 멋진 기술입니다!
이런 영속성 기능 외에도 Pydantic을 통한 데이터 검증 기능까지 얻을 수 있습니다. 검증이 어떻게 동작하는지 살펴보겠습니다.
Pydantic을 활용한 데이터 검증
Redis와 관계형 데이터베이스의 중요한 차이점 중 하나는 Redis가 스키마를 강제하지 않는다는 것입니다. 즉, 문자열을 저장해 둔 값에 나중에 숫자를 덮어쓰는 것도 가능합니다. 이는 관계형 데이터베이스보다 유연하지만, 그만큼 애플리케이션이 데이터 검증의 책임을 직접 짊어져야 한다는 의미이기도 합니다.
우리는 개발자가 애플리케이션마다 검증 처리 방법을 고민해야 하는 상황이 되어선 안 된다고 생각했습니다. 그래서 모든 Redis OM 모델은 Pydantic 모델이기도 합니다. 따라서 모델의 타입 힌트(type hint)를 기반으로 한 Pydantic 검증이 자동으로 적용되고, 커스텀 밸리데이터(custom validator)를 포함한 표준 Pydantic 훅(hook)을 통해 검증 로직을 세부적으로 제어할 수 있습니다.
다음 예제 코드를 통해 검증이 실제로 어떻게 동작하는지 확인해 보세요.
Redis OM for Python이 영속성 메서드와 데이터 검증만 제공한다 해도 충분히 매력적이라고 생각합니다. 하지만 우리는 더 많은 복잡성까지 대신 처리해 주고 싶었고, 그러려면 ORM처럼 표현력 있는 쿼리 작성을 지원해야 했습니다. 다음으로 쿼리 기능이 어떻게 동작하는지 알아보겠습니다.
유연한 쿼리 표현식
ORM은 단순히 선언적 모델만 제공하지 않습니다. 기본 키가 아닌 속성을 기준으로 데이터를 조회할 수 있는 API도 함께 제공합니다. 예를 들어 특정 나이 이상의 고객을 찾거나, 특정 날짜 이전에 가입한 고객을 조회하는 상황을 상상해 볼 수 있습니다.
기본 상태의 Redis는 기본 키 기반 조회에 탁월합니다. 어차피 값(value)이 데이터 구조인 키-값(key-value) 스토어이기 때문입니다. 하지만 Redis에는 쿼리 및 보조 인덱싱 시스템이 내장되어 있지 않기 때문에, 데이터를 인덱싱하고 쿼리하려면 복잡한 방식으로 직접 인덱스를 관리해야 했습니다.
여기서도 역시 이런 복잡성을 대신 처리해 주고 싶었습니다. 그래서 우리는 필수적인 Redis 모듈인 RediSearch 위에 유연한 쿼리 표현식을 구축했습니다. RediSearch는 오픈소스(source-available) 모듈로, Redis에 없던 쿼리 및 인덱싱 기능을 제공합니다.
Customer 모델의 일부 필드에 `index=True`로 표시하면 어떻게 되는지 살펴보겠습니다. 이제 모델을 통해 다음과 같이 쿼리할 수 있습니다.
이 표현식 문법이 다소 익숙하게 느껴지실 수 있습니다. Peewee, SQLAlchemy, Django ORM에서 좋았던 점들을 모두 섞어 담은 결과물이기 때문입니다.
내장 모델(Embedded Models)
Redis로 복잡한 데이터를 모델링하다 보면 필연적으로 내장(embedded) 데이터를 저장해야 하는 순간이 옵니다. Redis Hash로 고객 데이터를 모델링한다면, 개별 고객의 Hash 안에 배송지 주소 같은 정보를 함께 담고 싶어질 것입니다. 불행히도 Redis Hash는 List, Set 또는 다른 Hash 같은 중첩 컨테이너를 저장할 수 없기 때문에 이 방법은 동작하지 않습니다.
바로 이럴 때 데이터를 네이티브 JSON 객체로 저장하는 것이 큰 의미를 갖습니다. 고객 데이터를 JSON 문서로 모델링하면 단일 고객 레코드 안에 원하는 무엇이든 내장할 수 있습니다.
하지만 Redis는 기본적으로 JSON을 지원하지 않습니다. 바로 이런 이유로 우리는 오픈소스 RedisJSON 모듈을 만들었습니다. RedisJSON을 사용하면 Redis를 문서 데이터베이스처럼 활용하여 복잡한 JSON 객체를 손쉽게 저장하고 쿼리할 수 있습니다.
Redis OM for Python에서는 Redis 인스턴스에 RedisJSON이 설치되어 있다면 JsonModel 클래스를 사용할 수 있습니다. 이 모델 클래스를 활용하면 JsonModel 안에 다른 JsonModel을 내장할 수 있습니다. 고객이 주문 배열을 갖고, 각 주문이 다시 상품 배열을 갖는 식으로 말이죠.
Redis OM for Python에서 내장 JSON 모델이 어떤 모습인지 예제를 통해 확인해 보세요.
복잡한 JSON 객체를 유연하게 저장할 수 있을 뿐만 아니라, Redis OM for Python은 이런 중첩 구조를 인지하고 그 구조에 대한 쿼리 표현식 작성까지 지원합니다. 정말 놀랍지 않나요?
Redis OM for Python 직접 경험하기
Redis OM for Python에 대한 저의 설렘이 전달되었기를 바랍니다. 저는 현재 Python 생태계의 최고 장점들을 결합하여, Redis 개발자가 굳이 감당할 필요가 없다고 생각되는 복잡성을 대신 처리해 주는 데 집중해 왔습니다.
흥미가 생기셨다면 시작하기 튜토리얼을 확인해 보세요. Redis OM for Python은 아직 '프리뷰(Preview)'라고 부르는 매우 초기 단계입니다. 따라서 아직 다듬어야 할 부분이 있고, 버그를 만날 수도 있으며, 전체 문서화 작업도 진행 중입니다. 하지만 비전은 분명하니, 꼭 한번 경험해 보시길 권합니다.
마지막으로, 우리는 선언적 데이터 모델에서 출발했지만 데이터 모델링을 비롯해 그 이상으로 만들고 싶은 것들이 아직 많이 남아 있다는 점을 강조하고 싶습니다. 앞으로 더욱 흥미로운 Redis OM 기능들을 기대해 주세요!