HyperSec XDR Data Engine

Schema Builder Guide

Building Schemas

Introduction

This document provides an in-depth guide on building and optimizing schemas in HyperSec XDR. It is intended for data engineers and developers who need to design efficient, scalable, and performant schemas for handling large volumes of data in real-time analytics.

We will cover how to:

Schema Design Principles

Designing an effective schema in ClickHouse involves understanding both the nature of your data and the querying patterns. Key principles include:

Choosing Data Types

Selecting the right data types is crucial for performance and storage efficiency. Below is a mapping of logical types to ClickHouse types, along with OpenSearch equivalents and comments.

Data Type Mappings

Logical Type ClickHouse Type Comment
string String CODEC(ZSTD(1)) Standard strings with compression for storage efficiency.
string_fast String CODEC(LZ4) Strings that are heavily queried; LZ4 offers faster decompression at the expense of larger storage size.
string_lowcardinality LowCardinality(String) CODEC(ZSTD(1)) For strings with fewer distinct values (<1 million); improves performance and reduces storage.
string_fast_lowcardinality LowCardinality(String) CODEC(LZ4) Heavily queried low cardinality strings; faster access with acceptable storage overhead.
text String CODEC(ZSTD(1)) Larger strings and text fields that may not be used in exact match queries.
json String CODEC(ZSTD(1)) Store JSON objects as strings; move to native JSON type once it is GA.
int8 Nullable(Int8) CODEC(ZSTD(1)) 8-bit integers; nullable fields are supported.
int16 Nullable(Int16) CODEC(ZSTD(1)) 16-bit integers.
int32 Nullable(Int32) CODEC(ZSTD(1)) 32-bit integers.
int64 Nullable(Int64) CODEC(ZSTD(1)) 64-bit integers.
int128 Nullable(Int128) CODEC(ZSTD(1)) 128-bit integers; for very large numbers.
int256 Nullable(Int256) CODEC(ZSTD(1)) 256-bit integers.
float32 Nullable(Float32) CODEC(ZSTD(1)) 32-bit floating-point numbers.
float64 Nullable(Float64) CODEC(ZSTD(1)) 64-bit floating-point numbers.
boolean Nullable(Boolean) CODEC(LZ4) Boolean values; LZ4 codec for fast access.
timestamp DateTime64(3, 'UTC') CODEC(DoubleDelta, LZ4) Timeseries increasing timestamps (e.g., timestamp, timestamp_load); required and not nullable.
datetime Nullable(DateTime64(3, 'UTC')) CODEC(DoubleDelta, ZSTD(1)) All other date and timestamp fields; nullable.
ipv4 Nullable(IPv4) CODEC(T64, LZ4) IPv4 addresses.
ipv6 Nullable(IPv6) CODEC(LZ4) IPv6 addresses.
geo_point Nullable(Point) CODEC(ZSTD(1)) Geographical points (latitude and longitude).
tuple Nullable(Tuple) CODEC(ZSTD(1)) Tuples representing a fixed set of elements.
map Nullable(Map) CODEC(ZSTD(1)) Key-value pairs.
uuid Nullable(UUID) CODEC(ZSTD(1)) Supports all GUID types in OpenSearch.

Guidelines for Choosing Data Types

Compression Codecs

Nullable Fields

Choosing ORDER BY Clauses

In ClickHouse, the ORDER BY clause is fundamental as it determines both how data is sorted on disk and how the primary index is built. This ordering directly impacts query performance, data compression efficiency, and overall storage requirements.

Best Practices for Choosing Columns for ORDER BY and PRIMARY KEY

When selecting columns for the ORDER BY clause, consider the following guidelines based on ClickHouse's official recommendations:

1. Align with Common Query Filters

2. Prefer Columns with Low Cardinality Early in the ORDER BY

3. Consider Data Correlation

4. Use Expressions for Efficiency

Handling Nullable Fields

Deciding on the Order of Columns

The order of columns in the ORDER BY clause significantly impacts both query performance and compression efficiency.

Ordering by Increasing Cardinality

Balancing with Query Patterns

