참고: 이 내용은 2022. 3. 31에 게시된 컨텐츠(Solving a Partial Data Retention Challenge with Cryptography and Java UDFs)에서 번역되었습니다.

Singular에서 우리는 각각이 모든 종류의 기밀 정보에 연결된 테라바이트 단위의 사용 로그와 같은 고객의 중요한 정보를 처리합니다. 때때로 고객은 개인 정보 보호를 위해 우리에게 이 데이터에 선택적 보존 정책을 적용할 것을 요청하기도 합니다. 우리가 Snowflake로 마이그레이션하는 동안 이러한 요구 사항이 하나 있었습니다. 우리는 특정 정보를 30일 이내로 보관할 것을 요청받았습니다. 하지만, 이 정보는 우리가 30일 이상 보관해야 하는 열 사이에 배치되었습니다. 설상가상으로 30일 보존 제한이 있는 특정 값이 문자열의 중간에 나타나기도 했습니다. 이 문자열 자체는 30일 이상 보존 대상이었습니다.

이것은 우리에게 흥미로운 과제를 제기했습니다. 쿼리 속도와 비용을 최소화하면서 수집 후 30일 후에 열과 문자열의 일부를 선택적으로 삭제하려면 어떻게 해야 할까요?

Snowflake의 간략한 소개

우리가 작업 중인 사용 로그는 추가 간접비 없이 안정적으로 액세스할 수 있어야 합니다. 따라서 우리는 Snowflake를 사용하고 있습니다. Snowflake를 사용한 작업에서 우리는 쿼리를 필요한 수준으로 수행시키려면 각 로그를 정규화되지 않게 저장하는 것, 즉 연결된 모든 정보를 함께 저장하는 것이 필수적이라는 사실을 알게 되었습니다. 따라서 대부분의 데이터를 각 로그에 몇 개월 동안 저장하고 일부 데이터는 30일 동안만 보존하려는 경우 문제가 발생합니다.

Snowflake는 각각 50MB에서 500MB 사이의 데이터를 포함하는 마이크로 파티션에 데이터를 저장합니다. 마이크로 파티션의 행은 열 형식으로 저장됩니다. 즉, 쿼리된 열만 읽히므로 이것은 필드가 많을 때 매우 유용합니다. 또한 Snowflake는 각 마이크로 파티션에 대해 범위 인덱스(마이크로 파티션 내 각 열의 최댓값 및 최솟값)를 유지합니다. 이것은 쿼리가 확인해야 하는 마이크로 파티션을 필터링하는 데 사용되는데, 이로써 잠재적으로 검색할 데이터 양을 줄일 수 있습니다. 우리는 대규모 데이터 세트에 대해 대상 성능 수준에서 쿼리를 실행하기 위해 이러한 인덱스를 현명하게 사용하는 방법을 배웠지만 여기서는 해당 주제에 대해 자세히 설명하지 않을 것입니다. 이 블로그 게시물의 목적상 중요한 것은 지속적으로 증가하는 필드(이 경우 타임스탬프 필드)를 사용하여 우수한 필터를 생성할 수 있다는 사실입니다.

외부 API 호출 또는 내부 정기 작업에 의해 시작된 Snowflake 쿼리는 모두 가상 웨어하우스로 할당된 리소스를 사용합니다. 각 가상 웨어하우스는 쿼리되는 데이터의 스토리지와 완전히 독립적으로 메모리 및 처리 능력을 할당한 것입니다. 이렇게 하면 할당이 완전히 동적이 됩니다. 작은 가상 웨어하우스를 추가로 할당하는 것이 하나의 4XL을 할당하는 것만큼 쉽습니다.

집계된 쿼리나 단순 수정 등 모든 작업에 대해 대량의 데이터를 검색하려면 대량의 리소스를 투자해야 합니다. 지금부터 이 사실을 명심하십시오.

Singular에서 Snowflake를 사용하는 방법

Singular에서는 Snowflake를 사용하여 지속적으로 업데이트되는 방대한 양의 데이터를 분석합니다. 우리는 매일 수십 테라바이트의 로그를 수집합니다. 파일을 S3 버킷에 지속적으로 업로드하고 Snowpipe를 사용하여 Snowfake에 담습니다. 그리고 마지막으로 Streams Tasks를 사용하여 장기 스토리지 테이블에 삽입합니다. 당사의 수집 아키텍처에 대해 자세히 알아보려면 지난 블로그인 이벤트 웨어하우스를 Athena에서 Snowfake로 마이그레이션하기를 참조하십시오.

요약하자면, 우리 데이터는 여러 테이블에 장기간 저장되며, 이 테이블에서는 행을 삭제하여 보존을 처리합니다. 이러한 DELETE 명령은 데이터를 수집 시 올바르게 분할하여 대부분 전체 마이크로 파티션을 삭제하므로 효율성을 크게 높입니다.

이 중 어느 것도 정해진 시간 후에 특정 열을 삭제해야 하는 특정 문제를 해결하는 데 도움이 되지 않습니다. 따라서 문자열 또는 JSON 열에 포함된 부분 정보를 쉽게 삭제할 수 없습니다. 그렇다면 이제 솔루션에 대해 이야기해 보겠습니다.

