CREATE VIEW

CREATE VIEW

새로운 뷰를 만들어요. 뷰는 일반(normal), 머티리얼라이즈드(materialized), 갱신 가능 머티리얼라이즈드(refreshable materialized)일 수 있어요.

출처: 문서

본문

Normal View

Syntax:

CREATE [OR REPLACE] VIEW [IF NOT EXISTS] [db.]table_name [(alias1 [, alias2 ...])] [ON CLUSTER cluster_name]
[DEFINER = { user | CURRENT_USER }] [SQL SECURITY { DEFINER | INVOKER | NONE }]
AS SELECT ...
[COMMENT 'comment']

일반 뷰는 어떤 데이터도 저장하지 않아요. 접근할 때마다 다른 테이블에서 읽기를 수행할 뿐입니다. 즉, 일반 뷰는 저장된 쿼리에 지나지 않아요. 뷰에서 읽을 때 이 저장된 쿼리는 FROM 절의 서브쿼리로 사용됩니다.

예를 들어 뷰를 만들었다고 가정해요.

CREATE VIEW view AS SELECT ...

그리고 쿼리를 작성하세요.

SELECT a, b, c FROM view

이 쿼리는 서브쿼리를 사용하는 것과 완전히 동일해요.

SELECT a, b, c FROM (SELECT ...)

Parameterized View

파라미터화된 뷰는 일반 뷰와 비슷하지만, 즉시 해석되지 않는 파라미터로 만들 수 있어요. 이 뷰는 뷰의 이름을 함수 이름으로, 파라미터 값을 그 인자로 지정하는 테이블 함수와 함께 사용할 수 있어요.

CREATE VIEW view AS SELECT * FROM TABLE WHERE Column1={column1:datatype1} and Column2={column2:datatype2} ...

위는 아래와 같이 파라미터를 대체해 테이블 함수로 사용할 수 있는 테이블용 뷰를 만들어요.

SELECT * FROM view(column1=value1, column2=value2 ...)

파라미터화된 뷰는 파라미터 값에 의존하므로, 파라미터가 제공되지 않으면 스키마가 없어요. 즉, system.columns 테이블에는 파라미터화된 뷰에 대한 정보가 없습니다. 또한 DESCRIBE 쿼리는 파라미터가 제공될 때만 동작합니다.

DESCRIBE view(column1=value1, column2=value2 ...)

Materialized View

CREATE MATERIALIZED VIEW [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster_name] [TO[db.]name [(columns)]] [ENGINE = engine] [POPULATE]
[REFRESH ...]
[DEFINER = { user | CURRENT_USER }] [SQL SECURITY { DEFINER | NONE }]
AS SELECT ...
[COMMENT 'comment']
CREATE OR REPLACE MATERIALIZED VIEW [db.]table_name [ON CLUSTER cluster_name] [TO[db.]name [(columns)]] [ENGINE = engine] [POPULATE]
[REFRESH ...]
[DEFINER = { user | CURRENT_USER }] [SQL SECURITY { DEFINER | NONE }]
AS SELECT ...
[COMMENT 'comment']

OR REPLACEIF NOT EXISTS는 상호 배타적입니다. 둘을 결합하면 문법 오류예요.

CREATE OR REPLACE MATERIALIZED VIEW

CREATE OR REPLACE MATERIALIZED VIEW는 기존 머티리얼라이즈드 뷰와 그 내부 저장 테이블(있는 경우)을 원자적으로 교체해요. 이 연산에는 Atomic 또는 Replicated 데이터베이스 엔진이 필요합니다.

CREATE OR REPLACE MATERIALIZED VIEW [db.]name [ON CLUSTER cluster]
[TO [db.]target_table]
[ENGINE = engine]
[POPULATE]
[REFRESH ...]
AS SELECT ...

주요 동작:

  • TO 절 없이: 기존 내부 테이블을 버리고 새로 만든다. 내부 테이블의 기존 데이터는 POPULATE가 지정되지 않으면 유실됩니다.
  • TO 절과 함께: 뷰 정의만 교체된다. 대상 테이블과 그 데이터는 영향을 받지 않아요.
  • REFRESH, ON CLUSTER 및 모든 엔진 옵션과 호환됩니다. POPULATEAtomic 데이터베이스에서만 지원되며 Replicated 데이터베이스에서는 거부됩니다(아래 POPULATE 참고 참조).
  • CREATE VIEWDROP VIEW 권한이 필요합니다.

CREATE OR REPLACE MATERIALIZED VIEWAtomic 또는 Replicated 데이터베이스 엔진에서만 지원됩니다. Ordinary 데이터베이스 엔진에서는 지원되지 않아요.

예시:

-- Create a materialized view with an inner table
CREATE OR REPLACE MATERIALIZED VIEW mv
    ENGINE = MergeTree ORDER BY x
    AS SELECT x, sum(y) AS total FROM src GROUP BY x;

