# ClickHouse Reverse ETL Source


<blockquote class="announcement">
  <div class="tip-quote">
    
    <div class="tip-text"><p>The ClickHouse Reverse ETL source is in <strong>Private Beta</strong>, where we work with early users and customers to test new features and get feedback before making them generally available.</p>
<p>Reach out to <a href="mailto:support@rudderstack.com" >Customer Success</a> to enable it for your workspace.</p>
</div>
  </div>
</blockquote>

<p><a href="https://clickhouse.com/" >ClickHouse</a> is an open source, column-oriented database for analytics. It runs as a managed service (ClickHouse Cloud) or on your own servers.</p>
<p>RudderStack reads audience members from your ClickHouse database and keeps them in sync with your destinations. You connect ClickHouse and build the audience in <a href="https://www.rudderstack.com/docs/activate/lookout/" >Rudder Lookout</a>. RudderStack then runs each sync. It keeps its working tables in a separate database in your ClickHouse server, and it never writes to your data.</p>
<!-- end-chunk -->
<!-- begin-chunk -->
<h2 id="what-you-can-sync">What you can sync</h2><p>The ClickHouse source supports one path: a SQL audience that you build in Lookout and activate to a destination.</p>
<table>
  <thead>
      <tr>
          <th>Supported</th>
          <th>Not supported</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><ul><li>Audiences built in Lookout</li><li><a href="https://www.rudderstack.com/docs/data-pipelines/reverse-etl/developer-guides/sync-modes/#mirror-mode" >Mirror mode</a>, the only sync mode</li><li>ClickHouse Cloud, with one replica or several</li><li>A self-hosted ClickHouse server on a single node</li></ul></td>
          <td><ul><li>Table, model, and Audiences (data graph) sources</li><li>Creating the source from <strong>Add source</strong> in the RudderStack dashboard or through the API</li><li>Upsert and full sync modes</li><li>Self-hosted clusters, and <code>Replicated</code> databases outside ClickHouse Cloud</li><li>Aiven and the Tinybird ClickHouse interface</li><li>SSH tunnels, PrivateLink, and a custom CA certificate</li></ul></td>
      </tr>
  </tbody>
</table>
<!-- end-chunk -->
<!-- begin-chunk -->
<h2 id="prerequisites">Prerequisites</h2><ol>
<li>Reach out to <a href="mailto:support@rudderstack.com" >Customer Success</a> to enable ClickHouse for your workspace.</li>
<li><a href="https://www.rudderstack.com/docs/activate/lookout/integrations/rudderstack/" >Link your Lookout workspace to your RudderStack workspace</a>. Lookout creates the source in that RudderStack workspace when you activate an audience.</li>
<li>Check that your ClickHouse server runs version <strong>26.3 or later</strong>. Run this query and compare the result:</li>
</ol>
<div class="rs-code">
  <div class="rs-code__head">sql<button class="rs-code__copy" type="button">
      
      Copy
    </button>
  </div>
  <div class="highlight"><pre class="chroma"><code class="language-sql" data-lang="sql">SELECT version();</code></pre></div>
</div>
<ol start="4">
<li>Make sure your server accepts <strong>HTTPS</strong> connections. RudderStack does not connect over plain HTTP.</li>
</ol>
<div class="rs-tabs">
	<div class="rs-tabs__list">
		<button type="button" class="rs-tabs__tab"
			id="tab-fbcdea"
		
		>ClickHouse Cloud</button>
		<button type="button" class="rs-tabs__tab"
			id="tab-dafbce"
		
		>Self-hosted</button>
	</div>
	
<div class="rs-tabs__panel" id="panel-fbcdea">

Use the HTTPS endpoint of your service on port <code>8443</code>.

</div>

<div class="rs-tabs__panel" id="panel-dafbce" hidden>

Configure an HTTPS port (<code>https_port</code>) with a certificate. The certificate must come from a publicly trusted certificate authority and match the host name. RudderStack does not accept a custom or private CA certificate.

</div>

</div>

<ol start="5">
<li>If your server restricts network access, allowlist the <a href="#ips-to-allowlist" >RudderStack and Lookout IP addresses</a>. Allow them on the HTTPS port only.</li>
</ol>
<div class="rs-tabs">
	<div class="rs-tabs__list">
		<button type="button" class="rs-tabs__tab"
			id="tab-dfbcea"
		
		>ClickHouse Cloud</button>
		<button type="button" class="rs-tabs__tab"
			id="tab-decfab"
		
		>Self-hosted</button>
	</div>
	
<div class="rs-tabs__panel" id="panel-dfbcea">

Add the addresses to the IP access list of your service.

</div>

<div class="rs-tabs__panel" id="panel-decfab" hidden>

Allow the same addresses on the HTTPS port.

</div>

</div>

<blockquote class="warning">
  <div class="tip-quote">
    
    <div class="tip-text">Lookout connects only to public addresses. A host name that resolves to a private network address (for example, <code>10.0.0.0/8</code> or <code>192.168.0.0/16</code>) fails with <code>CH_HOST_NOT_ALLOWED</code>. IPv6 addresses and <code>localhost</code> are not accepted in the <strong>Host</strong> field.</div>
  </div>
</blockquote>
<!-- end-chunk -->
<!-- begin-chunk -->
<h2 id="granting-permissions">Grant permissions</h2><p>Ask a ClickHouse administrator to run the queries in the following steps, in order. The steps use these placeholders:</p>
<table>
  <thead>
      <tr>
          <th>Placeholder</th>
          <th>Replace with</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>rudder_retl</code></td>
          <td>The name of the user RudderStack connects as</td>
      </tr>
      <tr>
          <td><code>&lt;YOUR_DATABASE&gt;</code></td>
          <td>The database that holds the data your audiences read</td>
      </tr>
      <tr>
          <td><code>&lt;strong_unique_password&gt;</code></td>
          <td>A strong password for the user</td>
      </tr>
  </tbody>