데이터 보존 문제에 대한 접근 방식

우리는 먼저 SQL 쿼리를 사용하여 하루에 한 번씩 관련 데이터를 수정하는 단순한 접근 방식을 시도했습니다. 이것은 실현 불가능했습니다. Snowflake에서 단일 행의 단일 열을 수정하면 해당 행이 있는 마이크로 파티션이 다시 작성됩니다. 이 경우 이것은 전체 데이터 세트를 반복해서 다시 쓰는 것을 의미합니다.

두 번째 접근 방식은 중요한 보존 제한 데이터를 기본 테이블에서 보조 테이블로 분리하고 데이터를 쿼리할 때 JOIN 절을 사용하여 데이터를 검색하는 것이었습니다. 이렇게 하면 보조 데이터 테이블을 30일 보존으로 제한하는 간단한 DELETE 작업을 설정할 수 있습니다. 이 접근 방식의 문제 중 하나는 다른 데이터와 함께 문자열에 포함된 중요한 값을 어떻게 처리할 것인가였습니다. 그러나 주요 문제는 중요한 데이터의 고유한 값의 양이었습니다. 보조 테이블을 사용하는 JOIN 문은 쿼리를 상당히 느리게 할 수 있습니다.

약간의 암호학

우리가 생각해낸 솔루션은 민감한 값을 처음부터 숨기는 것이었습니다. Snowflake에 업로드하기 전에 데이터를 검색하여 발견한 각 중요 값을 암호화합니다. 암호화 키가 없으면 암호화된 값을 읽을 수 없습니다.

그러나 짧은 값을 많이 사용할 경우 암호화 방법이 매우 느릴 수 있습니다. 예를 들어, 수십 개의 문자 메시지를 해독하는 것은 같은 크기의 파일 하나를 해독하는 것보다 훨씬 더 오래 걸립니다. 그래서 우리는 값을 더 빨리 암호화하고 해독할 방법을 모색했고, 그 방법을 가장 기본적인 암호화 방식에서 찾을 수 있었습니다. 바로 일반 값(숨길 대상)과 암호화 키에 적용되는 Exclusive 또는 (XOR) 바이너리 연산자입니다.

XOR은 두 비트를 결합한 연산자입니다. 비트가 다르면 결과가 1이 되고 그렇지 않으면 0이 됩니다. 다른 비트 연산자와 마찬가지로 비트 단위로 동일한 길이의 바이너리 입력 두 개에 적용할 수 있습니다. 이런 방식으로 사용되 이것은 매우 유용한 속성을 가진 간단한(따라서 매우 빠른) 연산자입니다. 일반 메시지 P와 무작위 키 K를 사용하고 XOR(P, K)을 적용하면 키 K가 없으면 읽을 수 없는 암호가 만들어집니다. 그러나 XOR(암호, K)를 다시 적용하면 일반 P가 다시 생성됩니다. 키가 한 번만 사용되고 숨겨진 상태로 유지되는 한 암호화된 데이터는 키 없이 읽을 수 없습니다. (암호화하는 각 값에 대해 키가 고유해야 하는 자세한 이유를 보려면 Cryptosmith의 설명을 읽으십시오.)

그림 1: XOR 연산자를 기본 암호화를 수행하는 간단하고 빠른 방법으로 사용할 수 있습니다.

이때부터 우리는 일정량의 메모리(JOIN을 사용하지 않고)만 사용하면서 값 하나당 해당 값 크기 이상의 고유한 암호화 키를 생성하는 방법을 찾아야 했습니다. 이를 위해서는 암호화의 기본 요소 중 하나인 암호 해시 함수 또는 CHF가 필요했습니다.

CHF는 매우 유용한 도구입니다. CHF에 익숙하지 않은 경우 학습할 가치가 있습니다. 이들은 임의의 긴 문자열을 일정한 크기의 출력으로 매핑하는 빠른 단방향 함수입니다. 출력은 입력에 의해 결정론적으로 결정되지만 입력 부분에서는 여전히 추측하기 어렵습니다. CHF를 사용하면 데이터를 해독하는 데 필요한 여러 정보를 기반으로 암호화 키를 생성할 수 있습니다. 한 조각만 없어도 암호화 키를 재생성할 수 없습니다.

CHF는 최소한 위에 설명된 암호화 방법을 사용하는 경우 암호화된 데이터의 길이를 제한합니다. 다행히도, 우리는 지리 수준 정보나 네트워크 세부 정보와 같은 짧은 문자열만 암호화하면 되었습니다.

그림 2에 표시돼 있듯, 매일 무작위 값 하나(1)를 30일 후에 삭제하면 고유한 값이 수반될 경우 만료시키고자 하는 모든 데이터에 대해 고유한 암호화 키를 생성할 수 있었습니다. 무작위 데이터 생성기의 도움을 받아 생성된 무작위 소금(2) 값과 일별 값을 사용하여 암호화를 위한 실제 고유 키(3)를 만들었습니다. 그런 다음 소금은 일별 값의 날짜와 함께 암호화된 값에 연결되어 Snowflake(4)에 기록된 실제 결과가 되었습니다. 이렇게 해서 쿼리에서 암호화 키를 다시 만들 수 있었습니다.