Examples

Example 1: Optimized for Compression

CREATE TABLE logs_events (
    event_type LowCardinality(String),
    timestamp DateTime64(3, 'UTC'),
    org_id UInt32,
    user_id UInt64,
    message String CODEC(ZSTD(1)),
    -- Additional fields...
) ENGINE = MergeTree()
ORDER BY (event_type, toDate(timestamp), org_id);

Example 2: Optimized for Query Performance

CREATE TABLE logs_events (
    org_id UInt32,
    timestamp DateTime64(3, 'UTC'),
    event_type LowCardinality(String),
    user_id UInt64,
    message String CODEC(ZSTD(1)),
    -- Additional fields...
) ENGINE = MergeTree()
ORDER BY (org_id, timestamp, event_type);

Applying ClickHouse's Recommendations

From the official documentation:

Testing and Iteration

Conclusion

Choosing the optimal ORDER BY and PRIMARY KEY in ClickHouse requires balancing multiple factors:

By carefully considering these aspects and leveraging ClickHouse's capabilities, you can design schemas that provide efficient storage and high-performance query execution.


Updated Section in Documentation

Choosing ORDER BY Clauses

The ORDER BY clause in ClickHouse determines how data is sorted on disk, which directly impacts query performance and storage efficiency. Proper ordering enables ClickHouse to skip reading unnecessary data during queries (data skipping) and improves compression ratios.

Guidelines for ORDER BY

  1. Align with Common Query Filters * Select Frequently Queried Columns: Include columns that are commonly used in WHERE clauses. * Maximize Data Skipping: Choose columns that, when filtered, can exclude large portions of data.
  2. Consider Data Cardinality * Order by Increasing Cardinality: Generally, place low-cardinality columns before high-cardinality ones to improve compression. * Balance with Query Needs: If high-cardinality columns are essential for query performance, they may need to be placed earlier.
  3. Data Correlation * Include Correlated Columns: Columns that correlate with others can improve compression when ordered together.
  4. Use Expressions for Effective Partitioning * Reduce Granularity: Use functions like toDate(timestamp) to reduce data granularity, effectively partitioning the data.
  5. Avoid Nullable Columns * Non-Nullable in ORDER BY: Ensure columns in the ORDER BY clause are non-nullable.

Deciding on Column Order

Examples

Example Table Definition

CREATE TABLE logs_events (
    event_type LowCardinality(String),
    timestamp DateTime64(3, 'UTC'),
    org_id UInt32,
    message String CODEC(ZSTD(1)),
    -- Additional fields...
) ENGINE = MergeTree()
ORDER BY (event_type, toDate(timestamp), org_id);

Choosing Index Types

ClickHouse offers secondary indexes to accelerate queries that cannot be efficiently served by the primary key alone.

Types of Indexes

Guidelines for Index Selection

Defining an Index

Example

Best Practices

Examples

Defining a Table with Optimal Data Types and Indexes

CREATE TABLE xdr_12345678.logs_beats_winlogbeat (
    timestamp DateTime64(3, 'UTC') CODEC(DoubleDelta, LZ4),
    org_id String CODEC(ZSTD(1)),
    event_hash String CODEC(ZSTD(1)),
    user_id LowCardinality(String) CODEC(ZSTD(1)),
    event_type String CODEC(ZSTD(1)),
    source_ip IPv4 CODEC(T64, LZ4),
    destination_ip IPv4 CODEC(T64, LZ4),
    message String CODEC(ZSTD(1)),
    -- Additional fields...
    INDEX idx_event_type (event_type) TYPE set(1000) GRANULARITY 4,
    INDEX idx_user_id (user_id) TYPE bloom_filter(0.01) GRANULARITY 64
) ENGINE = MergeTree()
ORDER BY (org_id, timestamp);

Explanation

Conclusion

Designing schemas in ClickHouse for HyperSec XDR involves careful consideration of data types, compression codecs, ordering, and indexing. By following the guidelines provided in this document, you can build efficient and scalable schemas that meet your real-time analytics needs.

Additional Resources