메인 콘텐츠로 건너뛰기
데이터 마스킹은 데이터 보호를 위한 기법으로, 원본 데이터의 형식과 구조는 유지하면서 개인 식별 정보(PII) 또는 민감한 정보를 제거한 버전으로 대체하는 방식입니다. 이 가이드에서는 여러 가지 접근 방식을 사용해 ClickHouse에서 데이터를 마스킹하는 방법을 설명합니다:
  • 마스킹 정책 (ClickHouse Cloud, 25.12+): 특정 사용자/역할에 대해 쿼리 시점에 적용되는 네이티브 동적 마스킹
  • 문자열 대체 함수: 내장 함수를 사용한 기본적인 마스킹
  • 마스킹된 뷰: 변환 로직이 포함된 뷰 생성
  • materialized 컬럼: 원본 데이터와 함께 마스킹된 버전 저장
  • 쿼리 마스킹 규칙: 로그의 민감한 데이터 마스킹 (ClickHouse OSS)

마스킹 정책 사용 (ClickHouse Cloud)

마스킹 정책은 ClickHouse Cloud 버전 25.12부터 사용할 수 있습니다.
CREATE MASKING POLICY SQL 문은 특정 사용자 또는 역할에 대해 쿼리 시점에 컬럼 값을 동적으로 마스킹하는 네이티브 방식을 제공합니다. 다른 방식과 달리 마스킹 정책은 별도의 뷰를 생성하거나 마스킹된 데이터를 저장할 필요가 없습니다. 사용자가 테이블에 쿼리할 때 변환이 투명하게 적용됩니다.

기본 마스킹 정책

마스킹 정책을 보여주기 위해 고객 정보가 포함된 orders 테이블을 생성해 보겠습니다:
CREATE TABLE orders (
    user_id UInt32,
    name String,
    email String,
    phone String,
    total_amount Decimal(10,2),
    order_date Date,
    shipping_address String
)
ENGINE = MergeTree()
ORDER BY user_id;

INSERT INTO orders VALUES
    (1001, 'John Smith', 'john.smith@gmail.com', '555-123-4567', 299.99, '2024-01-15', '123 Main St, New York, NY 10001'),
    (1002, 'Sarah Johnson', 'sarah.johnson@outlook.com', '555-987-6543', 149.50, '2024-01-16', '456 Oak Ave, Los Angeles, CA 90210'),
    (1003, 'Michael Brown', 'mbrown@company.com', '555-456-7890', 599.00, '2024-01-17', '789 Pine Rd, Chicago, IL 60601'),
    (1004, 'Emily Rogers', 'emily.rogers@yahoo.com', '555-321-0987', 89.99, '2024-01-18', '321 Elm St, Houston, TX 77001'),
    (1005, 'David Wilson', 'dwilson@email.net', '555-654-3210', 449.75, '2024-01-19', '654 Cedar Blvd, Phoenix, AZ 85001');
이제 마스킹된 데이터를 볼 수 있어야 하는 사용자용 역할을 생성하세요:
CREATE ROLE masked_data_viewer;
masked_data_viewer 역할에 적용할 마스킹 정책을 생성하세요:
CREATE MASKING POLICY mask_pii_data ON orders
    UPDATE
        name = replaceRegexpOne(name, '^([A-Za-z]+)\\s+(.*)$', '\\1 ****'),
        email = replaceRegexpOne(email, '^(.{2})[^@]*(@.*)$', '\\1****\\2'),
        phone = replaceRegexpOne(phone, '^(\\d{3})-(\\d{3})-(\\d{4})$', '\\1-***-\\3'),
        shipping_address = replaceRegexpOne(shipping_address, '^[^,]+,\\s*(.*)$', '*** \\1')
    TO masked_data_viewer;