-- Replace with a new definition (old inner table data is lost)
CREATE OR REPLACE MATERIALIZED VIEW mv
    ENGINE = MergeTree ORDER BY x
    AS SELECT x, count() AS cnt FROM src GROUP BY x;

-- Replace with POPULATE to backfill from existing source data
CREATE OR REPLACE MATERIALIZED VIEW mv
    ENGINE = MergeTree ORDER BY x
    POPULATE
    AS SELECT x FROM src;

-- Replace an inner-table MV with a TO-table MV (target data is preserved)
CREATE OR REPLACE MATERIALIZED VIEW mv TO target
    AS SELECT x FROM src;

Materialized views 사용에 대한 단계별 가이드는 여기에 있어요.

머티리얼라이즈드 뷰는 해당 SELECT 쿼리에 의해 변환된 데이터를 저장해요.

TO [db].[table] 없이 머티리얼라이즈드 뷰를 만들 때는 데이터 저장을 위한 테이블 엔진인 ENGINE을 지정해야 합니다.

TO [db].[table]로 머티리얼라이즈드 뷰를 만들 때, POPULATE로 기존 소스 데이터에서 대상 테이블을 백필(backfill)할 수도 있어요(대상 테이블에 이미 데이터가 있을 수 있으며, 그 경우 백필된 행이 추가됩니다). POPULATEREFRESH와 결합할 수 없습니다. 갱신 가능 머티리얼라이즈드 뷰는 첫 번째 refresh로 채워지므로 POPULATE는 초기 데이터를 두 번 로드할 것입니다(첫 refresh를 건너뛰려면 EMPTY를 사용하세요).

머티리얼라이즈드 뷰는 이렇게 구현됩니다. SELECT에 지정된 테이블에 데이터를 삽입할 때, 삽입된 데이터의 일부가 이 SELECT 쿼리에 의해 변환되고 결과가 뷰에 삽입됩니다.

ClickHouse의 머티리얼라이즈드 뷰는 삽입 중 대상 테이블에 컬럼 순서 대신 컬럼 이름을 사용합니다. 일부 컬럼 이름이 SELECT 쿼리 결과에 없으면, 컬럼이 Nullable이 아니더라도 ClickHouse는 기본값을 사용합니다. 머티리얼라이즈드 뷰를 사용할 때 모든 컬럼에 별칭을 추가하는 것이 안전한 방법이에요.

ClickHouse의 머티리얼라이즈드 뷰는 더 삽입 트리거처럼 구현됩니다. 뷰 쿼리에 일부 집계가 있으면, 그것은 새로 삽입된 데이터 배치에만 적용됩니다. 소스 테이블의 기존 데이터에 대한 변경(예: update, delete, drop partition 등)은 머티리얼라이즈드 뷰를 변경하지 않아요.

ClickHouse의 머티리얼라이즈드 뷰는 오류 발생 시 결정적 동작을 가지지 않습니다. 이미 쓰여진 블록은 대상 테이블에 보존되지만, 오류 이후의 모든 블록은 보존되지 않는다는 뜻이에요.

기본적으로 뷰 중 하나로 푸시하는 것이 예외를 던지면 INSERT 쿼리가 실패합니다. 그 시점에 블록이 이미 소스 테이블에 도달했는지는 보장되지 않습니다. 삽입 파이프라인 타이밍에 달려 있고 뷰 오류에 달려 있지 않기 때문이에요. 삽입 중복 제거(insert_deduplicate, deduplicate_blocks_in_dependent_materialized_views)로 실패한 INSERT를 재시도하면 소스 테이블과 모든 종속 뷰에 정확히 한 번(eaxctly-once) 전달됩니다.

INSERT 쿼리에 materialized_views_ignore_errors=true 설정은 오류 보고만 변경합니다. 각 뷰 오류는 경고로 기록되고 INSERT 쿼리는 성공합니다. 실패한 뷰의 대상으로의 전달은 부분적입니다 — 예외 전에 처리된 블록은 유지되고, 실패한 블록과 이후 블록은 그 뷰에서 버려집니다. 그 대상의 다운스트림 뷰는 도착한 블록만 보므로 전달도 부분적입니다. 던지지 않은 형제 뷰(및 그 다운스트림 체인)는 완전히 기록되고, 소스 테이블은 평소처럼 기록됩니다. INSERT가 성공을 보고하므로 클라이언트는 실패 신호를 받지 않고 자동 재시도도 트리거되지 않습니다. 이 설정은 소스 테이블 쓰기가 뷰 쪽 문제로 막혀서는 안 될 때만 사용하세요(예: system.*_log 테이블).

materialized_views_ignore_errorssystem.*_log 테이블에서 기본값 true예요.