</table>
<p>Names must use only letters, digits, and underscores, start with a letter or an underscore, and have at most 128 characters.</p>
<!-- end-chunk -->
<!-- begin-chunk -->
<h3 id="step-1-create-the-user">Step 1: Create the user</h3><p>Create a dedicated user for RudderStack:</p>
<div class="rs-code">
  <div class="rs-code__head">sql<button class="rs-code__copy" type="button">
      
      Copy
    </button>
  </div>
  <div class="highlight"><pre class="chroma"><code class="language-sql" data-lang="sql">CREATE USER rudder_retl IDENTIFIED BY &#39;&lt;strong_unique_password&gt;&#39;;</code></pre></div>
</div>
<p>The password can&rsquo;t contain control characters, and it can&rsquo;t start or end with a space.</p>
<p><strong>Expected result</strong>: The query succeeds. <code>SHOW USERS</code> lists <code>rudder_retl</code>.</p>
<!-- end-chunk -->
<!-- begin-chunk -->
<h3 id="creating-the-rudderstack-schema-and-granting-permissions">Step 2: Create the working database and grant permissions</h3><p>RudderStack keeps its working tables in a separate database called <code>_rudderstack</code>. Run the following script as an administrator:</p>
<div class="rs-code">
  <div class="rs-code__head">sql<button class="rs-code__copy" type="button">
      
      Copy
    </button>
  </div>
  <div class="highlight"><pre class="chroma"><code class="language-sql" data-lang="sql">-- Create the RudderStack working database.
CREATE DATABASE IF NOT EXISTS `_rudderstack`;

-- Lets RudderStack read your database. It cannot change your data.
GRANT SELECT
ON `&lt;YOUR_DATABASE&gt;`.*
TO `rudder_retl`;

-- Lets RudderStack create, write, read and drop tables in the working database, where it keeps its sync state.
GRANT SELECT, INSERT, CREATE TABLE, DROP TABLE
ON `_rudderstack`.*
TO `rudder_retl`;

-- Lets RudderStack delete old rows from its own sync log in the working database. It cannot delete anything in your data.
GRANT ALTER DELETE ON `_rudderstack`.`sync_log` TO `rudder_retl`;

-- Lets RudderStack check and stop its own running queries. It also shows the running queries of every user on this server, with their text.
GRANT SELECT ON system.processes TO `rudder_retl`;

-- Lets RudderStack check whether its own queries finished. It also shows the logged queries of every user on this server, with their text.
GRANT SELECT ON system.query_log TO `rudder_retl`;</code></pre></div>
</div>
<p>Repeat the <code>GRANT SELECT ON &lt;YOUR_DATABASE&gt;.*</code> statement for each database your audiences read. You can also grant <code>SELECT</code> on single tables instead of the whole database. RudderStack then shows a warning during validation, but it accepts the narrower grant.</p>

<!DOCTYPE html>
<html lang="en">
<blockquote class="tip">
  <div class="tip-quote">
    
    <div class="tip-text">Lookout generates this script with your names filled in. It appears under <strong>Setup SQL</strong> while you <a href="#setting-up-the-clickhouse-source" >fill in the connection form</a>. You can copy or download it from there.</div>
  </div>
</blockquote>
</html>
<p><strong>Expected result</strong>: Each statement succeeds. ClickHouse Cloud does not support <code>GRANT ALL</code>, so the script lists each privilege.</p>
<!-- end-chunk -->
<!-- begin-chunk -->
<h4 id="why-rudderstack-needs-each-permission">Why RudderStack needs each permission</h4><table>
  <thead>
      <tr>
          <th>Permission</th>
          <th>Why RudderStack needs it</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>SELECT</code> on <code>&lt;YOUR_DATABASE&gt;.*</code></td>
          <td>Reads the rows your audience query selects.</td>
      </tr>
      <tr>
          <td><code>CREATE TABLE</code> and <code>INSERT</code> on <code>_rudderstack.*</code></td>
          <td>Creates a snapshot table for each sync, then fills it with the audience members.</td>
      </tr>
      <tr>
          <td><code>SELECT</code> on <code>_rudderstack.*</code></td>
          <td>Compares the new snapshot with the previous one, and reads the records to send.</td>
      </tr>
      <tr>
          <td><code>DROP TABLE</code> on <code>_rudderstack.*</code></td>
          <td>Removes old snapshots and other working tables RudderStack no longer needs.</td>
      </tr>
      <tr>
          <td>All four privileges on <code>_rudderstack.*</code></td>
          <td><code>RENAME TABLE</code> and <code>EXCHANGE TABLES</code> need <code>SELECT</code>, <code>INSERT</code>, <code>CREATE TABLE</code>, and <code>DROP TABLE</code>. RudderStack uses them to publish each new snapshot.</td>
      </tr>
      <tr>
          <td><code>ALTER DELETE</code> on <code>_rudderstack.sync_log</code></td>
          <td>Deletes sync log rows older than the <strong>Sync log retention</strong>. The script always grants it, so you can turn on retention later without a new grant. The grant works before RudderStack creates the <code>sync_log</code> table.</td>
      </tr>
      <tr>
          <td><code>SELECT</code> on <code>system.processes</code></td>
          <td>Stops a sync query that runs past its time limit. ClickHouse Cloud refuses the stop without this grant.</td>
      </tr>
      <tr>
          <td><code>SELECT</code> on <code>system.query_log</code></td>
          <td>Checks whether a query finished when its response was lost. ClickHouse Cloud refuses this check without this grant.</td>
      </tr>
  </tbody>