masked_data_viewer 역할이 부여된 사용자가 orders 테이블을 쿼리하면 마스킹된 데이터가 자동으로 표시됩니다:
Query
SELECT * FROM orders ORDER BY user_id;
Response (for masked_data_viewer role)
┌─user_id─┬─name─────────┬─email──────────────┬─phone────────┬─total_amount─┬─order_date─┬─shipping_address──────────┐
│    1001 │ John ****    │ jo****@gmail.com   │ 555-***-4567 │       299.99 │ 2024-01-15 │ *** New York, NY 10001    │
│    1002 │ Sarah ****   │ sa****@outlook.com │ 555-***-6543 │        149.5 │ 2024-01-16 │ *** Los Angeles, CA 90210 │
│    1003 │ Michael **** │ mb****@company.com │ 555-***-7890 │          599 │ 2024-01-17 │ *** Chicago, IL 60601     │
│    1004 │ Emily ****   │ em****@yahoo.com   │ 555-***-0987 │        89.99 │ 2024-01-18 │ *** Houston, TX 77001     │
│    1005 │ David ****   │ dw****@email.net   │ 555-***-3210 │       449.75 │ 2024-01-19 │ *** Phoenix, AZ 85001     │
└─────────┴──────────────┴────────────────────┴──────────────┴──────────────┴────────────┴───────────────────────────┘
masked_data_viewer 역할이 없는 사용자는 마스킹되지 않은 원본 데이터를 볼 수 있습니다.

조건부 마스킹

WHERE 절을 사용하면 특정 행에만 마스킹을 적용할 수 있습니다. 예를 들어, 고액 주문에만 마스킹을 적용하는 경우는 다음과 같습니다:
CREATE MASKING POLICY mask_high_value_orders ON orders
    UPDATE
        name = replaceRegexpOne(name, '^([A-Za-z]+)\\s+(.*)$', '\\1 ****'),
        email = replaceRegexpOne(email, '^(.{2})[^@]*(@.*)$', '\\1****\\2')
    WHERE total_amount > 200
    TO masked_data_viewer;

우선순위가 있는 여러 마스킹 정책

여러 마스킹 정책이 동일한 컬럼에 적용되는 경우, PRIORITY 절을 사용하여 어떤 변환을 적용할지 제어합니다. 우선순위 값이 높을수록 나중에 적용됩니다:
-- 낮은 우선순위: 모든 민감한 데이터에 대한 기본 마스킹
CREATE MASKING POLICY basic_masking ON orders
    UPDATE
        name = '****',
        email = '****@****.com'
    TO masked_data_viewer
    PRIORITY 0;

-- 높은 우선순위: 더 정교한 마스킹 (basic_masking 재정의)
CREATE MASKING POLICY refined_masking ON orders
    UPDATE
        name = replaceRegexpOne(name, '^([A-Za-z]+)\\s+(.*)$', '\\1 ****')
    WHERE total_amount > 100
    TO masked_data_viewer
    PRIORITY 10;
이 예시에서 total_amount > 100인 주문은 name 컬럼에 대해 refined_masking 정책(우선순위 10)이 basic_masking 정책(우선순위 0)을 재정의하고, email에는 계속 기본 마스킹이 적용됩니다.

해시 기반 마스킹

일관된 마스킹이 필요한 경우(즉, 동일한 입력이 항상 동일한 마스킹 결과를 생성해야 하는 경우)에는 해시 함수를 사용합니다:
CREATE MASKING POLICY hash_sensitive_data ON orders
    UPDATE
        email = concat(toString(cityHash64(email)), '@masked.com'),
        phone = concat('555-', toString(cityHash64(phone) % 10000000))
    TO masked_data_viewer;

마스킹 정책 관리

모든 마스킹 정책 확인:
SHOW MASKING POLICIES;
마스킹 정책 삭제:
DROP MASKING POLICY mask_pii_data ON orders;
기존 정책을 교체하세요:
CREATE OR REPLACE MASKING POLICY mask_pii_data ON orders
    UPDATE name = '[REDACTED]'
    TO masked_data_viewer;