POPULATE를 지정하면 뷰를 만들 때 기존 소스 테이블 데이터가 뷰에 삽입됩니다. 그렇지 않으면 뷰는 뷰가 생성된 후 소스 테이블에 삽입된 데이터만 포함합니다.

일반 CREATE MATERIALIZED VIEW의 경우 POPULATE는 기본적으로 원자적입니다(설정 materialized_views_populate_atomically = 1). 뷰가 소스 테이블의 새 삽입에 구독되고, 기존 데이터의 스냅샷이 소스 테이블에 대한 짧은 배타적 잠금 아래에서 함께 찍혀 동시에 삽입된 모든 행이 뷰에 정확히 한 번 전달됩니다(놓치지도 않고 중복되지도 않습니다). (아마 오래 실행되는) population은 그 후 잠금 없이 고정된 스냅샷을 읽습니다.

이것은 로컬 삽입 경로 원자성입니다. 배타적 잠금은 같은 서버에서 이 소스 테이블의 저장소 잠금을 획득하는 삽입과만 직렬화되므로, 정확히 한 번 보장은 이 서버를 통해 도착하는 삽입만 다룹니다. 클러스터 전체 보장은 아닙니다 — ReplicatedMergeTree 소스의 다른 리플리카에서 삽입되거나 분산 쓰기 경로(Distributed 테이블이나 ON CLUSTER를 통한)로 삽입된 행은 이 컷오프 밖에 있어 여전히 놓치거나 중복될 수 있어요.

population이 실패하면 — 예를 들어 바쁜 소스 테이블에 대한 배타적 잠금을 lock_acquire_timeout 안에 얻지 못하거나 뷰의 SELECT가 실행 중에 예외를 던지면 — 방금 만든 뷰는 버려지고 CREATE 쿼리는 실패하며, 만든 것을 아무것도 남기지 않으므로 그냥 재시도할 수 있어요. TO [db].[table] 형태의 경우 이 롤백은 뷰만 버리고 기존 대상 테이블은 절대 버리지 않습니다. 그러나 실패한 population이 이미 대상에 삽입한 행은 그 테이블에 남아 있어, 그 테이블에 대한 실패한 INSERT ... SELECT 이후와 정확히 같습니다. 따라서 CREATE 재시도는 그 행들을 다시 삽입합니다. 백필이 정확해야 한다면 잘린(truncated) 또는 새 대상 테이블로 재시도하거나 ReplacingMergeTree 같은 중복 제거 엔진을 사용하세요.

원자성은 소스 테이블이 고정된 시점 스냅샷 읽기를 지원해야 합니다 — MergeTree 계열과 Memory. 다른 소스(뷰, Distributed, Merge, Buffer, Log 계열, 또는 Atomic 데이터베이스에 없는 테이블)의 경우 population은 레거시의 비원자적 동작으로 폴백합니다(서버 로그에 기록). 기존 데이터가 별도의 비조정 스냅샷으로 읽히므로 population 중에 삽입된 행은 놓치거나 중복될 수 있어요. 그 경우 정확한 데이터가 필요하면 뷰를 만들고 별도의 INSERT ... SELECT를 실행하세요. materialized_views_populate_atomically = 0으로 설정하면 모든 소스에 대해 이 레거시 동작을 강제합니다.

원자적 population은 일반 CREATE MATERIALIZED VIEW에만 적용됩니다. CREATE OR REPLACE / REPLACE MATERIALIZED VIEW ... POPULATE는 항상 레거시의 비원자적 population을 사용합니다.

POPULATEReplicated 데이터베이스에서 지원되지 않고(database_replicated_allow_heavy_create로 재정의 가능) ClickHouse Cloud에서도 지원되지 않습니다. 그 재정의를 통해 활성화되면 population은 항상 레거시의 비원자적 것입니다 — 실패한 population은 모든 리플리카에서 일관되게 롤백될 수 없기 때문이에요.

SELECT 쿼리는 DISTINCT, GROUP BY, ORDER BY, LIMIT를 포함할 수 있어요. 해당 변환이 삽입된 각 데이터 블록에 독립적으로 수행된다는 점에 유의하세요. 예를 들어 GROUP BY가 설정되면 삽입 중 데이터가 집계되지만, 삽입된 데이터의 단일 패킷 내에서만 집계됩니다. 데이터는 더 이상 집계되지 않습니다. SummingMergeTree처럼 독립적으로 데이터 집계를 수행하는 ENGINE을 사용할 때는 예외입니다.

머티리얼라이즈드 뷰가 TO [db.]name 구문을 사용하면 뷰를 DETACH하고 대상 테이블에 ALTER를 실행한 다음 이전에 분리한(DETACH) 뷰를 ATTACH할 수 있어요.

뷰는 일반 테이블과 동일하게 보여요. 예를 들어 SHOW TABLES 쿼리 결과에 나열됩니다.

