> ## Documentation Index
> Fetch the complete documentation index at: https://www.propeldata.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Partition key

> Example section for showcasing API endpoints

<div />

In this guide, you'll learn how to select the optimal partition key for your table in Propel's Serverless ClickHouse.

<Note>
  If your table is smaller than <b>10 GB</b>, you don't need to customize the partition key and can skip this guide. 🥳
</Note>

## Understanding the partition key

ClickHouse can easily handle tables with hundreds of millions of rows; however, for tables that exceed 10 GB in storage, it's recommended to define an optional partition key that will organize the table's rows into a few logical groups, or partitions. ClickHouse can then operate on the smaller partitions individually, instead of the entire table, speeding up data access and manipulation.

The number of partitions should not be too many, since accessing too many partitions in a single query can hurt performance or fail the query entirely. Ideally, INSERT and SELECT queries should access one or a few partitions at a time. For this reason, partition keys should be low cardinality, resulting in dozens up to hundreds of partitions, not thousands. For example, typical partition keys will partition by month, by date, or event type.

```mermaid theme={"system"}
graph TD
  T["Table"] --> PN1["Partition 1"]
  T --> PN2["Partition 2"]
  T -.-> PNe["…"]
  T --> PNn["Partition N"]

  PN2 --> PT1["Part 1"]
  PN2 --> PT2["Part 2"]
  PN2 -.-> PTe["…"]
  PN2 --> PTn["Part N"]

  style T fill:#E1D9FF
  style PN1 fill:#DADCFF
  style PN2 fill:#DADCFF
  style PNe fill:#DADCFF
  style PNn fill:#DADCFF
  style PT1 fill:#D2DEFF
  style PT2 fill:#D2DEFF
  style PTe fill:#D2DEFF
  style PTn fill:#D2DEFF
```