자세한 내용은 CREATE MASKING POLICY 문서를 참조하십시오.

문자열 대체 함수 사용

기본적인 데이터 마스킹에는 replace 계열 함수로 편리하게 데이터를 마스킹할 수 있습니다:
FunctionDescription
replaceOne검색 대상 문자열(haystack)에서 pattern이 처음 나타나는 부분을 지정된 대체 문자열로 바꿉니다.
replaceAll검색 대상 문자열(haystack)에서 pattern이 나타나는 모든 부분을 지정된 대체 문자열로 바꿉니다.
replaceRegexpOne검색 대상 문자열(haystack)에서 정규식 pattern(re2 구문)과 일치하는 부분 문자열의 첫 번째 항목을 지정된 대체 문자열로 바꿉니다.
replaceRegexpAll검색 대상 문자열(haystack)에서 정규식 pattern(re2 구문)과 일치하는 부분 문자열의 모든 항목을 지정된 대체 문자열로 바꿉니다.
예를 들어, replaceOne 함수를 사용하면 이름 “John Smith”를 자리 표시자 [CUSTOMER_NAME]로 바꿀 수 있습니다:
Query
SELECT replaceOne(
    'Customer John Smith called about his account',
    'John Smith',
    '[CUSTOMER_NAME]'
) AS anonymized_text;
Response
┌─anonymized_text───────────────────────────────────┐
│ Customer [CUSTOMER_NAME] called about his account │
└───────────────────────────────────────────────────┘
좀 더 일반적으로는 replaceRegexpOne을 사용해 모든 고객 이름을 바꿀 수 있습니다:
Query
SELECT 
    replaceRegexpAll(
        'Customer John Smith called. Later, Mary Johnson and Bob Wilson also called.',
        '\\b[A-Z][a-z]+ [A-Z][a-z]+\\b',
        '[CUSTOMER_NAME]'
    ) AS anonymized_text;
Response
┌─anonymized_text───────────────────────────────────────────────────────────────────────┐
│ [CUSTOMER_NAME] Smith called. Later, [CUSTOMER_NAME] and [CUSTOMER_NAME] also called. │
└───────────────────────────────────────────────────────────────────────────────────────┘
또는 replaceRegexpAll 함수를 사용해 사회보장번호는 마지막 4자리만 남기고 마스킹할 수 있습니다.
Query
SELECT replaceRegexpAll(
    'SSN: 123-45-6789',
    '(\d{3})-(\d{2})-(\d{4})',
    'XXX-XX-\3'
) AS masked_ssn;
위 쿼리에서 \3은 세 번째 캡처 그룹을 결과 문자열에 치환하는 데 사용되며, 결과는 다음과 같습니다:
Response
┌─masked_ssn───────┐
│ SSN: XXX-XX-6789 │
└──────────────────┘

마스킹된 VIEW 생성

VIEW는 앞서 언급한 문자열 함수와 함께 사용하여 민감한 데이터가 포함된 컬럼에, 사용자에게 표시되기 전에 변환을 적용할 수 있습니다. 이렇게 하면 원본 데이터는 변경되지 않고, 뷰를 쿼리하는 사용자는 마스킹된 데이터만 보게 됩니다. 예시를 위해 고객 주문 기록을 저장하는 테이블이 있다고 가정해 보겠습니다. 특정 직원 그룹이 이 정보를 볼 수 있도록 하되, 고객의 전체 정보는 볼 수 없게 하려고 합니다. 아래 쿼리를 실행하여 예시 테이블 orders를 생성하고, 여기에 가상의 고객 주문 기록 몇 개를 삽입하십시오:
CREATE TABLE orders (
    user_id UInt32,
    name String,
    email String,
    phone String,
    total_amount Decimal(10,2),
    order_date Date,
    shipping_address String
)
ENGINE = MergeTree()
ORDER BY user_id;