뷰를 삭제하려면 DROP VIEW를 사용하세요. VIEW에 DROP TABLE이 동작하기는 하지만요.

SQL security

DEFINERSQL SECURITY는 뷰의 기본 쿼리를 실행할 때 사용할 ClickHouse 사용자를 지정할 수 있게 해줘요. SQL SECURITY에는 세 가지 합법적인 값이 있습니다: DEFINER, INVOKER, 또는 NONE. DEFINER 절에는 기존 사용자나 CURRENT_USER를 지정할 수 있어요.

다음 테이블은 뷰에서 select 하기 위해 어떤 사용자가 어떤 권한이 필요한지 설명합니다. SQL 보안 옵션과 무관하게, 어떤 경우든 읽기 위해서는 GRANT SELECT ON <view>가 여전히 필요하다는 점에 유의하세요.

SQL security option View Materialized View
DEFINER alice alice는 뷰의 소스 테이블에 대한 SELECT 권한이 있어야 해요. alice는 뷰의 소스 테이블에 대한 SELECT 권한과 뷰의 대상 테이블에 대한 INSERT 권한이 있어야 해요.
INVOKER 사용자는 뷰의 소스 테이블에 대한 SELECT 권한이 있어야 해요. 머티리얼라이즈드 뷰에는 SQL SECURITY INVOKER를 지정할 수 없어요.
NONE - -

SQL SECURITY NONE은 더 이상 사용되지 않는 옵션이에요. SQL SECURITY NONE으로 뷰를 만들 권리가 있는 모든 사용자는 임의 쿼리를 실행할 수 있게 됩니다. 따라서 이 옵션으로 뷰를 만들려면 GRANT ALLOW SQL SECURITY NONE TO <user>가 필요합니다.

DEFINER/SQL SECURITY가 지정되지 않으면 결과는 ignore_empty_sql_security_in_create_view_query 서버 설정에 따라 달라집니다.

기본값 true에서 쿼리는 작성된 그대로 저장되고 뷰는 빈 SQL security 유형을 얻습니다. 그러면 일반 뷰는 invoker의 권한으로 실행되고, 명시적으로 대상 테이블이 지정된 머티리얼라이즈드 뷰의 경우 그 대상 테이블에 대한 접근 검사는 건너뜁니다. 소스 테이블에 삽입할 때 대상 테이블에 대한 INSERT 권한이 필요하지 않고, 뷰에서 읽을 때 그에 대한 SELECT 권한이 필요하지 않습니다.

false에서 다음 기본값이 생성 시 뷰 정의에 기록됩니다:

갱신 가능 머티리얼라이즈드 뷰는 설정과 무관하게 항상 이러한 기본값을 받습니다.

뷰는 서버 시작 시 연결되거나 다시 로드될 때 저장된 정의의 SQL security 유형을 유지하므로, DEFINER/SQL SECURITY 없이 저장된 뷰는 빈 SQL security 유형을 유지합니다.

기존 뷰의 SQL security를 변경하려면 다음을 사용하세요.

ALTER TABLE MODIFY SQL SECURITY { DEFINER | INVOKER | NONE } [DEFINER = { user | CURRENT_USER }]

Examples

CREATE VIEW test_view
DEFINER = alice SQL SECURITY DEFINER
AS SELECT ...
CREATE VIEW test_view
SQL SECURITY INVOKER
AS SELECT ...

Live View

이 기능은 더 이상 사용되지 않으며 향후 제거될 예정이에요.

편의를 위해 이전 문서는 여기에 있습니다.

Refreshable Materialized View

CREATE MATERIALIZED VIEW [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster]
REFRESH [EVERY|AFTER interval [OFFSET interval]]
[RANDOMIZE FOR interval]
[DEPENDS ON [db.]name [, [db.]name [, ...]]]
[SETTINGS name = value [, name = value [, ...]]]
[APPEND [INCREMENTAL]]
[TO[db.]name] [(columns)] [ENGINE = engine]
[EMPTY]
[DEFINER = { user | CURRENT_USER }] [SQL SECURITY { DEFINER | NONE }]
AS SELECT ...
[COMMENT 'comment']

여기서 interval은 단순 간격의 시퀀스입니다:

number SECOND|MINUTE|HOUR|DAY|WEEK|MONTH|YEAR

REFRESH 절은 EVERY, AFTER, DEPENDS ON 중 적어도 하나를 지정해야 해요. 이들 중 아무것도 없는 맨 REFRESH는 거부됩니다. EVERY/AFTER 없이 REFRESH DEPENDS ON ...REFRESH AFTER 0 SECOND DEPENDS ON ...의 축약형입니다. 아래 Refresh Dependencies를 참고하세요.