</table>
<p>The script grants nothing else. RudderStack needs no <code>KILL QUERY</code> privilege, because it stops only its own queries. It also needs no write access to your data, no <code>CREATE DATABASE</code> or <code>DROP DATABASE</code>, no cluster privilege, and no access-management privilege.</p>

<blockquote class="warning">
  <div class="tip-quote">
    
    <div class="tip-text">The two <code>system</code> grants let the user read the running and logged queries of <strong>every</strong> user on the server, including their query text. RudderStack reads only the rows of its own user. RudderStack also refuses audience SQL that names the <code>system</code> or <code>information_schema</code> databases, and Lookout does not read these two tables when it explores your data.</div>
  </div>
</blockquote>
<!-- end-chunk -->
<!-- begin-chunk -->
<h4 id="working-database-rules">Working database rules</h4><ul>
<li>Keep the name <code>_rudderstack</code>. The connection form has no setting for this name.</li>
<li>Create the database before you connect. RudderStack never creates a database.</li>
<li>The working database must be different from the database in the <strong>Database</strong> field. It also can&rsquo;t be <code>default</code>, <code>system</code>, or <code>information_schema</code>.</li>
<li>The working database must use the <code>Atomic</code> engine or the <code>Shared</code> engine. ClickHouse Cloud uses <code>Shared</code>. RudderStack refuses every other engine, <code>Replicated</code> included.</li>
</ul>
<!-- end-chunk -->
<!-- begin-chunk -->
<h3 id="step-3-check-the-grants">Step 3: Check the grants</h3><p>Log in to ClickHouse as <code>rudder_retl</code> and run these checks:</p>
<div class="rs-code">
  <div class="rs-code__head">sql<button class="rs-code__copy" type="button">
      
      Copy
    </button>
  </div>
  <div class="highlight"><pre class="chroma"><code class="language-sql" data-lang="sql">CHECK GRANT SELECT ON `_rudderstack`.*;
CHECK GRANT INSERT ON `_rudderstack`.*;
CHECK GRANT CREATE TABLE ON `_rudderstack`.*;
CHECK GRANT DROP TABLE ON `_rudderstack`.*;
CHECK GRANT ALTER DELETE ON `_rudderstack`.`sync_log`;
CHECK GRANT SELECT ON system.processes;
CHECK GRANT SELECT ON system.query_log;</code></pre></div>
</div>
<p><strong>Expected result</strong>: Each statement returns <code>1</code>. A <code>0</code> means the grant is missing. Run the script in <a href="#creating-the-rudderstack-schema-and-granting-permissions" >Step 2</a> again.</p>
<p>RudderStack runs the same checks when you connect, and stops at the first missing grant. A missing <code>ALTER DELETE</code> grant only gives a warning while sync log retention is off. With retention on, each sync fails at the start with <code>CH_PERMISSION</code> and names <code>ALTER DELETE</code>.</p>
<!-- end-chunk -->
<!-- begin-chunk -->
<h2 id="setting-up-the-clickhouse-source">Connect ClickHouse in Lookout</h2>
<html lang="en">
<blockquote class="info">
  <div class="tip-quote">
    
    <div class="tip-text">Only Lookout admins can add a warehouse connection. See <a href="https://www.rudderstack.com/docs/activate/lookout/workspaces/roles/" >Roles and Permissions in Rudder Lookout</a>.</div>
  </div>
</blockquote>

</html>
<ol>
<li>In Lookout, go to <strong>Settings</strong> &gt; <strong>Warehouses</strong>.</li>
<li>Click <strong>Add account</strong>.</li>
<li>In <strong>Step 1: Select warehouse type</strong>, select <strong>ClickHouse</strong>.</li>
<li>In <strong>Step 2: Account details</strong>, fill in the <a href="#connection-settings" >connection settings</a>.</li>
<li>Check the <strong>Setup SQL</strong> below the form. If your administrator has not run it yet, copy it and ask them to run it now.</li>
<li>Click <strong>Validate and create</strong>.</li>
</ol>
<p><strong>Expected result</strong>: Lookout shows <strong>Account created</strong>. The ClickHouse connection appears under <strong>Settings</strong> &gt; <strong>Warehouses</strong>.</p>
<!-- end-chunk -->
<!-- begin-chunk -->
<h3 id="connection-settings">Connection settings</h3><table>
  <thead>
      <tr>
          <th>Setting</th>
          <th>Description</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>Account name</strong></td>
          <td>Enter a name to identify this connection in Lookout.</td>
      </tr>
      <tr>
          <td><strong>Host</strong></td>
          <td>Enter the host name of your ClickHouse server, without the scheme, port, or path. For example, enter <code>clickhouse.example.com</code>, not <code>https://clickhouse.example.com:8443</code>. A public IPv4 address also works.</td>
      </tr>
      <tr>
          <td><strong>Port (optional)</strong></td>
          <td>Enter the HTTPS port of your server. The default is <code>8443</code>.</td>
      </tr>
      <tr>
          <td><strong>Database</strong></td>
          <td>Enter the database that holds the data your audiences read. This is <code>&lt;YOUR_DATABASE&gt;</code> in the <a href="#creating-the-rudderstack-schema-and-granting-permissions" >grant script</a>. It can&rsquo;t be <code>system</code> or <code>information_schema</code>, in any letter case. RudderStack refuses these names when you save the account, and again when it connects.</td>
      </tr>
      <tr>
          <td><strong>Username</strong></td>
          <td>Enter the user you created in <a href="#step-1-create-the-user" >Step 1</a>, for example, <code>rudder_retl</code>.</td>
      </tr>
      <tr>
          <td><strong>Password</strong></td>
          <td>Enter the password of that user.</td>
      </tr>
  </tbody>