Diagram of a ClickHouse table. The table contains partitions, and each partition contains [parts](#what-is-a-part-in-clickhouse).

## How to choose the right partition key for your table

In the following section, we will provide you with guidelines and considerations to keep in mind when selecting the most suitable partition key for your table.

First, let's begin with some general guidelines that apply regardless of the data size and table engine:

* A SELECT query should not touch more than a few dozen partitions.
* The number of partitions in a table should be in the dozens or hundreds, not thousands.
* A single INSERT should deliver data to one or a few partitions.

### Small tables (smaller than 10 GB)

Partitioning is usually unnecessary for tables smaller than 10 gigabytes. Having no partition is better than having the wrong one.

So, if your table has 5 GB of data, for example, you can leave it empty and stop reading 🥳.

## Append-only data in MergeTree tables

For larger MergeTree tables (greater than 10 GB), it's crucial to select an appropriate partition key. As a general guideline, a single partition for MergeTree tables should be no more than 300 GB.

The following recommendations vary depending on the type of data: time series or incremental data.

#### Time series data

For time series data, you should choose a partition key based on your primary timestamp and data volume. Most of the time monthly partitioning is sufficient, and so Propel recommends this as a default, but if monthly data would exceed 300 GB, you should use weekly partitioning; etc.

Given a `timestamp` column, you can configure these partitioning schemes as follows:

* Monthly: `toYYYYMM(timestamp)`

  <Tabs>
    <Tab title="Console">
      <Frame>
        <img src="https://mintcdn.com/propeldocs/YxRDddM1lFWvUiPZ/images/docs/2024-06-10-partition-key-guide-console-01.png?fit=max&auto=format&n=YxRDddM1lFWvUiPZ&q=85&s=4d3dffd53ec468039e3742a6a04b4794" alt="Monthly partition key in ClickHouse" width="1552" height="285" data-path="images/docs/2024-06-10-partition-key-guide-console-01.png" />
      </Frame>
    </Tab>

    <Tab title="API">
      ```graphql theme={"system"}
      mutation {
        createDataPoolV2(
          input: {
            uniqueName: "events2"
            description: "Data Pool for events"
            timestamp: { columnName: "timestamp" }
            columns: [
              { columnName: "timestamp", type: TIMESTAMP, isNullable: true }
              { columnName: "customer_id", type: TIMESTAMP, isNullable: true }
              { columnName: "workspace_id", type: STRING, isNullable: true }
              { columnName: "event_type", type: STRING, isNullable: true }
              { columnName: "payload", type: JSON, isNullable: true }
            ]
            tableSettings: {
              engine: {
                mergeTree: {
                  type: MERGE_TREE
                }
              }
              partitionBy: ["toYYYYMM(timestamp)"]
            }
          }
        ) {
          dataPool {
            id
            uniqueName
            description
            tableSettings {
              orderBy
            }
          }
        }
      }
      ```
    </Tab>

    <Tab title="Terraform">
      ```hcl theme={"system"}
      resource "propel_data_pool" "events2" {
        unique_name = "events2"
        description = "Data Pool for events"
        timestamp   = "timestamp"

        column {
          name     = "timestamp"
          type     = "TIMESTAMP"
          nullable = true
        }
        column {
          name     = "customer_id"
          type     = "TIMESTAMP"
          nullable = true
        }
        column {
          name     = "workspace_id"
          type     = "STRING"
          nullable = true
        }
        column {
          name     = "event_type"
          type     = "STRING"
          nullable = true
        }
        column {
          name     = "payload"
          type     = "JSON"
          nullable = true
        }

        table_settings {
          engine {
            merge_tree {
              type = "MERGE_TREE"
            }
          }
          partition_by = ["toYYYYMM(timestamp)"]
        }
      }
      ```
    </Tab>
  </Tabs>

  ***

* Weekly: `toStartOfWeek(timestamp)`

  <Tabs>
    <Tab title="Console">
      <Frame>
        <img src="https://mintcdn.com/propeldocs/YxRDddM1lFWvUiPZ/images/docs/2024-06-10-partition-key-guide-console-02.png?fit=max&auto=format&n=YxRDddM1lFWvUiPZ&q=85&s=617d8171cd0d5a6fcb6983953156ed1c" alt="Weekly partition key in ClickHouse" width="1539" height="285" data-path="images/docs/2024-06-10-partition-key-guide-console-02.png" />
      </Frame>
    </Tab>

    <Tab title="API">
      ```graphql theme={"system"}
      mutation {
        createDataPoolV2(
          input: {
            uniqueName: "events2"
            description: "Data Pool for events"
            timestamp: { columnName: "timestamp" }
            columns: [
              { columnName: "timestamp", type: TIMESTAMP, isNullable: true }
              { columnName: "customer_id", type: TIMESTAMP, isNullable: true }
              { columnName: "workspace_id", type: STRING, isNullable: true }
              { columnName: "event_type", type: STRING, isNullable: true }
              { columnName: "payload", type: JSON, isNullable: true }
            ]
            tableSettings: {
              engine: {
                mergeTree: {
                  type: MERGE_TREE
                }
              }
              partitionBy: ["toStartOfWeek(timestamp)"]
            }
          }
        ) {
          dataPool {
            id
            uniqueName
            description
            tableSettings {
              orderBy
            }
          }
        }
      }
      ```
    </Tab>

    <Tab title="Terraform">
      ```hcl theme={"system"}
      resource "propel_data_pool" "events2" {
        unique_name = "events2"
        description = "Data Pool for events"
        timestamp   = "timestamp"

        column {
          name     = "timestamp"
          type     = "TIMESTAMP"
          nullable = true
        }
        column {
          name     = "customer_id"
          type     = "TIMESTAMP"
          nullable = true
        }
        column {
          name     = "workspace_id"
          type     = "STRING"
          nullable = true
        }
        column {
          name     = "event_type"
          type     = "STRING"
          nullable = true
        }
        column {
          name     = "payload"
          type     = "JSON"
          nullable = true
        }

        table_settings {
          engine {
            merge_tree {
              type = "MERGE_TREE"
            }
          }
          partition_by = ["toStartOfWeek(timestamp)"]
        }
      }
      ```
    </Tab>
  </Tabs>

  ***

* Daily: `toDate(timestamp)`

  <Tabs>
    <Tab title="Console">
      <Frame>
        <img src="https://mintcdn.com/propeldocs/YxRDddM1lFWvUiPZ/images/docs/2024-06-10-partition-key-guide-console-03.png?fit=max&auto=format&n=YxRDddM1lFWvUiPZ&q=85&s=b3aa5823da147a83361a83f6b9894c79" alt="Daily partition key in ClickHouse" width="1545" height="284" data-path="images/docs/2024-06-10-partition-key-guide-console-03.png" />
      </Frame>
    </Tab>

    <Tab title="API">
      ```graphql theme={"system"}
      mutation {
        createDataPoolV2(
          input: {
            uniqueName: "events2"
            description: "Data Pool for events"
            timestamp: { columnName: "timestamp" }
            columns: [
              { columnName: "timestamp", type: TIMESTAMP, isNullable: true }
              { columnName: "customer_id", type: TIMESTAMP, isNullable: true }
              { columnName: "workspace_id", type: STRING, isNullable: true }
              { columnName: "event_type", type: STRING, isNullable: true }
              { columnName: "payload", type: JSON, isNullable: true }
            ]
            tableSettings: {
              engine: {
                mergeTree: {
                  type: MERGE_TREE
                }
              }
              partitionBy: ["toDate(timestamp)"]
            }
          }
        ) {
          dataPool {
            id
            uniqueName
            description
            tableSettings {
              orderBy
            }
          }
        }
      }
      ```
    </Tab>

    <Tab title="Terraform">
      ```hcl theme={"system"}
      resource "propel_data_pool" "events2" {
        unique_name = "events2"
        description = "Data Pool for events"
        timestamp   = "timestamp"

        column {
          name     = "timestamp"
          type     = "TIMESTAMP"
          nullable = true
        }
        column {
          name     = "customer_id"
          type     = "TIMESTAMP"
          nullable = true
        }
        column {
          name     = "workspace_id"
          type     = "STRING"
          nullable = true
        }
        column {
          name     = "event_type"
          type     = "STRING"
          nullable = true
        }
        column {
          name     = "payload"
          type     = "JSON"
          nullable = true
        }

        table_settings {
          engine {
            merge_tree {
              type = "MERGE_TREE"
            }
          }
          partition_by = ["toDate(timestamp)"]
        }
      }
      ```
    </Tab>
  </Tabs>

  ***

* Hourly: `toStartOfHour(timestamp)`

  <Tabs>
    <Tab title="Console">
      <Frame>
        <img src="https://mintcdn.com/propeldocs/G8bGSg66eBpnYaHB/images/docs/2024-06-10-partition-key-guide-console-04.png?fit=max&auto=format&n=G8bGSg66eBpnYaHB&q=85&s=b7a4ad320f4ea8cc86e8ccdc01126adb" alt="Hourly partition key in ClickHouse" width="1546" height="284" data-path="images/docs/2024-06-10-partition-key-guide-console-04.png" />
      </Frame>
    </Tab>

    <Tab title="API">
      ```graphql theme={"system"}
        mutation {
        createDataPoolV2(
          input: {
            uniqueName: "events2"
            description: "Data Pool for events"
            timestamp: { columnName: "timestamp" }
            columns: [
              { columnName: "timestamp", type: TIMESTAMP, isNullable: true }
              { columnName: "customer_id", type: TIMESTAMP, isNullable: true }
              { columnName: "workspace_id", type: STRING, isNullable: true }
              { columnName: "event_type", type: STRING, isNullable: true }
              { columnName: "payload", type: JSON, isNullable: true }
            ]
            tableSettings: {
              engine: {
                mergeTree: {
                  type: MERGE_TREE
                }
              }
              partitionBy: ["toStartOfHour(timestamp)"]
            }
          }
        ) {
          dataPool {
            id
            uniqueName
            description
            tableSettings {
              orderBy
            }
          }
        }
      }
      ```
    </Tab>

    <Tab title="Terraform">
      ```hcl theme={"system"}
      resource "propel_data_pool" "events2" {
        unique_name = "events2"
        description = "Data Pool for events"
        timestamp   = "timestamp"

        column {
          name     = "timestamp"
          type     = "TIMESTAMP"
          nullable = true
        }
        column {
          name     = "customer_id"
          type     = "TIMESTAMP"
          nullable = true
        }
        column {
          name     = "workspace_id"
          type     = "STRING"
          nullable = true
        }
        column {
          name     = "event_type"
          type     = "STRING"
          nullable = true
        }
        column {
          name     = "payload"
          type     = "JSON"
          nullable = true
        }

        table_settings {
          engine {
            merge_tree {
              type = "MERGE_TREE"
            }
          }
          partition_by = ["toStartOfHour(timestamp)"]
        }
      }
      ```
    </Tab>
  </Tabs>

  ***

#### Incremental data

For sequential records with an increasing numeric column, you can divide the numeric column by a large number to assign it a partition. The size of the divisor should depend on how quickly the numeric column increases.

Given an increasing numeric column `order_id`, setting the partition key to `intDiv(order_id, 1000000)` will generate the following partitions:

* Partition 1: `order_id` values 0–1,000,000
* Partition 2: `order_id` values 1,000,000–2,000,000
* etc.

<Tabs>
  <Tab title="Console">
    <Frame>
      <img src="https://mintcdn.com/propeldocs/G8bGSg66eBpnYaHB/images/docs/2024-06-10-partition-key-guide-console-06.png?fit=max&auto=format&n=G8bGSg66eBpnYaHB&q=85&s=13578a7e4ef06f4acad7c6460d1cf0f2" alt="Incremental data partition key in ClickHouse" width="1868" height="232" data-path="images/docs/2024-06-10-partition-key-guide-console-06.png" />
    </Frame>
  </Tab>

  <Tab title="API">
    ```graphql theme={"system"}
    mutation {
      createDataPoolV2(
        input: {
          uniqueName: "events2"
          description: "Data Pool for events"
          timestamp: { columnName: "timestamp" }
          columns: [
            { columnName: "timestamp", type: TIMESTAMP, isNullable: true }
            { columnName: "customer_id", type: TIMESTAMP, isNullable: true }
            { columnName: "workspace_id", type: STRING, isNullable: true }
            { columnName: "event_type", type: STRING, isNullable: true }
            { columnName: "payload", type: JSON, isNullable: true }
          ]
          tableSettings: {
            engine: {
              mergeTree: {
                type: MERGE_TREE
              }
            }
            partitionBy: ["intDiv(order_id, 1000000)"]
          }
        }
      ) {
        dataPool {
          id
          uniqueName
          description
          tableSettings {
            orderBy
          }
        }
      }
    }
    ```
  </Tab>

  <Tab title="Terraform">
    ```hcl theme={"system"}
    resource "propel_data_pool" "events2" {
      unique_name = "events2"
      description = "Data Pool for events"
      timestamp   = "timestamp"

      column {
        name     = "timestamp"
        type     = "TIMESTAMP"
        nullable = true
      }
      column {
        name     = "customer_id"
        type     = "TIMESTAMP"
        nullable = true
      }
      column {
        name     = "workspace_id"
        type     = "STRING"
        nullable = true
      }
      column {
        name     = "event_type"
        type     = "STRING"
        nullable = true
      }
      column {
        name     = "payload"
        type     = "JSON"
        nullable = true
      }

      table_settings {
        engine {
          merge_tree {
            type = "MERGE_TREE"
          }
        }
        partition_by = ["intDiv(order_id, 1000000)"]
      }
    }
    ```
  </Tab>
</Tabs>

***

## Mutable data in ReplacingMergeTree tables

For larger ReplacingMergeTree tables, we recommend partitions to be a bit smaller, up to 50 GB.

For instance, if you have a large "users" dimension table (greater than 10 GB) and queries always filter on a `user_id` UUID column, the recommended partition key would be `xxHash64(user_id) % 10`. This will generate up to 10 partitions, with `user_ids` consistently distributed throughout.

<Tabs>
  <Tab title="Console">
    <Frame>
      <img src="https://mintcdn.com/propeldocs/G8bGSg66eBpnYaHB/images/docs/2024-06-10-partition-key-guide-console-05.png?fit=max&auto=format&n=G8bGSg66eBpnYaHB&q=85&s=5bd719280b7afee60cd78f40b1434dd5" alt="Large scale mutable data partition key in ClickHouse" width="1544" height="287" data-path="images/docs/2024-06-10-partition-key-guide-console-05.png" />
    </Frame>
  </Tab>

  <Tab title="API">
    ```graphql theme={"system"}
    mutation {
      createDataPoolV2(
        input: {
          uniqueName: "events2"
          description: "Data Pool for events"
          timestamp: { columnName: "timestamp" }
          columns: [
            { columnName: "timestamp", type: TIMESTAMP, isNullable: true }
            { columnName: "customer_id", type: TIMESTAMP, isNullable: true }
            { columnName: "workspace_id", type: STRING, isNullable: true }
            { columnName: "event_type", type: STRING, isNullable: true }
            { columnName: "payload", type: JSON, isNullable: true }
          ]
          tableSettings: {
            engine: {
              mergeTree: {
                type: MERGE_TREE
              }
            }
            partitionBy: ["xxHash64(user_id) % 10"]
          }
        }
      ) {
        dataPool {
          id
          uniqueName
          description
          tableSettings {
            orderBy
          }
        }
      }
    }
    ```
  </Tab>

  <Tab title="Terraform">
    ```hcl theme={"system"}
    resource "propel_data_pool" "events2" {
      unique_name = "events2"
      description = "Data Pool for events"
      timestamp   = "timestamp"

      column {
        name     = "timestamp"
        type     = "TIMESTAMP"
        nullable = true
      }
      column {
        name     = "customer_id"
        type     = "TIMESTAMP"
        nullable = true
      }
      column {
        name     = "workspace_id"
        type     = "STRING"
        nullable = true
      }
      column {
        name     = "event_type"
        type     = "STRING"
        nullable = true
      }
      column {
        name     = "payload"
        type     = "JSON"
        nullable = true
      }

      table_settings {
        engine {
          merge_tree {
            type = "MERGE_TREE"
          }
        }
        partition_by = ["xxHash64(user_id) % 10"]
      }
    }
    ```
  </Tab>
</Tabs>

***

## Aggregations in Summing- or Aggregating-MergeTree tables

For SummingMergeTree and AggregatingMergeTree tables, we recommend partitions to be up to 50 GB; however, because these tables are often small, due to aggregation, you rarely need to partition.

If your table aggregates by time, follow the instructions for [time series data above](#time-series-data). If your table aggregates by, for example, `user_id`, follow the instructions for [mutable data above](#mutable-data-in-replacingmergetree-tables).

## Frequently asked questions

<AccordionGroup>
  <Accordion title="Can you change the partition key of a table?">
    No, once a table is created in ClickHouse, the partition key cannot be changed directly. However, if you need to change the partition key, you have a couple of options:

    1. You can create a new table with the desired partition key and move the data from the old table to the new one using a copy job.
    2. Alternatively, you can create a materialized view with a destination Data Pool with a different partition key. This allows you to have a constantly updated new table that mirrors the original data with a different partitioning scheme.
  </Accordion>

  <Accordion title="What is a part in ClickHouse?">
    In ClickHouse, a part is a piece of a table that stores rows. Each part is represented as a single folder with columns. Parts are not the same as partitions; they are physical entities, while partitions are virtual.
  </Accordion>

  <Accordion title="Are parts the same as partitions?">
    No, parts and partitions in ClickHouse are not the same. Parts are physical entities that store rows of a table, with each part represented as a single folder with columns. On the other hand, partitions are virtual entities that don't have a physical representation. However, you can associate certain parts with the same partition based on the partition key.
  </Accordion>
</AccordionGroup>