해당 쿼리를 주기적으로 실행하고 그 결과를 테이블에 저장해요.

  • APPEND가 지정되면 각 refresh가 기존 행을 삭제하지 않고 테이블에 행을 삽입합니다. 삽입은 일반 INSERT INTO ... SELECT 쿼리처럼 원자적이지 않습니다.
  • APPEND INCREMENTAL이 지정되면 각 refresh가 이전 refresh 이후 소스 테이블에 커밋된 행에 대해서만 쿼리를 실행하고 결과를 추가합니다.
  • 그렇지 않으면 각 refresh가 테이블의 이전 내용을 원자적으로 교체합니다.

일반 비갱신 머티리얼라이즈드 뷰와의 차이:

  • 삽입 트리거 없음. SELECT에 지정된 테이블에 새 데이터가 삽입되어도 갱신 가능 머티리얼라이즈드 뷰로 자동 푸시되지 않습니다. 대신 데이터 삽입은 주기적 또는 수동 refresh 실행 중에만 일어납니다.
  • SELECT 쿼리에 대한 제한 없음. 테이블 함수(예: url()), 뷰, UNION, JOIN이 모두 허용됩니다. APPEND INCREMENTAL이 유일한 예외입니다. enable_block_number_column = 1enable_block_offset_column = 1인 단일 일반 MergeTree 소스 테이블이 필요하며, JOIN, UNION, 서브쿼리, 뷰, 테이블 함수를 거부합니다.

쿼리의 REFRESH ... SETTINGS 부분의 설정은 refresh 설정(예: refresh_retries)이며, 일반 설정(예: max_threads)과는 구별됩니다. 일반 설정은 쿼리 끝의 SETTINGS로 지정할 수 있어요.

Refresh Schedule

refresh 일정 예시:

REFRESH EVERY 1 DAY -- every day, at midnight (UTC)
REFRESH EVERY 1 MONTH -- on 1st day of every month, at midnight
REFRESH EVERY 1 MONTH OFFSET 5 DAY 2 HOUR -- on 6th day of every month, at 2:00 am
REFRESH EVERY 2 WEEK OFFSET 5 DAY 15 HOUR 10 MINUTE -- every other Saturday, at 3:10 pm
REFRESH EVERY 30 MINUTE -- at 00:00, 00:30, 01:00, 01:30, etc
REFRESH AFTER 30 MINUTE -- 30 minutes after the previous refresh completes, no alignment with time of day
-- REFRESH AFTER 1 HOUR OFFSET 1 MINUTE -- syntax error, OFFSET is not allowed with AFTER
REFRESH EVERY 1 WEEK 2 DAYS -- every 9 days, not on any particular day of the week or month;
                            -- specifically, when day number (since 1969-12-29) is divisible by 9
REFRESH EVERY 5 MONTHS -- every 5 months, different months each year (as 12 is not divisible by 5);
                       -- specifically, when month number (since 1970-01) is divisible by 5

RANDOMIZE FOR는 각 refresh의 시간을 무작위로 조정해요. 예:

REFRESH EVERY 1 DAY OFFSET 2 HOUR RANDOMIZE FOR 1 HOUR -- every day at random time between 01:30 and 02:30

주어진 뷰에 대해 한 번에 최대 한 개의 refresh가 실행될 수 있어요. 예: REFRESH EVERY 1 MINUTE인 뷰가 refresh에 2분이 걸리면 2분마다 refresh됩니다. 더 빨라져서 10초 만에 refresh되기 시작하면 매분 refresh로 돌아갑니다. (특히, 놓친 refresh의 백로그를 따라잡기 위해 10초마다 refresh되지는 않습니다 — 그런 백로그는 없어요.)

보통 첫 refresh는 머티리얼라이즈드 뷰가 생성된 직후 시작됩니다. 마지막 refresh 이후 시간이 무한이므로 어떤 일정이든 지금 refresh할 때라고 말하기 때문입니다. EMPTY가 지정되면 이 초기 refresh는 건너뛰고 첫 refresh가 다음 예정 시간에 일어납니다. 예: EVERY 1 HOUR의 경우 첫 refresh가 현재 시간의 끝에 일어납니다.

In Replicated DB

갱신 가능 머티리얼라이즈드 뷰가 Replicated 데이터베이스에 있으면 리플리카들이 서로 조정하여 각 예정 시간에 하나의 리플리카만 refresh를 수행합니다. 모든 리플리카가 refresh가 만든 데이터를 보도록 ReplicatedMergeTree 테이블 엔진이 필요합니다.

APPEND 모드에서 SETTINGS all_replicas = 1로 조정을 비활성화할 수 있어요. 이렇게 하면 리플리카들이 서로 독립적으로 refresh를 수행합니다. 이 경우 ReplicatedMergeTree가 필요하지 않습니다.