</table>
<p>The form has no option to turn off TLS or certificate checks. Every connection uses HTTPS and verifies the server certificate.</p>

<html lang="en">
<blockquote class="info">
  <div class="tip-quote">
    
    <div class="tip-text">The password goes to RudderStack&rsquo;s secrets store. Lookout does not store it.</div>
  </div>
</blockquote>

</html>
<!-- end-chunk -->
<!-- begin-chunk -->
<h3 id="verifying-credentials">Validation steps</h3><p>When you click <strong>Validate and create</strong>, RudderStack checks the connection before it creates the account. It runs these steps in order and stops at the first failure:</p>
<table>
  <thead>
      <tr>
          <th>Step</th>
          <th>What RudderStack checks</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>Connect and check the version</strong></td>
          <td>The host, port, and address, the HTTPS connection and certificate, the user name and password, that the server runs version 26.3 or later, and that the current database of the connection is the <strong>Database</strong> you entered.</td>
      </tr>
      <tr>
          <td><strong>Check the settings</strong></td>
          <td>That the user can set the query settings RudderStack sends with each query.</td>
      </tr>
      <tr>
          <td><strong>Check the grants and the RudderStack working database engine</strong></td>
          <td>Each grant in <a href="#step-3-check-the-grants" >Step 3</a>, and the engine of the working database.</td>
      </tr>
      <tr>
          <td><strong>Write to the _rudderstack database</strong></td>
          <td>That RudderStack can create a test table named <code>_rudder_probe_&lt;16 hex characters&gt;</code>, write a row, read it back, and drop the table.</td>
      </tr>
      <tr>
          <td><strong>Read diagnostics</strong></td>
          <td>Extra details for support. This step never fails.</td>
      </tr>
  </tbody>
</table>
<p>If a step fails, Lookout shows the step name, a message, and an error code. RudderStack creates no account. See <a href="#error-codes" >Error codes</a> to fix the problem, then click <strong>Validate and create</strong> again.</p>

<!DOCTYPE html>
<html lang="en">
<blockquote class="tip">
  <div class="tip-quote">
    
    <div class="tip-text">An idle ClickHouse Cloud service can take a while to wake up. The first validation request waits up to 90 seconds for it.</div>
  </div>
</blockquote>
</html>
<!-- end-chunk -->
<!-- begin-chunk -->
<h2 id="specifying-the-data-to-import">Activate an audience</h2><p>After you connect ClickHouse:</p>
<ol>
<li><a href="https://www.rudderstack.com/docs/activate/lookout/audiences/build-an-audience/" >Build an audience</a> on the ClickHouse connection.</li>
<li><a href="https://www.rudderstack.com/docs/activate/lookout/audiences/activate-an-audience/" >Activate the audience</a> to a destination.</li>
</ol>
<p>Lookout creates the ClickHouse source and its connection in your linked RudderStack workspace. The connection syncs in mirror mode.</p>
<!-- end-chunk -->
<!-- begin-chunk -->
<h2 id="how-a-sync-works">How a sync works</h2><p>Each sync runs these steps in your ClickHouse server:</p>
<ol>
<li>RudderStack connects over HTTPS and repeats the version, settings, and grant checks.</li>
<li>It checks the audience SQL against the <a href="#audience-sql-limits" >Audience SQL limits</a>.</li>
<li>It creates a new snapshot table in the working database and fills it with the current audience members. The rows go from your tables to the working database without leaving the server.</li>
<li>It compares the new snapshot with the previous one.</li>
<li>It sends the changes to the destination: removed members first, then changed members, then new members.</li>
<li>It publishes the new snapshot with <code>RENAME TABLE</code>, so the new snapshot becomes the baseline for the next sync. If an earlier run stopped halfway, RudderStack uses <code>EXCHANGE TABLES</code> to recover.</li>
</ol>
<p>If a step fails, the previous snapshot stays the baseline. The next sync compares against it.</p>
<p>A sync run can take at most 2 hours. RudderStack stops a query that is still running at that limit.</p>
<!-- end-chunk -->
<!-- begin-chunk -->
<h3 id="what-a-sync-sends">What a sync sends</h3><table>
  <thead>
      <tr>
          <th>Sync</th>
          <th>What the destination receives</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>First sync</td>
          <td>One insert for each current member. No deletes.</td>
      </tr>
      <tr>
          <td>Normal sync</td>
          <td>A delete for each member who left, an update for each member whose values changed, and an insert for each new member.</td>
      </tr>
      <tr>
          <td>Full sync</td>
          <td>One insert for each current member. No deletes. The full sync becomes the new baseline, and the next normal sync sends deletes again.</td>
      </tr>
  </tbody>
</table>
<p>You start a full sync with <strong>Run full sync</strong> in Lookout. RudderStack also runs a full sync on its own when you add or change a mapping, change the identifier, add a column, or change a column&rsquo;s type.</p>
<!-- end-chunk -->
<!-- begin-chunk -->
<h3 id="what-you-see-in-the-working-database">What you see in the working database</h3><p>The working database holds only RudderStack&rsquo;s own tables:</p>
<ul>
<li>Snapshot tables, one for each retained sync</li>
<li>Tables that track failed records, so the next sync can retry them</li>
<li>A sync log table, when sync logs are on</li>
<li>Short-lived test tables named <code>_rudder_probe_&lt;16 hex characters&gt;</code>, which RudderStack creates and drops during validation</li>
</ul>
<p>Table names are in lowercase and include the connection ID. See <a href="https://www.rudderstack.com/docs/data-pipelines/reverse-etl/developer-guides/rudderstack-schema/" ><code>_rudderstack</code> Schema Reference</a> for the table families.</p>