그림 2: 30일 후에 삭제되는 단일 일일 키를 사용하여 고유한 값이 수반되는 경우 만료시키고자 하는 모든 데이터에 대해 고유한 암호화 키를 생성할 수 있었습니다.

열 형식 및 문자열 내 보존 정책 구현

암호화된 값을 읽으려면 각 쿼리에 다음이 필요합니다.

  • 특정 값을 암호화하는 데 사용되는 일별 키
  • 숨겨진 값을 포함하는 암호화를 찾고 해독하는 방법

첫 번째 부분의 경우 우리는 GETVARIABLE 명령을 사용하여 쿼리에 키를 사용할 수 있게 해주는 Snowflake의 세션 변수를 사용합니다. 세션 변수는 단순하고 다른 데이터베이스에 상응하는 항목이 있으므로 여기서는 자세히 설명하지 않겠습니다. 두 번째 부분의 경우에는 특정 열에 액세스할 때 모든 쿼리의 SQL을 즉시 변경해주는 마스킹 정책을 사용합니다.

마스킹 정책은 예를 들어 설명할 가치가 있습니다. 마스킹 정책은 다음과 같은 단순한 쿼리를

다음으로 변환하는 열에서 정의됩니다.

이렇게 하면 전체 해독 프로세스가 내부 구조 아래로 이동하므로 마치 모든 중요한 데이터가 대신 단순하게 저장된 듯 암호화와 관계없이 쿼리를 계속 구성할 수 있습니다.

마스킹 정책에 대한 우리의 정의를 도식적으로 표현하면 다음과 같습니다.

보시다시피 이 정책은 열의 값에 따라 작동하는 함수와 매우 유사합니다. 이제 이것을 우리가 원하는 열에 적용할 수 있습니다.

이렇게 하면 로그 테이블에 대한 쿼리가 수정 없이 customer_sensitive_info 열에 있는 데이터를 완벽하게 해독할 수 있습니다.

Java UDF 구현하기

우리 솔루션을 구현하기 위한 또 다른 필수 도구는 JavaScript 사용자 정의 함수(Java UDF)입니다. 간단한 예부터 시작하여 보다 복잡한 예제를 통해 이 도구의 유연성을 보여 드리겠습니다.

마스킹 정책을 구현하는 과정에서 우리는 난관에 봉착했습니다. Snowflake는 XOR 두 바이너리 입력에 대한 메서드를 제공하지 않습니다. 우리는 기본 제공 SQL 메서드를 사용해 보았지만 그 결과에 만족하지 못했습니다. 하지만 Snowflake를 사용하면 Snowflake 엔진에서 UDF를 실행할 수 있으며, UDF 메커니즘은 빠르고 간단한 솔루션을 제공합니다. 다음 js_binxor을 살펴보십시오.

이 간단한 예는 UDF의 유용성을 보여줍니다. 그러나 전역 상태에 있기 때문에 UDF의 힘은 이 예보다 훨씬 더 큽니다. 따라서 성능 저하 없이 값비싼 초기화 로직에 의존할 수 있습니다. 이 경우 우리는 기본적으로 UDF 환경으로 가져오기할 수 없는 JavaScript 라이브러리를 사용했습니다.

보시다시피 우리는 Snowflake의 JavaScript 종속성에 포함되지 않은 라이브러리를 런타임 효율성을 저하하지 않고 ‘가져오기’할 수 있습니다. 또한 원하는 경우 일부 도우미 메서드를 정의하거나 비슷한 상황에 포함시키는 것이 일반적으로 조심스러운 다른 설정을 실행할 수도 있습니다. 이는 UDF의 진정한 다재다능함을 보여줍니다. 여러분도 이러한 다재다능함을 활용할 수 있기를 바랍니다.

일부 데이터 보존:기타 교훈
  • 때때로 문제를 해결하기 위해 단일 열 값 수준에서 처리할 수도 있습니다. 대규모 JOIN 또는 DELETE에 의존하기 전에 먼저 찾아보십시오.
  • 성능에 민감한 환경에서 암호화를 사용할 경우 함수의 이면에 있는 이론을 이해하는 것이 좋습니다. 표준 기성 솔루션은 이상적이지 않을 수 있습니다(일반적으로 성능을 미세 조정할 때 그러할 수 있음).
  • 개인 정보 보호 또는 보안이 우려되는 경우 구현에 각별히 주의하십시오. 암호화 함수는 종종 그들의 매개변수 및 사용에 대해 가정합니다. 여러분은 이러한 매개변수를 준수해야 하거나 취약성을 발생시킬 위험을 감수해야 합니다.
  • Snowflake에서 자신만의 방법을 만드는 것을 두려워하지 마세요! 개발 속도를 크게 높일 수 있습니다.