Klaviyo와 Snowflake를 연결합니다.
Klaviyo 고급 데이터 플랫폼은 Klaviyo의 표준 마케팅 애플리케이션에 포함되어 있지 않으며, 관련 기능에 액세스하려면 구독이 필요합니다. 이 요금제를 구매하는 방법을 알아보려면 결제 가이드를 확인해 주세요.
이 문서의 목적상 "테이블"이라는 용어를 사용하지만 뷰, 구체화된 뷰 및 테이블은 모두 가져올 수 있는 유효한 Snowflake 객체입니다. Klaviyo가 개체에 대해 SELECT col1 FROM table_name을 실행할 수 있다면, 원하는 대로 자유롭게 사용할 수 있습니다.
이 문서에서 "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", "OPTIONAL"이라는 핵심 단어는 RFC 2119에 설명된 대로 해석해야 해요.
Snowflake 관리자 설정
이 섹션에서는 Klaviyo가 데이터를 가져올 수 있도록 Snowflake 환경에서 수행해야 하는 단계에 대해 간략하게 설명합니다.
- 로컬 터미널에서 다음 명령을 실행하여 개인 키를 생성합니다.
openssl genrsa 2048 | openssl pkcs8 -topk8 -inform PEM -out rsa_key.p8 -nocrypt - 터미널에서 다음 명령을 실행하여 개인 키를 참조하는 공개 키를 생성합니다.
openssl rsa -in rsa_key.p8 -pubout -out rsa_key.pub - rsa_key.pub을 복사하여 아래 스크립트에 붙여넣으면 user_rsa_public_key의 자리 표시자 'GENERATE_PUBLIC_KEY' 값을 대체할 수 있습니다. 아래 스크립트는 Mac 사용자에게 적용되거나, 원하는 경우 IDE에서 rsa_key.pub을 열고 파일의 전체 내용을 복사할 수 있습니다.
# Mac terminal command to write the key to your terminal and copy it to the clipboard
cat rsa_key.pub | tee /dev/tty | pbcopy - Snowflake 환경에서 다음 스크립트를 실행하여 Klaviyo가 사용할 서비스 사용자를 생성합니다. 아래 설정을 완료하려면 보안 관리자 및 시스템 관리자 권한이 있어야 합니다. 어떤 역할이 있는지 검토하려면 SHOW GRANTS TO USER <your_username> 을 실행하고 두 역할이 모두 나열되어 있는지 확인하세요. 역할을 조정해야 하는 경우 시스템 관리자에게 문의하세요.
- 스크립트 시작 부분에 설정된 변수를 언제든지 업데이트할 수 있습니다.
- 요약하면 다음과 같습니다.
- 기존 웨어하우스를 선택하거나 새 웨어하우스를 만듭니다.
- 기존 데이터베이스를 선택하거나 새 스키마를 보유할 새 데이터베이스를 만듭니다.
- 새 스키마
KLAVIYO_TMP2개를 만들고,KLAVIYO_IMPORT_FROM_DWH - 새 네트워크 정책을 만들고 목록 Klaviyo IP를 허용합니다
- Klaviyo의 사용자 및 역할을 만듭니다.
- 이 스크립트는 idempotent(여러 번 안전하게 실행할 수 있음)이지만 이름이 충돌하는 기존 개체를 덮어쓰지는 않습니다.
BEGIN;
-- create variables for user / password / role / warehouse / database.
-- Change these to whatever you prefer.
SET role_name = 'KLAVIYO_DATA_TRANSFER_ROLE'; -- all letters must be uppercase, ex. 'KLAVIYO_DATA_TRANSFER_ROLE'
SET user_name = 'KLAVIYO_DATA_TRANSFER_USER'; -- all letters must be uppercase, ex. 'KLAVIYO_DATA_TRANSFER_USER'
SET warehouse_name = 'KLAVIYO_DATA_TRANSFER_WAREHOUSE'; -- all letters must be uppercase, ex. 'KLAVIYO_DATA_TRANSFER_WAREHOUSE'
SET database_name = 'KLAVIYO_DATABASE'; -- all letters must be uppercase, ex. 'KLAVIYO_DATABASE'. If this database doesn't exist, a new one will be created.
SET network_policy = 'KLAVIYO_DATA_TRANSFER_NETWORK_POLICY'; -- all letters must be uppercase, ex. 'KLAVIYO_NETWORK_POLICY'
SET network_rule = 'KLAVIYO_DATA_TRANSFER_NETWORK_RULE'; -- all letters must be uppercase, ex. 'KLAVIYO_NETWORK_RULE'
/* replace GENERATE_PUBLIC_KEY below with generated public key */
-- DO NOT CHANGE
SET schema_name_tmp = $database_name || '.KLAVIYO_TMP'; -- DO NOT CHANGE
SET schema_name_import = $database_name || '.KLAVIYO_IMPORT_FROM_DWH'; -- DO NOT CHANGE
SET full_network_rule_tmp = $schema_name_tmp || '.' || $network_rule; -- DO NOT CHANGE
SET full_network_rule_import = $schema_name_import || '.' || $network_rule; -- DO NOT CHANGE
-- change role to sysadmin for warehouse / database steps
USE ROLE sysadmin;
-- create a warehouse for data transfer service
CREATE WAREHOUSE IF NOT EXISTS IDENTIFIER($warehouse_name)
warehouse_size = xsmall
warehouse_type = standard
auto_suspend = 60
auto_resume = true
initially_suspended = true;
-- create database for data transfer service
CREATE DATABASE IF NOT EXISTS IDENTIFIER($database_name);
-- create schemas for data transfer service
CREATE SCHEMA IF NOT EXISTS IDENTIFIER($schema_name_tmp);
CREATE SCHEMA IF NOT EXISTS IDENTIFIER($schema_name_import);
-- change role to securityadmin for user / role steps
USE ROLE securityadmin;
-- create network rule and policy for database
GRANT USAGE ON DATABASE IDENTIFIER($database_name) TO ROLE securityadmin;
GRANT USAGE, CREATE NETWORK RULE ON SCHEMA IDENTIFIER($schema_name_tmp) TO ROLE securityadmin;
GRANT USAGE, CREATE NETWORK RULE ON SCHEMA IDENTIFIER($schema_name_import) TO ROLE securityadmin;
-- whitelist klaviyo ip ranges, for KLAVIYO_TMP schema
CREATE NETWORK RULE IF NOT EXISTS IDENTIFIER($full_network_rule_tmp)
type = IPV4
value_list = (
'184.72.183.187/32', '52.206.71.52/32', '3.227.146.32/32', '44.198.39.11/32', '35.172.58.121/32', '3.228.37.244/32', '54.88.219.8/32', '3.214.211.176/32'
)
comment = 'Klaviyo IP Ranges as of April 2025';
CREATE NETWORK POLICY IF NOT EXISTS IDENTIFIER($network_policy)
allowed_network_rule_list = ($full_network_rule_tmp);
-- whitelist klaviyo ip ranges, for KLAVIYO_IMPORT_FROM_DWH schema
CREATE NETWORK RULE IF NOT EXISTS IDENTIFIER($full_network_rule_import)
type = IPV4
value_list = (
'184.72.183.187/32', '52.206.71.52/32', '3.227.146.32/32', '44.198.39.11/32', '35.172.58.121/32', '3.228.37.244/32', '54.88.219.8/32', '3.214.211.176/32'
)
comment = 'Klaviyo IP Ranges as of April 2025';
CREATE NETWORK POLICY IF NOT EXISTS IDENTIFIER($network_policy)
allowed_network_rule_list = ($full_network_rule_import);
-- create role for data transfer service
CREATE ROLE IF NOT EXISTS IDENTIFIER($role_name);
GRANT ROLE IDENTIFIER($role_name) TO ROLE sysadmin;
-- create a user for data transfer service
CREATE USER IF NOT EXISTS IDENTIFIER($user_name)
type = SERVICE
network_policy = $network_policy
default_role = $role_name
default_warehouse = $warehouse_name
rsa_public_key = 'GENERATE_PUBLIC_KEY';
GRANT ROLE IDENTIFIER($role_name) TO USER IDENTIFIER($user_name);
ALTER USER IDENTIFIER($user_name) SET NETWORK_POLICY = $network_policy;
-- grant service role access to warehouse
GRANT USAGE
ON WAREHOUSE IDENTIFIER($warehouse_name)
TO ROLE IDENTIFIER($role_name);
-- grant service access to database
GRANT MONITOR, USAGE
ON DATABASE IDENTIFIER($database_name)
TO ROLE IDENTIFIER($role_name);
-- Grant privileges for KLAVIYO_TMP
GRANT USAGE ON SCHEMA IDENTIFIER($schema_name_tmp) TO ROLE IDENTIFIER($role_name);
GRANT MONITOR, USAGE, CREATE TABLE, CREATE VIEW, CREATE SEQUENCE, CREATE FUNCTION, CREATE PROCEDURE
ON SCHEMA IDENTIFIER($schema_name_tmp)
TO ROLE IDENTIFIER($role_name);
GRANT ALL ON FUTURE TABLES IN SCHEMA IDENTIFIER($schema_name_tmp) TO ROLE IDENTIFIER($role_name);
-- Grant privileges for KLAVIYO_IMPORT_FROM_DWH
GRANT USAGE ON SCHEMA IDENTIFIER($schema_name_import) TO ROLE IDENTIFIER($role_name);
GRANT SELECT
ON FUTURE TABLES
IN SCHEMA IDENTIFIER($schema_name_import)
TO ROLE IDENTIFIER($role_name);
COMMIT; Snowflake 데이터 설정
위에서 두 개의 새 스키마를 만들었습니다.
- KLAVIYO_TMP는 Klaviyo에서만 사용됩니다. 이 스키마에 생성된 테이블은 수정해서는 안 됩니다. Klaviyo는 더 이상 필요하지 않은 테이블을 삭제합니다.
- KLAVIYO_IMPORT_FROM_DWH는 Klaviyo를 가져올 최종 테이블을 저장하는 곳입니다. 동기화 생성 프로세스를 진행하면 이 스키마의 모든 테이블 중에서 선택할 수 있도록 나열됩니다. 따라서 가져올 최종 테이블만 저장하여 설정 도중 혼란을 방지해야 합니다.
Klaviyo로 가져오려는 모든 테이블은 다음 기준을 충족해야 합니다.
타임스탬프 요구 사항
- 테이블에는 행이 생성되거나 업데이트된 시기를 나타내는 타임스탬프 필드가 포함되어야 합니다. 이 항목은 inserted_at 또는 updated_at가 될 때가 많습니다. 동기화 생성 프로세스 중에 각 테이블에 대해 이 설정을 지정합니다.
- 타임스탬프 필드는 단조롭게 증가해야 합니다(예: 항상 더 커지거나 동일하게 유지되어야 하며 결코 작아지지 않아야 함).
- 동기화 생성 후에는 행의 타임스탬프 값을 과거 시간으로 설정하지 않아야 하며 그렇지 않으면 Klaviyo가 해당 행을 픽업하지 못할 수 있습니다.
- 위의 요구 사항을 준수하는 한 이 특정 필드의 시간대는 Klaviyo에 중요하지 않습니다
- 타임스탬프는 UTC 형식이거나 시간대 정보를 포함해야 합니다. 시간대 정보가 누락된 경우 Klaviyo에서 UTC로 가정합니다. 사용자 지정 속성의 경우 이러한 타임스탬프는 문자열 형식으로 유지되므로 원하는 시간대로 해석할 수 있습니다.
- 타임스탬프 필드는 행이 삽입된 시점을 반영해야 하며 현재 날짜 근처에 클러스터링되어야 합니다. Klaviyo는 표에서 가장 오래된 타임스탬프 값부터 1시간 창을 스캔하여 데이터를 동기화합니다. 타임스탬프가 과거보다 훨씬 먼 단일 행(예: 다른 모든 기록이 최근의 경우 2023년 레코드)으로 인해 Klaviyo는 해당 날짜로부터 매 동기화 주기마다 1시간마다 반복합니다. 이는 현재 예정된 릴리스에서 해결해야 할 제한입니다.
- 타임스탬프 기간 1시간당 행 밀도를 고려하세요. 데이터가 1시간 타임스탬프 기간으로 일괄적으로 로드되므로 동일한 1시간 기간에 수백만 개의 레코드가 생성되면 동기화가 느려지거나 지연될 수 있습니다. 행 밀도의 상한은 각 행의 데이터 양에 따라 다르지만, 염두에 두어야 할 좋은 규칙은 타임스탬프 기간 1시간당 100,000행입니다.
- Klaviyo는 동기화할 테이블에 행을 추가할 때마다 타임스탬프 필드를 CURRENT_TIMESTAMP() 또는 이와 동등한 함수로 설정할 것을 권장합니다. 여러 행의 타임스탬프가 동일할 수 있습니다. 아래 예시를 참조하세요.
INSERT INTO table_name AS
SELECT ...
, CURRENT_TIMESTAMP() AS inserted_at
... 표 구조
- 테이블은 추가 전용(또는 삽입 전용)으로 처리해야 합니다.
- 대신 행을 업데이트하려면 Klaviyo가 변경 사항을 파악할 수 있도록 타임스탬프 필드를 업데이트해야 합니다.
- 테이블은 타임스탬프 열에 주문해야 합니다. Snowflake는 삽입 순서에 따라 클러스터링 및 파티셔닝을 처리합니다. 이렇게 하면 Klaviyo의 가져오기 쿼리를 최적화하여 Snowflake의 계산 비용을 낮출 수 있습니다
프로필 고유성과 일관성
- 각 프로필 속성을 하나의 데이터 소스(표)에서만 가져와야 합니다. Klaviyo는 동기화 생성 중에 다른 테이블에서 동일한 속성을 선택하지 않도록 하여 이 요구 사항을 단순화합니다.
- 모든 가져오기 테이블에 동일한 프로필 식별자(이메일, 전화번호, 외부 ID 등)를 사용하여 중복 프로필이 생성될 위험을 최소화해야 합니다.
- 입력한 프로필 식별자가 Klaviyo 내 기존 프로필과 일치하지 않는 경우 Klaviyo는 새 프로필을 생성합니다.
- 예: Table1(이메일, fav_color) + Table2(전화, 생일)
- 현재 프로필이 존재하지 않는 경우 동일한 사람에 대해 2개의 프로필을 만들 수 있습니다. 프로필이 존재하는 경우 Klaviyo에서 프로필 해결을 처리하고 내부적으로 업데이트합니다.
- 이 문제를 방지하는 한 가지 방법은 모든 프로필에 하나의 가져오기 표만 사용하는 것입니다.
순환 가져오기-내보내기 루프 방지
- 순환 가져오기-내보내기 루프를 방지하기 위해 가져오기 및 내보내기 기능을 모두 사용하는 시나리오를 주의 깊게 관리해야 합니다. Klaviyo는 현재 이 시나리오를 감지하지 못하므로 내보내기 프로세스에서 가져오기 테이블의 업스트림에 있는 테이블로 데이터를 다시 전송하지 않도록 하세요.
- Klaviyo에는 아직 이 시나리오를 감지할 수 있는 논리가 없습니다.
- 다음과 같이 표시됩니다.
- 각 내보내기 동기화 주기에 Klaviyo는 모든 프로필을 내보냅니다
- 그런 다음 일련의 변환을 통해 내보낸 모든 프로필을 가져오기 표에 추가합니다.
- 각 가져오기 동기화 주기에 Klaviyo는 가져오기 테이블의 모든 프로필을 읽으며, 이는 결국 다시 내보내기됩니다
- 안전할 가능성이 높은 시나리오입니다.
- 내보내기 테이블만 사용하여 가져오기 테이블에 추가된 행을 제한하는 경우
- 내보내기 테이블을 확인해도 가져오기 테이블에 행이 추가되지 않습니다.
- 순환 가져오기-내보내기 루프의 결과는 무엇인가요?
- 이렇게 하면 사용자와 Klaviyo 모두에게 불필요한 계산 비용이 발생합니다.
문제 해결
동기화가 중단된 것 같습니다.
동기화가 실행 중이지만 몇 시간 후에도 Klaviyo에 데이터가 표시되지 않거나 동기화가 완료되는 데 비정상적으로 오랜 시간이 걸리는 경우, 과거에 훨씬 더 오래 된 타임스탬프 값이 가장 큰 원인일 수 있습니다.
- 테이블의 타임스탬프가 나머지 데이터보다 상당히 오래된 행(예: 2023년부터 다른 모든 행이 지난 주에 속하는 행)이 있는지 확인하세요. 하나의 이상치 행이라도 Klaviyo는 최근 데이터에 도달하기 전에 1시간 동안 비어 있는 수천 개의 창을 반복해야 합니다.
- 수정: 동기화를 활성화하거나 다시 활성화하기 전에 타임스탬프가 과거로부터 먼 행을 업데이트하거나 제거하거나 최근 값으로 설정합니다. 다시 채우기의 경우 모든 기록 행을 동일한 최근 타임스탬프(예: 현재 작업 런타임)로 설정하여 Klaviyo가 스캔해야 하는 1시간 윈도우 수를 최소화합니다. 약 100,000개 이상의 행이 있는 경우 약 100,000개의 타임스탬프를 61분 간격으로 배치합니다.
권장 Snowflake 클러스터링 키 설정
타임스탬프 열에 Snowflake 테이블을 클러스터링하면 Klaviyo의 가져오기 쿼리가 불필요한 마이크로 파티션을 건너뛸 수 있어 동기화 시간과 Snowflake 계산 비용을 모두 줄일 수 있습니다.
ALTER TABLE your_database.KLAVIYO_IMPORT_FROM_DWH.your_table CLUSTER BY (your_timestamp_column);