<blockquote class="warning">
  <div class="tip-quote">
    
    <div class="tip-text">Do not edit or drop tables in the working database while a connection uses it. RudderStack removes old tables itself.</div>
  </div>
</blockquote>
<!-- end-chunk -->
<!-- begin-chunk -->
<h2 id="audience-sql-limits">Audience SQL limits</h2><p>Lookout builds the audience SQL for you. Before each sync, RudderStack checks that SQL. It refuses any audience SQL that uses a construct in the following table. The sync then fails with <code>CH_QUERY_INVALID</code> and names the refused construct.</p>
<table>
  <thead>
      <tr>
          <th>Refused construct</th>
          <th>Label in the error</th>
          <th>Reason</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>SQL that RudderStack cannot split into tokens, or whose brackets do not balance</td>
          <td><code>SYNTAX</code></td>
          <td>RudderStack must read the whole query before it can check it.</td>
      </tr>
      <tr>
          <td>More than one statement, or any text after the final semicolon (a comment included)</td>
          <td><code>MULTIPLE STATEMENTS</code></td>
          <td>An audience is a single query.</td>
      </tr>
      <tr>
          <td>A query that does not start with <code>SELECT</code> or <code>WITH</code>, or that starts with a parenthesized <code>SELECT</code></td>
          <td><code>NOT SELECT</code>, <code>PARENTHESIZED SELECT</code></td>
          <td>An audience reads data. It never writes or changes it.</td>
      </tr>
      <tr>
          <td>A table name or column qualified by <code>system</code>, <code>information_schema</code>, or <code>INFORMATION_SCHEMA</code>, a table alias named <code>system</code>, or a backslash in a quoted part of a qualified name</td>
          <td><code>SYSTEM TABLE</code></td>
          <td>The RudderStack user can read other users&rsquo; query text in <code>system.query_log</code> and <code>system.processes</code>.</td>
      </tr>
      <tr>
          <td>A call to a table function, such as <code>url()</code>, <code>s3()</code>, <code>remote()</code>, <code>format()</code>, or <code>view()</code>, and any call to <code>file()</code></td>
          <td><code>TABLE FUNCTION</code></td>
          <td>Most of these functions read data from outside your databases, such as other servers or server files. RudderStack refuses, by name, every table function that ClickHouse 26.3 has.</td>
      </tr>
      <tr>
          <td>Any call to <code>hasColumnInTable</code></td>
          <td><code>NETWORK FUNCTION</code></td>
          <td>This function can make your server connect to any host.</td>
      </tr>
      <tr>
          <td>A query parameter of type <code>Identifier</code> or <code>Array(Identifier)</code>, such as <code>{t:Identifier}</code>. Parameters of other types are not refused.</td>
          <td><code>IDENTIFIER PARAMETER</code></td>
          <td>The value is bound after the check, so it can name a system table.</td>
      </tr>
      <tr>
          <td>The word <code>APPLY</code> in any letter case and any position, outside strings, quoted names, and comments. This includes a column or alias named <code>apply</code>.</td>
          <td><code>COLUMN TRANSFORMER</code></td>
          <td>The <code>APPLY</code> column transformer calls a function by its bare name, so <code>SELECT * APPLY(file)</code> reaches <code>file()</code>.</td>
      </tr>
      <tr>
          <td>The word <code>EXPLAIN</code> in any letter case and any position, outside strings, quoted names, and comments. This includes a column or alias named <code>explain</code>.</td>
          <td><code>EXPLAIN</code></td>
          <td>A subquery such as <code>SELECT * FROM (EXPLAIN SELECT 1)</code> runs as the <code>viewExplain</code> table function.</td>
      </tr>
      <tr>
          <td><code>SETTINGS</code>, at any level</td>
          <td><code>SETTINGS</code></td>
          <td>RudderStack sets the query settings itself, so preview and sync return the same members.</td>
      </tr>
      <tr>
          <td>A <code>FORMAT</code> clause, or a bare column named <code>format</code>. A <code>format()</code> call is refused as <code>TABLE FUNCTION</code>.</td>
          <td><code>FORMAT</code></td>
          <td>RudderStack reads the result in its own format.</td>
      </tr>
      <tr>
          <td><code>INTO OUTFILE</code></td>
          <td><code>INTO OUTFILE</code></td>
          <td>An audience never writes a file.</td>
      </tr>
      <tr>
          <td><code>PREWHERE</code>, <code>FINAL</code>, <code>SAMPLE</code>, <code>LIMIT BY</code>, or <code>ARRAY JOIN</code></td>
          <td>The clause name</td>
          <td>RudderStack controls how a query merges and samples rows, so every sync returns the complete audience.</td>
      </tr>
      <tr>
          <td>Audience SQL longer than 245,760 bytes (240 KiB)</td>
          <td><code>SIZE</code></td>
          <td>Longer SQL does not fit in a ClickHouse query together with RudderStack&rsquo;s own statements.</td>
      </tr>
  </tbody>
</table>
<p>A quoted column name is not refused. For example, <code>`apply`</code> and <code>`format`</code> pass, and so does a qualified name such as <code>u.format</code>. Quotes do not make a table function call or a <code>system</code> qualifier pass. If Lookout writes such a column without quotes, create a view that renames the column, and build the audience on the view.</p>
<p>Audience rules in Lookout support filter columns of these types: <code>String</code>, signed and unsigned integers, <code>Bool</code>, <code>DateTime</code>, and <code>DateTime64</code>. A rule on a column of another type fails with <code>CH_TYPE_UNSUPPORTED</code>.</p>
<!-- end-chunk -->
<!-- begin-chunk -->
<h2 id="revoke-access">Remove access</h2><p>To stop using ClickHouse with RudderStack:</p>
<ol>
<li>In Lookout, delete the audience activations that use the ClickHouse connection.</li>
<li>Copy the <a href="#teardown-sql" >teardown SQL</a> below. Replace <code>rudder_retl</code> with your user name.</li>
<li>Ask a ClickHouse administrator to run the teardown SQL.</li>
<li>In Lookout, go to <strong>Settings</strong> &gt; <strong>Warehouses</strong> and delete the ClickHouse connection.</li>
</ol>
<p><strong>Expected result</strong>: <code>SHOW USERS</code> no longer lists the RudderStack user.</p>