INSERT INTO orders VALUES
    (1001, 'John Smith', 'john.smith@gmail.com', '555-123-4567', 299.99, '2024-01-15', '123 Main St, New York, NY 10001'),
    (1002, 'Sarah Johnson', 'sarah.johnson@outlook.com', '555-987-6543', 149.50, '2024-01-16', '456 Oak Ave, Los Angeles, CA 90210'),
    (1003, 'Michael Brown', 'mbrown@company.com', '555-456-7890', 599.00, '2024-01-17', '789 Pine Rd, Chicago, IL 60601'),
    (1004, 'Emily Rogers', 'emily.rogers@yahoo.com', '555-321-0987', 89.99, '2024-01-18', '321 Elm St, Houston, TX 77001'),
    (1005, 'David Wilson', 'dwilson@email.net', '555-654-3210', 449.75, '2024-01-19', '654 Cedar Blvd, Phoenix, AZ 85001');
masked_orders 뷰를 생성하세요:
CREATE VIEW masked_orders AS
SELECT
    user_id,
    replaceRegexpOne(name, '^([A-Za-z]+)\\s+(.*)$', '\\1 ****') AS name,
    replaceRegexpOne(email, '^(.{0})[^@]*(@.*)$', '\\1****\\2') AS email,
    replaceRegexpOne(phone, '^(\\d{3})-(\\d{3})-(\\d{4})$', '\\1-***-\\3') AS phone,
    total_amount,
    order_date,
    replaceRegexpOne(shipping_address, '^[^,]+,\\s*(.*)$', '*** \\1') AS shipping_address
FROM orders;
위의 뷰 생성 쿼리의 SELECT 절에서는 부분적으로 마스킹하려는 민감한 정보가 포함된 name, email, phone, shipping_address 필드에 대해 replaceRegexpOne을 사용해 변환을 정의합니다. 뷰에서 데이터를 조회합니다:
Query
SELECT * FROM masked_orders
Response
┌─user_id─┬─name─────────┬─email──────────────┬─phone────────┬─total_amount─┬─order_date─┬─shipping_address──────────┐
│    1001 │ John ****    │ jo****@gmail.com   │ 555-***-4567 │       299.99 │ 2024-01-15 │ *** New York, NY 10001    │
│    1002 │ Sarah ****   │ sa****@outlook.com │ 555-***-6543 │        149.5 │ 2024-01-16 │ *** Los Angeles, CA 90210 │
│    1003 │ Michael **** │ mb****@company.com │ 555-***-7890 │          599 │ 2024-01-17 │ *** Chicago, IL 60601     │
│    1004 │ Emily ****   │ em****@yahoo.com   │ 555-***-0987 │        89.99 │ 2024-01-18 │ *** Houston, TX 77001     │
│    1005 │ David ****   │ dw****@email.net   │ 555-***-3210 │       449.75 │ 2024-01-19 │ *** Phoenix, AZ 85001     │
└─────────┴──────────────┴────────────────────┴──────────────┴──────────────┴────────────┴───────────────────────────┘
뷰에서 반환되는 데이터는 부분적으로 마스킹되어 민감한 정보가 가려진다는 점에 유의하십시오. 또한 정보를 조회하는 사용자의 권한 수준에 따라 난독화 수준이 서로 다른 여러 개의 뷰를 만들 수도 있습니다. 사용자가 마스킹된 데이터를 반환하는 뷰에만 접근하고, 원본의 마스킹되지 않은 데이터가 있는 테이블에는 접근하지 못하도록 하려면, 특정 역할에 뷰에 대한 SELECT 권한만 부여되도록 역할 기반 접근 제어를 사용해야 합니다. 먼저 역할을 생성합니다:
CREATE ROLE masked_orders_viewer;
다음으로, role에 뷰의 SELECT 권한을 부여합니다:
GRANT SELECT ON masked_orders TO masked_orders_viewer;
ClickHouse 역할은 누적되므로, 마스킹된 뷰만 볼 수 있어야 하는 사용자에게 어떤 역할을 통해서라도 기본 테이블(base table)에 대한 SELECT 권한이 없도록 해야 합니다. 따라서 안전을 위해 기본 테이블(base table) 접근 권한을 명시적으로 회수해야 합니다:
REVOKE SELECT ON orders FROM masked_orders_viewer;
마지막으로, 적절한 사용자에게 역할을 할당합니다:
GRANT masked_orders_viewer TO your_user;
이렇게 하면 masked_orders_viewer role이 있는 사용자는 뷰(view)의 마스킹된 데이터만 볼 수 있으며, 테이블(table)의 원래 마스킹되지 않은 데이터는 볼 수 없습니다.