비-APPEND 모드에서는 조정된 refresh만 지원됩니다. 비조정을 원하면 Atomic 데이터베이스와 CREATE ... ON CLUSTER 쿼리를 사용해 모든 리플리카에 갱신 가능 머티리얼라이즈드 뷰를 만드세요.

조정은 Keeper를 통해 이루어집니다. znode 경로는 default_replica_path 서버 설정으로 결정됩니다.

Refresh Dependencies

DEPENDS ON은 다른 테이블의 refresh를 동기화합니다:

CREATE MATERIALIZED VIEW dependent REFRESH EVERY 1 HOUR DEPENDS ON dependency [...]

종속 뷰의 refresh는 모든 종속성 뷰의 refresh가 완료된 후에만 시작됩니다.

다른 뷰의 refresh 직후에 refresh하려면:

CREATE MATERIALIZED VIEW dependent REFRESH AFTER 0 SECOND DEPENDS ON dependency [...]

또는 동등하게:

CREATE MATERIALIZED VIEW dependent REFRESH DEPENDS ON dependency [...]

DEPENDS ON은 갱신 가능 머티리얼라이즈드 뷰 사이에서만 동작합니다. 특히 종속성 뷰가 TO <table>을 사용하면 테이블이 아니라 뷰의 이름을 사용해야 해요. DEPENDS ON 목록에 일반 테이블이나 비갱신 뷰가 있거나 오타가 있으면 뷰는 절대 refresh되지 않고 system.view_refreshesMissingDependencies 상태를 보여줍니다. 종속성은 ALTER로 변경하거나 제거할 수 있습니다(Changing Refresh Parameters 참조).

Using DEPENDS ON for consistent propagation latency

두 뷰 모두 같은 주기의 REFRESH EVERY를 사용하면 종속성이 각 timeslot에 적용됩니다.

예를 들어 뷰 X와 Y가 둘 다 REFRESH EVERY 1 HOUR를 사용하고 Y가 X의 출력 테이블을 읽는다고 가정해요. 종속성이 없으면 Y는 보통 이전 시간의 refresh에서 나온 X의 데이터를 보게 됩니다. DEPENDS ON X를 사용하면 Y의 11:00 refresh가 X의 11:00 refresh가 완료된 후에만 시작됩니다.

           10:00            11:00            12:00
           │                │                │
  X:        [run]┐           [run]┐           [run]┐
                 │                │                │
  Y:             └►[run]          └►[run]          └►[run]

refresh가 주기보다 오래 걸리면 종속성과 종속 뷰 모두 timeslot을 독립적으로 건너뛸 수 있어요. 종속 뷰가 각 종속성 refresh에 대해 정확히 한 번 refresh한다는 보장은 없습니다.

           10:00          11:00          12:00          13:00
           │              │              │              |
  X:        [run]┐         [run]┐         [run]┐         [run]┐
                 │              └────┐    (Y skips 12:00)     └───┐
  Y:             └►[10:00 ru------un]└►[11:00 ru---------------un]└►[13:00 run]

Using DEPENDS ON for batched stream processing

REFRESH EVERY를 사용하지 않으면, 종속 뷰 X는 모든 종속성이 X의 마지막 refresh 이후 적어도 한 번 refresh되었을 때 refresh됩니다. REFRESH AFTER T는 지연을 추가합니다. 종속 뷰는 종속성이 refresh를 완료한 후 T 시간 뒤에 refresh를 시작합니다.

순환 종속성은 허용되고 유용해요. 갱신 가능 머티리얼라이즈드 뷰의 이 그래프를 생각해 보세요.

  1. X가 어떤 스트림에서 행 배치를 가져와 테이블에 넣는다.
  2. 그런 다음 Y와 Z가 둘 다 그 테이블에서 읽고, 다른 집계를 수행하며, 결과를 다른 테이블에 추가한다.
  3. 배치가 완전히 처리된 후 X가 다음 배치를 가져오고, 주기가 반복된다.
            source
               │
               ▼
          ┌─────────┐
     ┌───►│    X    │◄───┐
     │    └──┬───┬──┘    │
  DEPENDS    │   │    DEPENDS
    ON       ▼   ▼      ON
     │      ┌─┐ ┌─┐      │
     └──────┤Y│ │Z├──────┘
            └─┘ └─┘

완전한 예시:

CREATE TABLE current_batch (t UInt64, v Int64) ENGINE ReplicatedMergeTree ORDER BY t;
CREATE TABLE batch_log (max_t UInt64, n Int64, v_sum Int64, processed_at DateTime64) ENGINE ReplicatedMergeTree ORDER BY max_t;
CREATE TABLE stats (h UInt64, n UInt64) ENGINE ReplicatedSummingMergeTree ORDER BY h;