<html lang="en">
<blockquote class="info">
  <div class="tip-quote">
    
    <div class="tip-text">If your workspace shows <strong>Revoke in RudderStack</strong> in the connection&rsquo;s account menu, you can use it before step 4. It removes the account that RudderStack stores and shows the same teardown SQL under <strong>Finish in your warehouse</strong>.</div>
  </div>
</blockquote>

</html>
<!-- end-chunk -->
<!-- begin-chunk -->
<h3 id="teardown-sql">Teardown SQL</h3><p>The teardown SQL drops the user:</p>
<div class="rs-code">
  <div class="rs-code__head">sql<button class="rs-code__copy" type="button">
      
      Copy
    </button>
  </div>
  <div class="highlight"><pre class="chroma"><code class="language-sql" data-lang="sql">-- RudderStack -- remove ClickHouse access for user `rudder_retl`
-- ⚠ This user came from you, not from RudderStack. Run this only if nothing else logs in as `rudder_retl`.
DROP USER IF EXISTS `rudder_retl`;

-- RudderStack kept its working tables in the database `_rudderstack`.
-- Other RudderStack accounts on this server can share it, so this script never drops it.
-- Drop it yourself only after no RudderStack account uses it.</code></pre></div>
</div>
<p>The script never drops the working database, because other RudderStack accounts on the same server can use it. If the user is the built-in <code>default</code> user, the script drops nothing. Change its password, or revoke the grants yourself.</p>
<p>After no RudderStack account uses the working database, you can drop it.</p>

<blockquote class="danger">
    <div class="tip-quote">
        
        <div class="tip-text">This statement deletes every RudderStack working table in <code>_rudderstack</code> on this server. It deletes them for every RudderStack account that uses this database, not only for this connection. Run it only when no RudderStack account uses the database.</div>
    </div>
</blockquote>
<div class="rs-code">
  <div class="rs-code__head">sql<button class="rs-code__copy" type="button">
      
      Copy
    </button>
  </div>
  <div class="highlight"><pre class="chroma"><code class="language-sql" data-lang="sql">DROP DATABASE IF EXISTS `_rudderstack` SYNC;</code></pre></div>
</div>
<!-- end-chunk -->
<!-- begin-chunk -->
<h2 id="ips-to-allowlist">IPs to allowlist</h2>
<html lang="en">
<blockquote class="info">
  <div class="tip-quote">
    
    <div class="tip-text">This section applies if your ClickHouse server accepts connections only from known addresses. RudderStack connects from these addresses for validation, preview, and every sync.</div>
  </div>
</blockquote>

</html>

<p>To enable network access to RudderStack, allowlist the following RudderStack IPs depending on your region and <a href="https://www.rudderstack.com/pricing" >RudderStack plan</a>:</p>
<table>
      <tr>
        <th>Plan</th>
        <th colspan="3"><center>Region</center></th>
      </tr>
      <tr>
        <td></td>
        <td><b><center>US</center></b></td>
        <td><b><center>EU</center></b></td>
        <td><b><center>IN</center></b></td>
      </tr>
      <tr>
        <td>Free and Growth</td>
        <td><ul><li>3.216.35.97</li><li>18.214.35.254</li><li>23.20.96.9</li><li>34.198.90.241</li><li>34.211.241.254</li><li>52.38.160.231</li><li>54.147.40.62</li></ul></td>
        <td><ul><li>3.123.104.182</li><li>3.125.132.33</li><li>18.198.90.215</li><li>18.196.167.201</li></ul></td>
        <td><center>Not applicable</center></td>
      </tr>
      <tr>
        <td>Enterprise</td>
        <td><ul><li>3.216.35.97</li><li>34.198.90.241</li><li>44.236.60.231</li><li>54.147.40.62</li><li>100.20.239.77</li></ul></td>
        <td><ul><li>3.66.99.198</li><li>3.64.201.167</li><li>3.123.104.182</li><li>3.125.132.33</li></ul></td>
        <td><ul><li>3.7.235.227</li><li>13.200.113.188</li><li>35.154.198.69</li><li>3.6.122.214</li></ul></td>
      </tr>
</table>

<html lang="en">
<blockquote class="info">
  <div class="tip-quote">
    
    <div class="tip-text"><p>All the outbound traffic is routed through these RudderStack IPs.</p>
<p>Rudder Lookout connects to your warehouse from three of the US addresses: <code>3.216.35.97</code>, <code>34.198.90.241</code>, and <code>54.147.40.62</code>. Lookout uses these addresses for workspaces in every region. If you use Lookout with an EU or IN workspace, allow them too. See <a href="https://www.rudderstack.com/docs/activate/lookout/integrations/warehouses/#allow-lookout-to-reach-your-warehouse" >Allow Lookout to reach your warehouse</a>.</p>
</div>
  </div>
</blockquote>

</html>

<p>Allow two sets of addresses:</p>
<ul>
<li><strong>RudderStack</strong>: The addresses in the table above for your plan and region. RudderStack runs syncs and validation from these.</li>
<li><strong>Lookout</strong>: Lookout connects to your server directly to test the connection and to explore your data. Allow these addresses:</li>
</ul>

<ul>
<li><code>3.216.35.97</code></li>
<li><code>34.198.90.241</code></li>
<li><code>54.147.40.62</code></li>
</ul>

