diff --git a/.gitlab/ci/gitlab-gems.gitlab-ci.yml b/.gitlab/ci/gitlab-gems.gitlab-ci.yml
index d972139ad6ee1f428289607668bb3ae7a775832f..ff961877309ac298cb06d8db38e6ef011881eb5a 100644
--- a/.gitlab/ci/gitlab-gems.gitlab-ci.yml
+++ b/.gitlab/ci/gitlab-gems.gitlab-ci.yml
@@ -8,3 +8,6 @@ include:
   - local: .gitlab/ci/templates/gem.gitlab-ci.yml
     inputs:
       gem_name: "gitlab-utils"
+  - local: .gitlab/ci/templates/gem.gitlab-ci.yml
+    inputs:
+      gem_name: "click_house-client"
diff --git a/Gemfile b/Gemfile
index 9ce66bdb455d0c7ec29d6f98e82c76dd354079ae..51d5ba5c3a6394c53897e9c2c33624b168e9a113 100644
--- a/Gemfile
+++ b/Gemfile
@@ -326,6 +326,7 @@ gem 'sassc-rails', '~> 2.1.0'
 gem 'autoprefixer-rails', '10.2.5.1'
 gem 'terser', '1.0.2'
 
+gem 'click_house-client', path: 'gems/click_house-client', require: 'click_house/client'
 gem 'addressable', '~> 2.8'
 gem 'tanuki_emoji', '~> 0.6'
 gem 'gon', '~> 6.4.0'
diff --git a/Gemfile.lock b/Gemfile.lock
index c2de0e0deb628d6349dc16e816a1d24acbf941e5..80e92e603b1e67b88a3536ece6af192a7b72453a 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -4,6 +4,14 @@ PATH
     activerecord-gitlab (0.1.0)
       activerecord (>= 6.1.7.3)
 
+PATH
+  remote: gems/click_house-client
+  specs:
+    click_house-client (0.1.0)
+      activesupport (< 8)
+      addressable (~> 2.8)
+      json (~> 2.6.3)
+
 PATH
   remote: gems/gitlab-rspec
   specs:
@@ -1733,6 +1741,7 @@ DEPENDENCIES
   carrierwave (~> 1.3)
   charlock_holmes (~> 0.7.7)
   circuitbox (= 2.0.0)
+  click_house-client!
   cloud_profiler_agent (~> 0.0.0)!
   commonmarker (~> 0.23.9)
   concurrent-ruby (~> 1.1)