-- (system.numbers stands in for a data source with monotonically increasing timestamps or sequence numbers)
CREATE MATERIALIZED VIEW current_batch_v REFRESH EVERY 10 SECOND DEPENDS ON batch_log_v, stats_v TO current_batch AS SELECT number as t, number * 10 as v FROM system.numbers WHERE number > (SELECT max(max_t) FROM batch_log) LIMIT 100;

CREATE MATERIALIZED VIEW batch_log_v REFRESH DEPENDS ON current_batch_v APPEND TO batch_log AS SELECT max(t) as max_t, count() as n, sum(v) as v_sum, now64() as processed_at FROM current_batch;

CREATE MATERIALIZED VIEW stats_v REFRESH DEPENDS ON current_batch_v APPEND TO stats AS SELECT cityHash64(v) % 20 as h, count() as n FROM current_batch GROUP BY h;

-- Must trigger initial refresh manually.
SYSTEM REFRESH VIEW current_batch_v;

더 긴 체인도 잘 동작합니다.

이것은 refresh 조정이 활성화된 경우, 즉 뷰가 Replicated 또는 Shared 데이터베이스에 있을 때만 잘 동작합니다. 조정이 없으면 서버 재시작이 주기를 깨뜨려, 뷰를 만든 후 한 번이 아니라 재시작마다 수동 SYSTEM REFRESH VIEW가 필요합니다.

Refresh Settings

사용 가능한 refresh 설정:

  • refresh_retries - refresh 쿼리가 예외로 실패할 때 재시도할 횟수. 모든 재시도가 실패하면 다음 예정 refresh 시간으로 건너뜁니다. 0은 재시도 없음, -1은 무한 재시도를 의미합니다. 기본: 2.
  • refresh_retry_initial_backoff_ms - refresh_retries가 0이 아니면 첫 재시도 전 지연. 이후 각 재시도는 refresh_retry_max_backoff_ms까지 지연을 두 배로 늘립니다. 기본: 100 ms.
  • refresh_retry_max_backoff_ms - refresh 시도 사이 지연의 지수적 증가 한도. 기본: 60000 ms (1분).
  • all_replicas - APPENDReplicated 데이터베이스에서, 모든 리플리카가 독립적으로 refresh할지 또는 각 예정 시간에 하나의 리플리카만 refresh할지 제어합니다. 뷰가 생성된 후에는 변경할 수 없습니다. 기본: false.

Changing Refresh Parameters

기존 갱신 가능 머티리얼라이즈드 뷰의 refresh 파라미터는 ALTER TABLE ... MODIFY REFRESH로 변경합니다:

ALTER TABLE [db.]name MODIFY REFRESH EVERY|AFTER ... [RANDOMIZE FOR ...] [DEPENDS ON ...] [SETTINGS ...]

일정(EVERY 또는 AFTER)은 필수입니다. 이 문은 항상 모든 refresh 파라미터 — 일정, RANDOMIZE FOR, DEPENDS ON, refresh 설정 — 를 지정된 것으로 교체합니다. 생략된 것은 기본값(설정)으로 재설정되거나 제거됩니다(종속성, 무작위화).

  • refresh 설정만 변경하려면(예: refresh_retries) 기존 일정을 반복하세요: ALTER TABLE rmv MODIFY REFRESH EVERY 1 HOUR SETTINGS refresh_retries = 5;
  • 머티리얼라이즈드 뷰에서는 ALTER TABLE ... MODIFY SETTING refresh_retries = ...가 지원되지 않습니다. 반드시 MODIFY REFRESH를 거쳐야 해요.
  • refresh 모드 변경은 지원되지 않습니다. APPENDINCREMENTAL은 추가하거나 제거할 수 없습니다.
  • all_replicas 설정은 생성 후 변경할 수 없습니다.

예시:

-- Change the schedule, drop existing settings and dependencies.
ALTER TABLE rmv MODIFY REFRESH EVERY 30 MINUTE;

-- Change the schedule and tune retry behavior.
ALTER TABLE rmv MODIFY REFRESH EVERY 30 MINUTE
SETTINGS refresh_retries = 5,
         refresh_retry_initial_backoff_ms = 500,
         refresh_retry_max_backoff_ms = 60000;

-- Keep the dependency while changing the period.
ALTER TABLE rmv MODIFY REFRESH EVERY 6 HOUR DEPENDS ON other_rmv;

-- Drop the dependency by omitting `DEPENDS ON`.
ALTER TABLE rmv MODIFY REFRESH EVERY 6 HOUR;

Other operations

모든 갱신 가능 머티리얼라이즈드 뷰의 상태는 system.view_refreshes 테이블에서 볼 수 있어요. 특히 refresh 진행 상황(실행 중이라면), 마지막 및 다음 refresh 시간, refresh가 실패하면 예외 메시지를 포함합니다.

refresh를 수동으로 멈추거나, 시작하거나, 트리거하거나, 취소하려면 SYSTEM STOP|START|REFRESH|WAIT|CANCEL VIEW를 사용하세요.