<!-- end-chunk -->
<!-- begin-chunk -->
<h2 id="error-codes">Error codes</h2><p>Validation and sync errors carry one of the following codes.</p>
<table>
  <thead>
      <tr>
          <th>Error code</th>
          <th>Meaning</th>
          <th>What to do</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>CH_AUTHENTICATION</code></td>
          <td>ClickHouse did not accept the user name or password.</td>
          <td>Check the <strong>Username</strong> and <strong>Password</strong>, then try again.</td>
      </tr>
      <tr>
          <td><code>CH_PERMISSION</code></td>
          <td>The user is missing a grant, or its settings profile refuses a setting RudderStack sends. The message names the grant or setting.</td>
          <td>Run the <a href="#creating-the-rudderstack-schema-and-granting-permissions" >grant script</a> again. If the message names a setting, ask your administrator to let the user change that setting. A profile with <code>readonly = 1</code> refuses every setting change.</td>
      </tr>
      <tr>
          <td><code>CH_VERSION_BELOW_FLOOR</code></td>
          <td>The server runs a version older than 26.3.</td>
          <td>Upgrade the server to 26.3 or later.</td>
      </tr>
      <tr>
          <td><code>CH_TLS</code></td>
          <td>The TLS connection failed. The server certificate check failed, the TLS handshake failed, or the server answered without TLS.</td>
          <td>Check that the port serves HTTPS. Use a certificate from a publicly trusted authority that matches the host name.</td>
      </tr>
      <tr>
          <td><code>CH_HOST_NOT_ALLOWED</code></td>
          <td>The host is not valid, or it resolves to an address RudderStack does not connect to, such as a private, loopback, or link-local address.</td>
          <td>Use the public host name of your server.</td>
      </tr>
      <tr>
          <td><code>CH_DNS_FAILED</code></td>
          <td>The host name did not resolve.</td>
          <td>Check the <strong>Host</strong> field.</td>
      </tr>
      <tr>
          <td><code>CH_NETWORK</code></td>
          <td>RudderStack could not reach the server, or the connection timed out.</td>
          <td>Check the <strong>Host</strong> and <strong>Port (optional)</strong> fields, that the server is running and accepts HTTPS, and that the <a href="#ips-to-allowlist" >RudderStack IPs</a> are allowlisted.</td>
      </tr>
      <tr>
          <td><code>CH_UNAVAILABLE</code></td>
          <td>The server is not available.</td>
          <td>Check that your ClickHouse service is running, then try again.</td>
      </tr>
      <tr>
          <td><code>CH_REDIRECT_REFUSED</code></td>
          <td>The server answered with a redirect to another address. RudderStack does not follow redirects.</td>
          <td>Enter the host name the server answers on directly.</td>
      </tr>
      <tr>
          <td><code>CH_CONFIG_INVALID</code></td>
          <td>A connection setting is not valid. The message names the field. This code also covers a working database name that is not allowed, a working database that does not exist, a <strong>Database</strong> of <code>system</code> or <code>information_schema</code>, and a connection whose current database is not the <strong>Database</strong> you entered.</td>
          <td>Fix the named field. See <a href="#connection-settings" >Connection settings</a> and <a href="#working-database-rules" >Working database rules</a>.</td>
      </tr>
      <tr>
          <td><code>CH_WORKSPACE_NOT_ENABLED</code></td>
          <td>The workspace is not enabled for the ClickHouse source.</td>
          <td>Contact <a href="mailto:support@rudderstack.com" >Customer Success</a> to enable this source for your workspace.</td>
      </tr>
      <tr>
          <td><code>CH_OBJECT_NOT_FOUND</code></td>
          <td>A database or table that the query uses does not exist.</td>
          <td>Check that the database and the tables in your audience query exist. Check the <strong>Database</strong> field.</td>
      </tr>
      <tr>
          <td><code>CH_CLUSTER_UNSUPPORTED</code></td>
          <td>The host points to a self-hosted cluster with more than one server.</td>
          <td>Connect to a single-node server or to ClickHouse Cloud.</td>
      </tr>
      <tr>
          <td><code>CH_SCRATCH_CLEANUP_FAILED</code></td>
          <td>RudderStack could not drop a test table or an old working table.</td>
          <td>Check that the user still has <code>DROP TABLE</code> on the working database. RudderStack retries the cleanup at the next sync.</td>
      </tr>
      <tr>
          <td><code>CH_TIMEOUT</code></td>
          <td>ClickHouse took too long to answer.</td>
          <td>Try again. If it keeps happening, check that your ClickHouse service is running.</td>
      </tr>
      <tr>
          <td><code>CH_RATE_LIMITED</code></td>
          <td>ClickHouse is limiting requests.</td>
          <td>Wait a few minutes, then try again.</td>
      </tr>
      <tr>
          <td><code>CH_RESOURCE</code></td>
          <td>ClickHouse ran out of a resource or hit a server limit, such as the memory limit of a query.</td>
          <td>Narrow the audience, or ask your administrator to raise the limit for the RudderStack user.</td>
      </tr>
      <tr>
          <td><code>CH_QUERY_INVALID</code></td>
          <td>The audience SQL uses a refused construct.</td>
          <td>See <a href="#audience-sql-limits" >Audience SQL limits</a>.</td>
      </tr>
      <tr>
          <td><code>CH_TYPE_UNSUPPORTED</code></td>
          <td>An audience rule uses a column type that Lookout does not support yet.</td>
          <td>Use a column of a supported type.</td>
      </tr>
      <tr>
          <td><code>CH_SCHEMA_MISMATCH</code></td>
          <td>A column type changed between syncs, and the previous snapshot no longer fits the new type. RudderStack keeps the previous snapshot and sends nothing.</td>
          <td>Fix the audience so the column keeps a compatible type, or start a full sync with <strong>Run full sync</strong>. A full sync sends no deletes.</td>
      </tr>
      <tr>
          <td><code>CH_DDL_NOT_VISIBLE</code></td>
          <td>A new working table did not become visible in time. RudderStack sends nothing.</td>
          <td>Wait for the next sync. Contact support if it keeps happening.</td>
      </tr>
      <tr>
          <td><code>CH_OUTCOME_UNKNOWN</code></td>
          <td>RudderStack lost the answer from ClickHouse and could not confirm whether a query finished.</td>
          <td>Try again. RudderStack keeps the working tables of that run for recovery.</td>
      </tr>
      <tr>
          <td><code>CH_CANCELLED</code></td>
          <td>The check or sync stopped before it finished.</td>
          <td>Try again.</td>
      </tr>
  </tbody>