diff --git a/config/initializers/click_house.rb b/config/initializers/click_house.rb
new file mode 100644
index 0000000000000000000000000000000000000000..7fc216d9a59469651dd6ce22515df6913f667e6c
--- /dev/null
+++ b/config/initializers/click_house.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+return unless File.exist?(Rails.root.join('config/click_house.yml'))
+
+raw_config = Rails.application.config_for(:click_house)
+return if raw_config.blank?
+
+ClickHouse::Client.configure do |config|
+  raw_config.each do |database_identifier, db_config|
+    config.register_database(database_identifier,
+      database: db_config[:database],
+      url: db_config[:url],
+      username: db_config[:username],
+      password: db_config[:password],
+      variables: db_config[:variables] || {}
+    )
+  end
+
+  config.json_parser = Gitlab::Json
+  config.http_post_proc = ->(url, headers, body) do
+    options = {
+      headers: headers,
+      body: body,
+      allow_local_requests: Rails.env.development? || Rails.env.test?
+    }
+
+    response = Gitlab::HTTP.post(url, options)
+    ClickHouse::Client::Response.new(response.body, response.code)
+  end
+end
diff --git a/gems/click_house-client/.gitlab-ci.yml b/gems/click_house-client/.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..0384da8a893186a86b2dd149f572f634b0e99b67
--- /dev/null
+++ b/gems/click_house-client/.gitlab-ci.yml
@@ -0,0 +1,4 @@
+include:
+  - local: gems/gem.gitlab-ci.yml
+    inputs:
+      gem_name: "click_house-client"
diff --git a/gems/click_house-client/.rspec b/gems/click_house-client/.rspec
new file mode 100644
index 0000000000000000000000000000000000000000..34c5164d9b56c7d528f061c97f2d2fe02c834bdd
--- /dev/null
+++ b/gems/click_house-client/.rspec
@@ -0,0 +1,3 @@
+--format documentation
+--color
+--require spec_helper
diff --git a/gems/click_house-client/.rubocop.yml b/gems/click_house-client/.rubocop.yml
new file mode 100644
index 0000000000000000000000000000000000000000..24d88acff8184e2f58d75a857654d39868082ac9
--- /dev/null
+++ b/gems/click_house-client/.rubocop.yml
@@ -0,0 +1,17 @@
+inherit_from:
+  - ../../.rubocop.yml
+
+CodeReuse/ActiveRecord:
+  Enabled: false
+
+Rails/ApplicationRecord:
+  Enabled: false
+
+AllCops:
+  TargetRubyVersion: 3.0
+
+Naming/FileName:
+  Exclude:
+    - spec/**/*.rb
+    - lib/gitlab/rspec.rb
+    - lib/gitlab/rspec/all.rb
diff --git a/gems/click_house-client/Gemfile b/gems/click_house-client/Gemfile
new file mode 100644
index 0000000000000000000000000000000000000000..be173b205f70152dd1e0a0319f131e51c49eb9cd
--- /dev/null
+++ b/gems/click_house-client/Gemfile
@@ -0,0 +1,5 @@
+# frozen_string_literal: true
+
+source "https://rubygems.org"
+
+gemspec
diff --git a/gems/click_house-client/Gemfile.lock b/gems/click_house-client/Gemfile.lock
new file mode 100644
index 0000000000000000000000000000000000000000..088e39c07fb226c4620fc9f1f03f026fc4056559
--- /dev/null
+++ b/gems/click_house-client/Gemfile.lock
@@ -0,0 +1,102 @@
+PATH
+  remote: .
+  specs:
+    click_house-client (0.1.0)
+      activesupport (< 8)
+      addressable (~> 2.8)
+      json (~> 2.6.3)
+
+GEM
+  remote: https://rubygems.org/
+  specs:
+    activesupport (7.0.6)
+      concurrent-ruby (~> 1.0, >= 1.0.2)
+      i18n (>= 1.6, < 2)
+      minitest (>= 5.1)
+      tzinfo (~> 2.0)
+    addressable (2.8.4)
+      public_suffix (>= 2.0.2, < 6.0)
+    ast (2.4.2)
+    concurrent-ruby (1.2.2)
+    diff-lcs (1.5.0)
+    gitlab-styles (10.1.0)
+      rubocop (~> 1.50.2)
+      rubocop-graphql (~> 0.18)
+      rubocop-performance (~> 1.15)
+      rubocop-rails (~> 2.17)
+      rubocop-rspec (~> 2.22)
+    i18n (1.14.1)
+      concurrent-ruby (~> 1.0)
+    json (2.6.3)
+    minitest (5.18.1)
+    parallel (1.23.0)
+    parser (3.2.2.3)
+      ast (~> 2.4.1)
+      racc
+    public_suffix (5.0.1)
+    racc (1.7.1)
+    rack (3.0.8)
+    rainbow (3.1.1)
+    rake (13.0.6)
+    regexp_parser (2.8.1)
+    rexml (3.2.5)
+    rspec (3.12.0)
+      rspec-core (~> 3.12.0)
+      rspec-expectations (~> 3.12.0)
+      rspec-mocks (~> 3.12.0)
+    rspec-core (3.12.2)
+      rspec-support (~> 3.12.0)
+    rspec-expectations (3.12.3)
+      diff-lcs (>= 1.2.0, < 2.0)
+      rspec-support (~> 3.12.0)
+    rspec-mocks (3.12.5)
+      diff-lcs (>= 1.2.0, < 2.0)
+      rspec-support (~> 3.12.0)
+    rspec-support (3.12.1)
+    rubocop (1.50.2)
+      json (~> 2.3)
+      parallel (~> 1.10)
+      parser (>= 3.2.0.0)
+      rainbow (>= 2.2.2, < 4.0)
+      regexp_parser (>= 1.8, < 3.0)
+      rexml (>= 3.2.5, < 4.0)
+      rubocop-ast (>= 1.28.0, < 2.0)
+      ruby-progressbar (~> 1.7)
+      unicode-display_width (>= 2.4.0, < 3.0)
+    rubocop-ast (1.29.0)
+      parser (>= 3.2.1.0)
+    rubocop-capybara (2.18.0)
+      rubocop (~> 1.41)
+    rubocop-factory_bot (2.23.1)
+      rubocop (~> 1.33)
+    rubocop-graphql (0.19.0)
+      rubocop (>= 0.87, < 2)
+    rubocop-performance (1.18.0)
+      rubocop (>= 1.7.0, < 2.0)
+      rubocop-ast (>= 0.4.0)
+    rubocop-rails (2.20.2)
+      activesupport (>= 4.2.0)
+      rack (>= 1.1)
+      rubocop (>= 1.33.0, < 2.0)
+    rubocop-rspec (2.22.0)
+      rubocop (~> 1.33)
+      rubocop-capybara (~> 2.17)
+      rubocop-factory_bot (~> 2.22)
+    ruby-progressbar (1.13.0)
+    tzinfo (2.0.6)
+      concurrent-ruby (~> 1.0)
+    unicode-display_width (2.4.2)
+
+PLATFORMS
+  x86_64-linux
+
+DEPENDENCIES
+  click_house-client!
+  gitlab-styles (~> 10.1.0)
+  rake (~> 13.0)
+  rspec (~> 3.0)
+  rubocop
+  rubocop-rspec
+
+BUNDLED WITH
+   2.4.15
diff --git a/gems/click_house-client/README.md b/gems/click_house-client/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..6afabcad1a021514e28c8f1c6f2a9f54e63bcb5c
--- /dev/null
+++ b/gems/click_house-client/README.md
@@ -0,0 +1,3 @@
+ClickHouse::Client
+
+This Gem provides a simple way to query ClickHouse databases using the HTTP interface. 
diff --git a/gems/click_house-client/click_house-client.gemspec b/gems/click_house-client/click_house-client.gemspec
new file mode 100644
index 0000000000000000000000000000000000000000..5544065ef17077d0151360ad05cdc0df237509a5
--- /dev/null
+++ b/gems/click_house-client/click_house-client.gemspec
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+Gem::Specification.new do |spec|
+  spec.name = "click_house-client"
+  spec.version = "0.1.0"
+  spec.authors = ["group::optimize"]
+  spec.email = ["engineering@gitlab.com"]
+
+  spec.summary = "GitLab's client to interact with ClickHouse"
+  spec.description = "This Gem provides a simple way to query ClickHouse databases using the HTTP interface."
+  spec.homepage = "https://gitlab.com/gitlab-org/gitlab/-/tree/master/gems/click_house-client"
+  spec.license = "MIT"
+  spec.required_ruby_version = ">= 3.0"
+
+  spec.add_runtime_dependency "activesupport", "< 8"
+  spec.add_runtime_dependency "addressable", "~> 2.8"
+  spec.add_runtime_dependency 'json', '~> 2.6.3'
+
+  spec.add_development_dependency 'gitlab-styles', '~> 10.1.0'
+  spec.add_development_dependency "rake", "~> 13.0"
+  spec.add_development_dependency "rspec", "~> 3.0"
+  spec.add_development_dependency 'rubocop'
+  spec.add_development_dependency 'rubocop-rspec'
+end
diff --git a/gems/click_house-client/lib/click_house/client.rb b/gems/click_house-client/lib/click_house/client.rb
new file mode 100644
index 0000000000000000000000000000000000000000..1c8da64f38f7002616f7fe23b99db9a053a5032f
--- /dev/null
+++ b/gems/click_house-client/lib/click_house/client.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+require 'addressable'
+require 'json'
+require 'active_support/time'
+require_relative "client/database"
+require_relative "client/configuration"
+require_relative "client/formatter"
+require_relative "client/response"
+
+module ClickHouse
+  module Client
+    class << self
+      def configuration
+        @configuration ||= Configuration.new
+      end
+
+      def configure
+        yield(configuration)
+        configuration.validate!
+      end
+    end
+
+    Error = Class.new(StandardError)
+    ConfigurationError = Class.new(Error)
+    DatabaseError = Class.new(Error)
+
+    def self.execute(query, database, configuration = self.configuration)
+      db = configuration.databases[database]
+      raise ConfigurationError, "The database '#{database}' is not configured" unless db
+
+      response = configuration.http_post_proc.call(
+        db.uri.to_s,
+        db.headers,
+        "#{query} FORMAT JSON" # always return JSON
+      )
+
+      raise DatabaseError, response.body unless response.success?
+
+      Formatter.format(configuration.json_parser.parse(response.body))
+    end
+  end
+end
diff --git a/gems/click_house-client/lib/click_house/client/configuration.rb b/gems/click_house-client/lib/click_house/client/configuration.rb
new file mode 100644
index 0000000000000000000000000000000000000000..882b37993dc83a804ca9f9ad1fdc5815fbd2dc3e
--- /dev/null
+++ b/gems/click_house-client/lib/click_house/client/configuration.rb
@@ -0,0 +1,68 @@
+# frozen_string_literal: true
+
+module ClickHouse
+  module Client
+    class Configuration
+      # Configuration options:
+      #
+      # *register_database* (method): registers a database, the following arguments are required:
+      #   - database: database name
+      #   - url: URL and port to the HTTP interface
+      #   - username
+      #   - password
+      #   - variables (optional): configuration for the client
+      #
+      # *http_post_proc*: A callable object for invoking the HTTP request.
+      #   The object must handle the following parameters: url, headers, body
+      #   and return a Gitlab::ClickHouse::Client::Response object.
+      #
+      # *json_parser*: object for parsing JSON strings, it should respond to the "parse" method
+      #
+      # Example:
+      #
+      # Gitlab::ClickHouse::Client.configure do |c|
+      #   c.register_database(:main,
+      #     database: 'gitlab_clickhouse_test',
+      #     url: 'http://localhost:8123',
+      #     username: 'default',
+      #     password: 'clickhouse',
+      #     variables: {
+      #       join_use_nulls: 1 # treat JOINs as per SQL standard
+      #     }
+      #   )
+      #
+      #   c.http_post_proc = lambda do |url, headers, body|
+      #     options = {
+      #       headers: headers,
+      #       body: body,
+      #       allow_local_requests: false
+      #     }
+      #
+      #     response = Gitlab::HTTP.post(url, options)
+      #     Gitlab::ClickHouse::Client::Response.new(response.body, response.code)
+      #   end
+      #
+      #   c.json_parser = JSON
+      # end
+      attr_accessor :http_post_proc, :json_parser
+      attr_reader :databases
+
+      def initialize
+        @databases = {}
+        @http_post_proc = nil
+        @json_parser = JSON
+      end
+
+      def register_database(name, **args)
+        raise ConfigurationError, "The database '#{name}' is already registered" if @databases.key?(name)
+
+        @databases[name] = Database.new(**args)
+      end
+
+      def validate!
+        raise ConfigurationError, "The 'http_post_proc' option is not configured" unless @http_post_proc
+        raise ConfigurationError, "The 'json_parser' option is not configured" unless @json_parser
+      end
+    end
+  end
+end
diff --git a/gems/click_house-client/lib/click_house/client/database.rb b/gems/click_house-client/lib/click_house/client/database.rb
new file mode 100644
index 0000000000000000000000000000000000000000..beeb2a8cbd6ce0a326266de279eb81bac827beb8
--- /dev/null
+++ b/gems/click_house-client/lib/click_house/client/database.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+module ClickHouse
+  module Client
+    class Database
+      attr_reader :database
+
+      def initialize(database:, url:, username:, password:, variables: {})
+        @database = database
+        @url = url
+        @username = username
+        @password = password
+        @variables = variables.merge(database: database).freeze
+      end
+
+      def uri
+        @uri ||= begin
+          parsed = Addressable::URI.parse(@url)
+          parsed.query_values = @variables
+          parsed
+        end
+      end
+
+      def headers
+        @headers ||= {
+          'X-ClickHouse-User' => @username,
+          'X-ClickHouse-Key' => @password
+        }.freeze
+      end
+    end
+  end
+end
diff --git a/gems/click_house-client/lib/click_house/client/formatter.rb b/gems/click_house-client/lib/click_house/client/formatter.rb
new file mode 100644
index 0000000000000000000000000000000000000000..bb60d8db7f736d185e71a77a2b1606446f9148c9
--- /dev/null
+++ b/gems/click_house-client/lib/click_house/client/formatter.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+module ClickHouse
+  module Client
+    class Formatter
+      DEFAULT = ->(value) { value }
+
+      TYPE_CASTERS = {
+        'UInt64' => ->(value) { Integer(value) },
+        "DateTime64(6, 'UTC')" => ->(value) { ActiveSupport::TimeZone["UTC"].parse(value) }
+      }.freeze
+
+      def self.format(result)
+        name_type_mapping = result['meta'].each_with_object({}) do |column, hash|
+          hash[column['name']] = column['type']
+        end
+
+        result['data'].map do |row|
+          row.each_with_object({}) do |(column, value), casted_row|
+            caster = TYPE_CASTERS.fetch(name_type_mapping[column], DEFAULT)
+
+            casted_row[column] = caster.call(value)
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/gems/click_house-client/lib/click_house/client/response.rb b/gems/click_house-client/lib/click_house/client/response.rb
new file mode 100644
index 0000000000000000000000000000000000000000..898f0b0e0243f164a8b3ceb1feb414f19be0fede
--- /dev/null
+++ b/gems/click_house-client/lib/click_house/client/response.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+module ClickHouse
+  module Client
+    class Response
+      attr_reader :body
+
+      def initialize(body, http_status_code)
+        @body = body
+        @http_status_code = http_status_code
+      end
+
+      def success?
+        @http_status_code == 200
+      end
+    end
+  end
+end
diff --git a/gems/click_house-client/spec/click_house/client/configuration_spec.rb b/gems/click_house-client/spec/click_house/client/configuration_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..8cbd64ca6502de68d4b8aa198511581216bb3f33
--- /dev/null
+++ b/gems/click_house-client/spec/click_house/client/configuration_spec.rb
@@ -0,0 +1,66 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ClickHouse::Client::Configuration do
+  subject(:configuration) do
+    config = described_class.new
+    config.http_post_proc = -> {}
+    config.json_parser = Object
+    config
+  end
+
+  describe '#register_database' do
+    let(:database_options) do
+      {
+        database: 'test_db',
+        url: 'http://localhost:3333',
+        username: 'user',
+        password: 'pass',
+        variables: {
+          join_use_nulls: 1
+        }
+      }
+    end
+
+    it 'registers a database' do
+      configuration.register_database(:my_db, **database_options)
+
+      expect(configuration.databases.size).to eq(1)
+      database = configuration.databases[:my_db]
+
+      expect(database.uri.to_s).to eq('http://localhost:3333?database=test_db&join_use_nulls=1')
+    end
+
+    context 'when adding the same DB multiple times' do
+      it 'raises error' do
+        configuration.register_database(:my_db, **database_options)
+        expect do
+          configuration.register_database(:my_db, **database_options)
+        end.to raise_error(ClickHouse::Client::ConfigurationError, /database 'my_db' is already registered/)
+      end
+    end
+  end
+
+  describe '#validate!' do
+    context 'when `http_post_proc` option is not configured' do
+      it 'raises error' do
+        configuration.http_post_proc = nil
+
+        expect do
+          configuration.validate!
+        end.to raise_error(ClickHouse::Client::ConfigurationError, /'http_post_proc' option is not configured/)
+      end
+    end
+
+    context 'when `json_parser` option is not configured' do
+      it 'raises error' do
+        configuration.json_parser = nil
+
+        expect do
+          configuration.validate!
+        end.to raise_error(ClickHouse::Client::ConfigurationError, /'json_parser' option is not configured/)
+      end
+    end
+  end
+end
diff --git a/gems/click_house-client/spec/click_house/client/database_spec.rb b/gems/click_house-client/spec/click_house/client/database_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..112b2ee12b17482d5481ab5f436140748d5888b2
--- /dev/null
+++ b/gems/click_house-client/spec/click_house/client/database_spec.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ClickHouse::Client::Database do
+  subject(:database) do
+    described_class.new(
+      database: 'test_db',
+      url: 'http://localhost:3333',
+      username: 'user',
+      password: 'pass',
+      variables: {
+        join_use_nulls: 1
+      }
+    )
+  end
+
+  describe '#uri' do
+    it 'builds the correct URL' do
+      expect(database.uri.to_s).to eq('http://localhost:3333?database=test_db&join_use_nulls=1')
+    end
+  end
+
+  describe '#headers' do
+    it 'returns the correct headers' do
+      expect(database.headers).to eq({
+        'X-ClickHouse-User' => 'user',
+        'X-ClickHouse-Key' => 'pass'
+      })
+    end
+  end
+end
diff --git a/gems/click_house-client/spec/click_house/client_spec.rb b/gems/click_house-client/spec/click_house/client_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..19850bc722745d43f51f4172669d133991dc2f69
--- /dev/null
+++ b/gems/click_house-client/spec/click_house/client_spec.rb
@@ -0,0 +1,98 @@
+# frozen_string_literal: true
+
+RSpec.describe ClickHouse::Client do
+  describe '#execute' do
+    # Assuming we have a DB table with the following schema
+    #
+    # CREATE TABLE issues (
+    #   `id` UInt64,
+    #   `title` String DEFAULT '',
+    #   `description` Nullable(String),
+    #   `created_at` DateTime64(6, 'UTC') DEFAULT now(),
+    #   `updated_at` DateTime64(6, 'UTC') DEFAULT now()
+    # )
+    # ENGINE = ReplacingMergeTree(updated_at)
+    # ORDER BY (id)
+
+    let(:query_result_fixture) { File.expand_path('../fixtures/query_result.json', __dir__) }
+
+    let(:database_config) do
+      {
+        database: 'test_db',
+        url: 'http://localhost:3333',
+        username: 'user',
+        password: 'pass',
+        variables: {
+          join_use_nulls: 1
+        }
+      }
+    end
+
+    let(:configuration) do
+      ClickHouse::Client::Configuration.new.tap do |config|
+        config.register_database(:test_db, **database_config)
+        config.http_post_proc = ->(_url, _headers, _query) {
+          body = File.read(query_result_fixture)
+          ClickHouse::Client::Response.new(body, 200)
+        }
+      end
+    end
+
+    it 'parses the results and returns the data as array of hashes' do
+      result = described_class.execute('SELECT * FROM issues', :test_db, configuration)
+
+      timestamp1 = ActiveSupport::TimeZone["UTC"].parse('2023-06-21 13:33:44')
+      timestamp2 = ActiveSupport::TimeZone["UTC"].parse('2023-06-21 13:33:50')
+      timestamp3 = ActiveSupport::TimeZone["UTC"].parse('2023-06-21 13:33:40')
+
+      expect(result).to eq([
+        {
+          'id' => 2,
+          'title' => 'Title 2',
+          'description' => 'description',
+          'created_at' => timestamp1,
+          'updated_at' => timestamp1
+        },
+        {
+          'id' => 3,
+          'title' => 'Title 3',
+          'description' => nil,
+          'created_at' => timestamp2,
+          'updated_at' => timestamp2
+        },
+        {
+          'id' => 1,
+          'title' => 'Title 1',
+          'description' => 'description',
+          'created_at' => timestamp3,
+          'updated_at' => timestamp3
+        }
+      ])
+    end
+
+    context 'when the DB is not configured' do
+      it 'raises erro' do
+        expect do
+          described_class.execute('SELECT * FROM issues', :different_db, configuration)
+        end.to raise_error(ClickHouse::Client::ConfigurationError, /not configured/)
+      end
+    end
+
+    context 'when error response is returned' do
+      let(:configuration) do
+        ClickHouse::Client::Configuration.new.tap do |config|
+          config.register_database(:test_db, **database_config)
+          config.http_post_proc = ->(_url, _headers, _query) {
+            ClickHouse::Client::Response.new('some error', 404)
+          }
+        end
+      end
+
+      it 'raises error' do
+        expect do
+          described_class.execute('SELECT * FROM issues', :test_db, configuration)
+        end.to raise_error(ClickHouse::Client::DatabaseError, 'some error')
+      end
+    end
+  end
+end
diff --git a/gems/click_house-client/spec/fixtures/query_result.json b/gems/click_house-client/spec/fixtures/query_result.json
new file mode 100644
index 0000000000000000000000000000000000000000..872b0e5b6edbefab6683ca67cd23944b79a0daed
--- /dev/null
+++ b/gems/click_house-client/spec/fixtures/query_result.json
@@ -0,0 +1,53 @@
+{
+  "meta": [
+    {
+      "name": "id",
+      "type": "UInt64"
+    },
+    {
+      "name": "title",
+      "type:": "String"
+    },
+    {
+      "name": "description",
+      "type": "Nullable(String)"
+    },
+    {
+      "name": "created_at",
+      "type": "DateTime64(6, 'UTC')"
+    },
+    {
+      "name": "updated_at",
+      "type": "DateTime64(6, 'UTC')"
+    }
+  ],
+  "data": [
+    {
+      "id": "2",
+      "title": "Title 2",
+      "description": "description",
+      "created_at": "2023-06-21 13:33:44.000000",
+      "updated_at": "2023-06-21 13:33:44.000000"
+    },
+    {
+      "id": "3",
+      "title": "Title 3",
+      "description": null,
+      "created_at": "2023-06-21 13:33:50.000000",
+      "updated_at": "2023-06-21 13:33:50.000000"
+    },
+    {
+      "id": "1",
+      "title": "Title 1",
+      "description": "description",
+      "created_at": "2023-06-21 13:33:40.000000",
+      "updated_at": "2023-06-21 13:33:40.000000"
+    }
+  ],
+  "rows": 3,
+  "statistics": {
+    "elapsed": 0.001581789,
+    "rows_read": 3,
+    "bytes_read": 172
+  }
+}
diff --git a/gems/click_house-client/spec/spec_helper.rb b/gems/click_house-client/spec/spec_helper.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d2b5e59c9a370b7fec656c646229b1f63c937a87
--- /dev/null
+++ b/gems/click_house-client/spec/spec_helper.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require "click_house/client"
+
+RSpec.configure do |config|
+  # Enable flags like --only-failures and --next-failure
+  config.example_status_persistence_file_path = ".rspec_status"
+
+  # Disable RSpec exposing methods globally on `Module` and `main`
+  config.disable_monkey_patching!
+
+  config.expect_with :rspec do |c|
+    c.syntax = :expect
+  end
+end
diff --git a/spec/lib/gitlab/database/click_house_client_spec.rb b/spec/lib/gitlab/database/click_house_client_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..9260475fa4fdb9b4bab8defc95cd4aed4804333a
--- /dev/null
+++ b/spec/lib/gitlab/database/click_house_client_spec.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'ClickHouse::Client', feature_category: :database do
+  it 'does not have any databases configured' do
+    databases = ClickHouse::Client.configuration.databases
+
+    expect(databases).to be_empty
+  end
+end