refresh 완료를 기다리려면 SYSTEM WAIT VIEW를 사용하세요. 특히 뷰 생성 후 초기 refresh를 기다릴 때 유용해요.

재미있는 사실: refresh 쿼리는 refresh되고 있는 뷰 자체를 읽어도 되며, refresh 전 버전의 데이터를 봅니다. 이는 Conway의 생명 게임을 구현할 수 있다는 뜻이에요: https://pastila.nl/?00021a4b/d6156ff819c83d490ad2dcec05676865#O0LGWTO7maUQIA4AcGUtlA==

Temporary Views

ClickHouse는 다음 특성으로 임시 뷰를 지원합니다(해당하는 곳에서는 임시 테이블과 일치).

  • 세션 수명 임시 뷰는 현재 세션 동안만 존재합니다. 세션이 끝나면 자동으로 버려집니다.
  • 데이터베이스 없음 임시 뷰는 데이터베이스 이름으로 한정할 수 없습니다. 데이터베이스 외부(세션 네임스페이스)에 존재합니다.
  • 복제 없음 / ON CLUSTER 없음 임시 객체는 세션에 로컬이며 ON CLUSTER로 만들 수 없습니다.
  • 이름 해석 임시 객체(테이블 또는 뷰)가 영구 객체와 같은 이름을 가지고, 쿼리가 데이터베이스 없이 이름을 참조하면 임시 객체가 사용됩니다.
  • 논리 객체(저장소 없음) 임시 뷰는 SELECT 텍스트만 저장합니다(내부적으로 View 저장소를 사용). 데이터를 유지하지 않고 INSERT를 받지 못합니다.
  • 엔진 절 ENGINE을 지정할 필요가 없으며, ENGINE = View로 제공되면 무시/같은 논리 뷰로 취급됩니다.
  • 보안 / 권한 임시 뷰를 만들려면 CREATE VIEW가 암시적으로 부여하는 CREATE TEMPORARY VIEW 권한이 필요합니다.
  • SHOW CREATE 임시 뷰의 DDL을 출력하려면 SHOW CREATE TEMPORARY VIEW view_name;를 사용하세요.

Syntax

CREATE TEMPORARY VIEW [IF NOT EXISTS] view_name AS <select_query>

OR REPLACE는 임시 뷰에서 지원되지 않습니다(임시 테이블과 일치하도록). 임시 뷰를 "교체"해야 한다면 버리고 다시 만드세요.

Examples

임시 소스 테이블과 그 위의 임시 뷰 만들기:

CREATE TEMPORARY TABLE t_src (id UInt32, val String);
INSERT INTO t_src VALUES (1, 'a'), (2, 'b');

CREATE TEMPORARY VIEW tview AS
SELECT id, upper(val) AS u
FROM t_src
WHERE id <= 2;

SELECT * FROM tview ORDER BY id;

그 DDL 표시:

SHOW CREATE TEMPORARY VIEW tview;

버리기:

DROP TEMPORARY VIEW IF EXISTS tview;  -- temporary views are dropped with TEMPORARY TABLE syntax

Disallowed / limitations

  • CREATE OR REPLACE TEMPORARY VIEW ... → 허용되지 않음(DROP + CREATE 사용).
  • CREATE TEMPORARY MATERIALIZED VIEW ... → 허용되지 않음.
  • CREATE TEMPORARY VIEW db.view AS ... → 허용되지 않음(데이터베이스 한정자 없음).
  • CREATE TEMPORARY VIEW view ON CLUSTER 'name' AS ... → 허용되지 않음(임시 객체는 세션-로컬).
  • POPULATE, REFRESH, TO [db.table], 내부 엔진, 모든 MV 특정 절 → 임시 뷰에 적용되지 않음.

Notes on distributed queries

임시 뷰는 정의일 뿐입니다. 전달할 데이터가 없어요. 임시 뷰가 임시 테이블(예: Memory)을 참조하면, 그 데이터는 임시 테이블이 동작하는 것과 같은 방식으로 분산 쿼리 실행 중에 원격 서버로 전송될 수 있어요.

Example

-- A session-scoped, in-memory table
CREATE TEMPORARY TABLE temp_ids (id UInt64) ENGINE = Memory;

INSERT INTO temp_ids VALUES (1), (5), (42);

-- A session-scoped view over the temp table (purely logical)
CREATE TEMPORARY VIEW v_ids AS
SELECT id FROM temp_ids;

-- Replace 'test' with your cluster name.
-- GLOBAL JOIN forces ClickHouse to *ship* the small join-side (temp_ids via v_ids)
-- to every remote server that executes the left side.
SELECT count()
FROM cluster('test', system.numbers) AS n
GLOBAL ANY INNER JOIN v_ids USING (id)
WHERE n.number < 100;

더 알아보기 (Learn more)