MATERIALIZED 컬럼과 컬럼 수준 접근 제한 사용

별도의 뷰를 만들지 않고 원본 데이터와 함께 마스킹된 버전의 데이터를 저장할 수도 있습니다. 이를 위해 materialized 컬럼을 사용할 수 있습니다. 이러한 컬럼의 값은 행이 삽입될 때 지정된 materialized 표현식에 따라 자동으로 계산되며, 이를 사용해 데이터의 마스킹된 버전을 담은 새 컬럼을 만들 수 있습니다. 앞선 예시와 같이, 마스킹된 데이터를 위한 별도의 VIEW를 만드는 대신 이제 MATERIALIZED를 사용해 마스킹된 컬럼을 생성하겠습니다:
DROP TABLE IF EXISTS orders;
CREATE TABLE orders (
    user_id UInt32,
    name String,
    name_masked String MATERIALIZED replaceRegexpOne(name, '^([A-Za-z]+)\\s+(.*)$', '\\1 ****'),
    email String,
    email_masked String MATERIALIZED replaceRegexpOne(email, '^(.{0})[^@]*(@.*)$', '\\1****\\2'),
    phone String,
    phone_masked String MATERIALIZED replaceRegexpOne(phone, '^(\\d{3})-(\\d{3})-(\\d{4})$', '\\1-***-\\3'),
    total_amount Decimal(10,2),
    order_date Date,
    shipping_address String,
    shipping_address_masked String MATERIALIZED replaceRegexpOne(shipping_address, '^[^,]+,\\s*(.*)$', '*** \\1')
)
ENGINE = MergeTree()
ORDER BY user_id;

INSERT INTO orders VALUES
    (1001, 'John Smith', 'john.smith@gmail.com', '555-123-4567', 299.99, '2024-01-15', '123 Main St, New York, NY 10001'),
    (1002, 'Sarah Johnson', 'sarah.johnson@outlook.com', '555-987-6543', 149.50, '2024-01-16', '456 Oak Ave, Los Angeles, CA 90210'),
    (1003, 'Michael Brown', 'mbrown@company.com', '555-456-7890', 599.00, '2024-01-17', '789 Pine Rd, Chicago, IL 60601'),
    (1004, 'Emily Rogers', 'emily.rogers@yahoo.com', '555-321-0987', 89.99, '2024-01-18', '321 Elm St, Houston, TX 77001'),
    (1005, 'David Wilson', 'dwilson@email.net', '555-654-3210', 449.75, '2024-01-19', '654 Cedar Blvd, Phoenix, AZ 85001');
이제 다음 SELECT 쿼리를 실행하면 마스킹된 데이터가 삽입 시점에 ‘구체화되어’ 원래의 마스킹되지 않은 데이터와 함께 저장되는 것을 확인할 수 있습니다. ClickHouse는 기본적으로 SELECT * 쿼리에 materialized 컬럼을 자동으로 포함하지 않으므로, 마스킹된 컬럼은 명시적으로 선택해야 합니다.
Query
SELECT
    *,
    name_masked,
    email_masked,
    phone_masked,
    shipping_address_masked
FROM orders
ORDER BY user_id ASC
Response
   ┌─user_id─┬─name──────────┬─email─────────────────────┬─phone────────┬─total_amount─┬─order_date─┬─shipping_address───────────────────┬─name_masked──┬─email_masked───────┬─phone_masked─┬─shipping_address_masked────┐