</table>
<p>For a code not listed here, contact <a href="mailto:support@rudderstack.com" >RudderStack Support</a> and include the code.</p>
<!-- end-chunk -->
<!-- begin-chunk -->
<h2 id="troubleshooting">Troubleshooting</h2><table>
  <thead>
      <tr>
          <th>Issue</th>
          <th>Solution</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Validation fails at <strong>Connect and check the version</strong> with an HTTP 400 error</td>
          <td>You probably entered the native protocol port (for example, <code>9440</code> on ClickHouse Cloud). Enter the HTTPS port, usually <code>8443</code>.</td>
      </tr>
      <tr>
          <td>Validation fails at <strong>Check the grants and the RudderStack working database engine</strong></td>
          <td>Run the checks in <a href="#step-3-check-the-grants" >Step 3</a> as the RudderStack user. Run the grant script again for any check that returns <code>0</code>. If all checks return <code>1</code>, check that the working database uses the <code>Atomic</code> or <code>Shared</code> engine.</td>
      </tr>
      <tr>
          <td>Saving the account fails because the database cannot be <code>system</code> or <code>information_schema</code></td>
          <td>Enter the database that holds your audience data in the <strong>Database</strong> field. RudderStack refuses the ClickHouse built-in databases in any letter case.</td>
      </tr>
      <tr>
          <td>Validation fails at <strong>Write to the _rudderstack database</strong></td>
          <td>Check that the working database exists and that the user has <code>CREATE TABLE</code>, <code>INSERT</code>, <code>SELECT</code>, and <code>DROP TABLE</code> on it.</td>
      </tr>
      <tr>
          <td>A self-hosted server fails with <code>CH_CLUSTER_UNSUPPORTED</code></td>
          <td>A load balancer sends RudderStack&rsquo;s connections to different servers. Connect to a single server.</td>
      </tr>
  </tbody>
</table>
<!-- end-chunk -->
<!-- begin-chunk -->
<h2 id="limitations">Limitations</h2><ul>
<li>On ClickHouse Cloud with several replicas, a read from a lagging replica can return too few members. The sync then deletes members who did not leave, and the next correct sync inserts them again. The destination can count these as changes.</li>
<li>A table reload or a broken audience query that returns too few members makes the sync delete them. RudderStack has no stop for large deletes on any warehouse.</li>
<li>A full sync that RudderStack starts on its own sends no deletes, and it does not tell you. Members who left before that sync stay in the destination.</li>
<li>If a member&rsquo;s identifier changes but the primary key stays the same, the old identifier stays in the destination.</li>
<li>The sync leaves out a row with an empty (<code>NULL</code>) or duplicate key. If that member was synced before, the sync deletes them.</li>
</ul>
<!-- end-chunk -->
<!-- begin-chunk -->
<h2 id="faq">FAQ</h2><!-- end-chunk -->
<!-- begin-chunk -->
<h4 id="can-i-create-a-clickhouse-source-from-the-rudderstack-dashboard">Can I create a ClickHouse source from the RudderStack dashboard?</h4><p>No. You create ClickHouse sources from Lookout only, when you activate an audience. The ClickHouse source supports SQL audiences built in Lookout, synced in mirror mode.</p>
<!-- end-chunk -->
<!-- begin-chunk -->
<h4 id="does-rudderstack-support-clickhouse-over-plain-http">Does RudderStack support ClickHouse over plain HTTP?</h4><p>No. Every connection uses HTTPS and verifies the server certificate against publicly trusted certificate authorities.</p>
<!-- end-chunk -->
<!-- begin-chunk -->
<h4 id="is-ssh-tunneling-supported">Is SSH tunneling supported?</h4><p>No. RudderStack does not support SSH tunnels or PrivateLink for the ClickHouse source.</p>
<!-- end-chunk -->
<!-- begin-chunk -->
<h4 id="which-clickhouse-versions-are-supported">Which ClickHouse versions are supported?</h4><p>ClickHouse 26.3 or later. RudderStack checks the version when you connect and at the start of each sync. An older server fails with <code>CH_VERSION_BELOW_FLOOR</code> before RudderStack writes anything.</p>
<!-- end-chunk -->
<!-- begin-chunk -->
<h4 id="why-does-rudderstack-need-access-to-systemquery_log-and-systemprocesses">Why does RudderStack need access to <code>system.query_log</code> and <code>system.processes</code>?</h4><p>RudderStack reads them to stop its own slow queries and to check whether a query finished when its answer was lost. RudderStack reads only the rows of its own user. See <a href="#why-rudderstack-needs-each-permission" >Why RudderStack needs each permission</a>.</p>
<!-- end-chunk -->
<!-- begin-chunk -->
<h4 id="does-rudderstack-write-to-my-data">Does RudderStack write to my data?</h4><p>No. RudderStack reads your data and writes only to the working database (<code>_rudderstack</code>).</p>