1. │    1001 │ John Smith    │ john.smith@gmail.com      │ 555-123-4567 │       299.99 │ 2024-01-15 │ 123 Main St, New York, NY 10001    │ John ****    │ jo****@gmail.com   │ 555-***-4567 │ **** New York, NY 10001    │
2. │    1002 │ Sarah Johnson │ sarah.johnson@outlook.com │ 555-987-6543 │        149.5 │ 2024-01-16 │ 456 Oak Ave, Los Angeles, CA 90210 │ Sarah ****   │ sa****@outlook.com │ 555-***-6543 │ **** Los Angeles, CA 90210 │
3. │    1003 │ Michael Brown │ mbrown@company.com        │ 555-456-7890 │          599 │ 2024-01-17 │ 789 Pine Rd, Chicago, IL 60601     │ Michael **** │ mb****@company.com │ 555-***-7890 │ **** Chicago, IL 60601     │
4. │    1004 │ Emily Rogers  │ emily.rogers@yahoo.com    │ 555-321-0987 │        89.99 │ 2024-01-18 │ 321 Elm St, Houston, TX 77001      │ Emily ****   │ em****@yahoo.com   │ 555-***-0987 │ **** Houston, TX 77001     │
5. │    1005 │ David Wilson  │ dwilson@email.net         │ 555-654-3210 │       449.75 │ 2024-01-19 │ 654 Cedar Blvd, Phoenix, AZ 85001  │ David ****   │ dw****@email.net   │ 555-***-3210 │ **** Phoenix, AZ 85001     │
   └─────────┴───────────────┴───────────────────────────┴──────────────┴──────────────┴────────────┴────────────────────────────────────┴──────────────┴────────────────────┴──────────────┴────────────────────────────┘
사용자가 마스킹된 데이터가 포함된 컬럼에만 접근할 수 있도록 하려면, 역할 기반 접근 제어을 다시 사용해 특정 역할에 orders의 마스킹된 컬럼에 대해서만 SELECT 권한이 부여되도록 설정할 수 있습니다. 이전에 만든 역할을 다시 생성합니다:
DROP ROLE IF EXISTS masked_order_viewer;
CREATE ROLE masked_order_viewer;
다음으로, orders 테이블에 대한 SELECT 권한을 부여합니다:
GRANT SELECT ON orders TO masked_data_reader;
민감한 컬럼에 대한 접근 권한을 회수하세요:
REVOKE SELECT(name) ON orders FROM masked_data_reader;
REVOKE SELECT(email) ON orders FROM masked_data_reader;
REVOKE SELECT(phone) ON orders FROM masked_data_reader;
REVOKE SELECT(shipping_address) ON orders FROM masked_data_reader;
마지막으로, 해당 역할을 적절한 사용자에게 할당합니다:
GRANT masked_orders_viewer TO your_user;
orders 테이블에 마스킹된 데이터만 저장하려는 경우, 마스킹되지 않은 민감한 컬럼을 EPHEMERAL로 지정할 수 있으며, 이렇게 하면 이 유형의 컬럼은 테이블에 저장되지 않습니다.
DROP TABLE IF EXISTS orders;
CREATE TABLE orders (
    user_id UInt32,
    name String EPHEMERAL,
    name_masked String MATERIALIZED replaceRegexpOne(name, '^([A-Za-z]+)\\s+(.*)$', '\\1 ****'),
    email String EPHEMERAL,
    email_masked String MATERIALIZED replaceRegexpOne(email, '^(.{2})[^@]*(@.*)$', '\\1****\\2'),
    phone String EPHEMERAL,
    phone_masked String MATERIALIZED replaceRegexpOne(phone, '^(\\d{3})-(\\d{3})-(\\d{4})$', '\\1-***-\\3'),
    total_amount Decimal(10,2),
    order_date Date,
    shipping_address String EPHEMERAL,
    shipping_address_masked String MATERIALIZED replaceRegexpOne(shipping_address, '^([^,]+),\\s*(.*)$', '*** \\2')
)
ENGINE = MergeTree()
ORDER BY user_id;

INSERT INTO orders (user_id, name, email, phone, total_amount, order_date, shipping_address) VALUES
    (1001, 'John Smith', 'john.smith@gmail.com', '555-123-4567', 299.99, '2024-01-15', '123 Main St, New York, NY 10001'),
    (1002, 'Sarah Johnson', 'sarah.johnson@outlook.com', '555-987-6543', 149.50, '2024-01-16', '456 Oak Ave, Los Angeles, CA 90210'),
    (1003, 'Michael Brown', 'mbrown@company.com', '555-456-7890', 599.00, '2024-01-17', '789 Pine Rd, Chicago, IL 60601'),
    (1004, 'Emily Rogers', 'emily.rogers@yahoo.com', '555-321-0987', 89.99, '2024-01-18', '321 Elm St, Houston, TX 77001'),
    (1005, 'David Wilson', 'dwilson@email.net', '555-654-3210', 449.75, '2024-01-19', '654 Cedar Blvd, Phoenix, AZ 85001');
이제 앞서와 동일한 쿼리를 실행하면 마스킹된 데이터만 구체화되어 테이블에 삽입된 것을 확인할 수 있습니다:
Query
SELECT
    *,
    name_masked,
    email_masked,
    phone_masked,
    shipping_address_masked
FROM orders
ORDER BY user_id ASC
Response
   ┌─user_id─┬─total_amount─┬─order_date─┬─name_masked──┬─email_masked───────┬─phone_masked─┬─shipping_address_masked───┐
1. │    1001 │       299.99 │ 2024-01-15 │ John ****    │ jo****@gmail.com   │ 555-***-4567 │ *** New York, NY 10001    │
2. │    1002 │        149.5 │ 2024-01-16 │ Sarah ****   │ sa****@outlook.com │ 555-***-6543 │ *** Los Angeles, CA 90210 │
3. │    1003 │          599 │ 2024-01-17 │ Michael **** │ mb****@company.com │ 555-***-7890 │ *** Chicago, IL 60601     │
4. │    1004 │        89.99 │ 2024-01-18 │ Emily ****   │ em****@yahoo.com   │ 555-***-0987 │ *** Houston, TX 77001     │
5. │    1005 │       449.75 │ 2024-01-19 │ David ****   │ dw****@email.net   │ 555-***-3210 │ *** Phoenix, AZ 85001     │
   └─────────┴──────────────┴────────────┴──────────────┴────────────────────┴──────────────┴───────────────────────────┘

로그 데이터에 쿼리 마스킹 규칙 사용

특히 로그 데이터만 마스킹하려는 ClickHouse OSS 사용자는 데이터를 마스킹하기 위해 query masking rules(로그 마스킹)를 사용할 수 있습니다. 이를 위해 서버 구성에서 정규식 기반 마스킹 규칙을 정의할 수 있습니다. 이 규칙은 쿼리와 모든 로그 메시지가 서버 로그 또는 시스템 테이블(예: system.query_log, system.text_log, system.processes)에 저장되기 전에 적용됩니다. 이렇게 하면 민감한 데이터가 로그에만 유출되는 것을 방지할 수 있습니다. 쿼리 결과의 데이터는 마스킹되지 않는다는 점에 유의하십시오. 예를 들어 주민등록번호를 마스킹하려면 다음 규칙을 서버 구성에 추가할 수 있습니다:
<query_masking_rules>
    <rule>
        <name>hide SSN</name>
        <regexp>(^|\D)\d{3}-\d{2}-\d{4}($|\D)</regexp>
        <replace>000-00-0000</replace>
    </rule>
</query_masking_rules>
마지막 수정일 2026년 6월 10일