diff --git a/Gemfile.lock b/Gemfile.lock
index 0a711333a8c11b77c5f7a06b8f39d972655d475e..92eeb2b933329f1b671eeff007b1dce96485988b 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -27,6 +27,9 @@ PATH
   remote: gems/gitlab-active-context
   specs:
     gitlab-active-context (0.0.1)
+      activesupport
+      connection_pool
+      pg
       zeitwerk
 
 PATH
diff --git a/Gemfile.next.lock b/Gemfile.next.lock
index cc360eb448a08c48285c55089b9251b708339d76..c1b711fecdb38e510c699e0e899cdc06e0bfb4ed 100644
--- a/Gemfile.next.lock
+++ b/Gemfile.next.lock
@@ -27,6 +27,9 @@ PATH
   remote: gems/gitlab-active-context
   specs:
     gitlab-active-context (0.0.1)
+      activesupport
+      connection_pool
+      pg
       zeitwerk
 
 PATH
diff --git a/gems/gitlab-active-context/Gemfile.lock b/gems/gitlab-active-context/Gemfile.lock
index f20e0bf5e27c83c9a2966d22687ad0e33ece412c..8bb6eb61ac590d5874269f3547872f4dc04f35f1 100644
--- a/gems/gitlab-active-context/Gemfile.lock
+++ b/gems/gitlab-active-context/Gemfile.lock
@@ -2,6 +2,9 @@ PATH
   remote: .
   specs:
     gitlab-active-context (0.0.1)
+      activesupport
+      connection_pool
+      pg
       zeitwerk
 
 GEM
@@ -87,6 +90,7 @@ GEM
     parser (3.3.6.0)
       ast (~> 2.4.1)
       racc
+    pg (1.5.9)
     psych (5.2.1)
       date
       stringio
diff --git a/gems/gitlab-active-context/gitlab-active-context.gemspec b/gems/gitlab-active-context/gitlab-active-context.gemspec
index fe9c5bb7f7c3b39e686e9651a262733350de377a..e96cded82b8f778a933a5d928c4f0544579399cf 100644
--- a/gems/gitlab-active-context/gitlab-active-context.gemspec
+++ b/gems/gitlab-active-context/gitlab-active-context.gemspec
@@ -19,6 +19,9 @@ Gem::Specification.new do |spec|
   spec.files = Dir['lib/**/*.rb']
   spec.require_paths = ["lib"]
 
+  spec.add_dependency 'activesupport'
+  spec.add_dependency 'connection_pool'
+  spec.add_dependency 'pg'
   spec.add_dependency 'zeitwerk'
 
   spec.add_development_dependency 'gitlab-styles'
diff --git a/gems/gitlab-active-context/lib/active_context.rb b/gems/gitlab-active-context/lib/active_context.rb
index f27e283de190346d4e6889b1f4eab129f548b177..b729911ca99ce6ed1aa9102b4fe97b72948d6df9 100644
--- a/gems/gitlab-active-context/lib/active_context.rb
+++ b/gems/gitlab-active-context/lib/active_context.rb
@@ -1,6 +1,10 @@
 # frozen_string_literal: true
 
-require "zeitwerk"
+require 'active_support/core_ext/string/inflections'
+require 'active_support/core_ext/module/delegation'
+require 'connection_pool'
+require 'pg'
+require 'zeitwerk'
 loader = Zeitwerk::Loader.for_gem
 loader.setup
 
@@ -8,4 +12,12 @@ module ActiveContext
   def self.configure(...)
     ActiveContext::Config.configure(...)
   end
+
+  def self.config
+    ActiveContext::Config.current
+  end
+
+  def self.adapter
+    ActiveContext::Adapter.current
+  end
 end
diff --git a/gems/gitlab-active-context/lib/active_context/adapter.rb b/gems/gitlab-active-context/lib/active_context/adapter.rb
new file mode 100644
index 0000000000000000000000000000000000000000..48f0462a75d92767c6208603bfcc46e61facda97
--- /dev/null
+++ b/gems/gitlab-active-context/lib/active_context/adapter.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+module ActiveContext
+  module Adapter
+    class << self
+      def current
+        @current ||= load_adapter
+      end
+
+      private
+
+      def load_adapter
+        config = ActiveContext::Config.current
+        return nil unless config.enabled
+
+        name, hash = config.databases.first
+        return nil unless name
+
+        adapter = hash.fetch(:adapter)
+        return nil unless adapter
+
+        adapter_klass = adapter.safe_constantize
+        return nil unless adapter_klass
+
+        options = hash.fetch(:options)
+
+        adapter_klass.new(options)
+      end
+    end
+  end
+end
diff --git a/gems/gitlab-active-context/lib/active_context/config.rb b/gems/gitlab-active-context/lib/active_context/config.rb
index 60f8e1399e89e8e7498c9202a6f674747c9d7e2b..06ca412babbbe30180a44fce2d9d46c18e82f662 100644
--- a/gems/gitlab-active-context/lib/active_context/config.rb
+++ b/gems/gitlab-active-context/lib/active_context/config.rb
@@ -2,27 +2,27 @@
 
 module ActiveContext
   class Config
-    CONFIG = Struct.new(:enabled, :databases, :logger)
+    Cfg = Struct.new(:enabled, :databases, :logger)
 
     class << self
       def configure(&block)
         @instance = new(block)
       end
 
-      def config
-        @instance&.config || {}
+      def current
+        @instance&.config || Cfg.new
       end
 
       def enabled?
-        config.enabled || false
+        current.enabled || false
       end
 
       def databases
-        config.databases || {}
+        current.databases || {}
       end
 
       def logger
-        config.logger || Logger.new($stdout)
+        current.logger || Logger.new($stdout)
       end
     end
 
@@ -31,7 +31,7 @@ def initialize(config_block)
     end
 
     def config
-      struct = CONFIG.new
+      struct = Cfg.new
       @config_block.call(struct)
       struct
     end
diff --git a/gems/gitlab-active-context/lib/active_context/databases/concerns/adapter.rb b/gems/gitlab-active-context/lib/active_context/databases/concerns/adapter.rb
new file mode 100644
index 0000000000000000000000000000000000000000..bf8854efbfa9bd84655810ee3b05a5d46cb823c9
--- /dev/null
+++ b/gems/gitlab-active-context/lib/active_context/databases/concerns/adapter.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module ActiveContext
+  module Databases
+    module Concerns
+      module Adapter
+        attr_reader :client
+
+        delegate :search, to: :client
+
+        def initialize(options)
+          @client = client_klass.new(options)
+        end
+
+        def client_klass
+          raise NotImplementedError
+        end
+      end
+    end
+  end
+end
diff --git a/gems/gitlab-active-context/lib/active_context/databases/concerns/client.rb b/gems/gitlab-active-context/lib/active_context/databases/concerns/client.rb
new file mode 100644
index 0000000000000000000000000000000000000000..08cb6b5da672a843ba737b51d16b6e4d98b212b5
--- /dev/null
+++ b/gems/gitlab-active-context/lib/active_context/databases/concerns/client.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module ActiveContext
+  module Databases
+    module Concerns
+      module Client
+        DEFAULT_PREFIX = 'gitlab'
+
+        attr_reader :options
+
+        def prefix
+          options[:prefix] || DEFAULT_PREFIX
+        end
+
+        def search(_)
+          raise NotImplementedError
+        end
+      end
+    end
+  end
+end
diff --git a/gems/gitlab-active-context/lib/active_context/databases/concerns/query_result.rb b/gems/gitlab-active-context/lib/active_context/databases/concerns/query_result.rb
new file mode 100644
index 0000000000000000000000000000000000000000..9ee353a6acfd0a328848011b84b93a1310c2d501
--- /dev/null
+++ b/gems/gitlab-active-context/lib/active_context/databases/concerns/query_result.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module ActiveContext
+  module Databases
+    module Concerns
+      module QueryResult
+        include Enumerable
+
+        def each
+          raise NotImplementedError
+        end
+      end
+    end
+  end
+end
diff --git a/gems/gitlab-active-context/lib/active_context/databases/postgresql/adapter.rb b/gems/gitlab-active-context/lib/active_context/databases/postgresql/adapter.rb
new file mode 100644
index 0000000000000000000000000000000000000000..33f2b2b199b83e93fbe66cfdc2fda16f19184d2a
--- /dev/null
+++ b/gems/gitlab-active-context/lib/active_context/databases/postgresql/adapter.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module ActiveContext
+  module Databases
+    module Postgresql
+      class Adapter
+        include ActiveContext::Databases::Concerns::Adapter
+
+        def client_klass
+          ActiveContext::Databases::Postgresql::Client
+        end
+      end
+    end
+  end
+end
diff --git a/gems/gitlab-active-context/lib/active_context/databases/postgresql/client.rb b/gems/gitlab-active-context/lib/active_context/databases/postgresql/client.rb
new file mode 100644
index 0000000000000000000000000000000000000000..1ab361b20b38acc6da217b544f95837ae2d99b3d
--- /dev/null
+++ b/gems/gitlab-active-context/lib/active_context/databases/postgresql/client.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+module ActiveContext
+  module Databases
+    module Postgresql
+      class Client
+        include ActiveContext::Databases::Concerns::Client
+
+        DEFAULT_POOL_SIZE = 5
+        DEFAULT_POOL_TIMEOUT = 5
+
+        def initialize(options)
+          @options = options
+          @pool = ConnectionPool.new(
+            size: options.fetch(:pool_size, DEFAULT_POOL_SIZE),
+            timeout: options.fetch(:pool_timeout, DEFAULT_POOL_TIMEOUT)
+          ) do
+            PG.connect(connection_params)
+          end
+        end
+
+        def search(_query)
+          with_connection do |conn|
+            res = conn.exec('SELECT * FROM pg_stat_activity')
+            QueryResult.new(res)
+          end
+        end
+
+        private
+
+        def with_connection
+          @pool.with do |conn|
+            yield(conn)
+          end
+        end
+
+        def close
+          @pool&.shutdown(&:close)
+        end
+
+        def connection_params
+          {
+            host: options[:host],
+            port: options[:port],
+            dbname: options[:database],
+            user: options[:username],
+            password: options[:password],
+            connect_timeout: options.fetch(:connect_timeout, 5)
+          }.compact
+        end
+      end
+    end
+  end
+end
diff --git a/gems/gitlab-active-context/lib/active_context/databases/postgresql/query_result.rb b/gems/gitlab-active-context/lib/active_context/databases/postgresql/query_result.rb
new file mode 100644
index 0000000000000000000000000000000000000000..dfdf4920b371735ad12ce3beb63abbcee4bbdb19
--- /dev/null
+++ b/gems/gitlab-active-context/lib/active_context/databases/postgresql/query_result.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+module ActiveContext
+  module Databases
+    module Postgresql
+      class QueryResult
+        include ActiveContext::Databases::Concerns::QueryResult
+
+        def initialize(pg_result)
+          @pg_result = pg_result
+        end
+
+        def each
+          return enum_for(:each) unless block_given?
+
+          pg_result.each do |row|
+            yield row
+          end
+        end
+
+        def count
+          pg_result.ntuples
+        end
+
+        def clear
+          pg_result.clear if pg_result.respond_to?(:clear)
+        end
+
+        private
+
+        attr_reader :pg_result
+      end
+    end
+  end
+end
diff --git a/gems/gitlab-active-context/spec/active_context_spec.rb b/gems/gitlab-active-context/spec/active_context_spec.rb
index 6b9bc738671b2c83f2b1cb60b3d333e67fdbea9a..81708b4f02c9394458ca2cf5101fd23f179cc41f 100644
--- a/gems/gitlab-active-context/spec/active_context_spec.rb
+++ b/gems/gitlab-active-context/spec/active_context_spec.rb
@@ -28,4 +28,35 @@
       expect(ActiveContext::Config.logger).to be_a(::Logger)
     end
   end
+
+  describe '.config' do
+    it 'returns the current configuration' do
+      config = described_class.config
+      expect(config).to be_a(ActiveContext::Config::Cfg)
+    end
+  end
+
+  describe '.adapter' do
+    it 'returns nil when not configured' do
+      expect(described_class.adapter).to be_nil
+    end
+
+    it 'returns configured adapter' do
+      described_class.configure do |config|
+        config.enabled = true
+        config.databases = {
+          main: {
+            adapter: 'ActiveContext::Databases::Postgresql::Adapter',
+            options: {
+              host: 'localhost',
+              port: 5432,
+              database: 'test_db'
+            }
+          }
+        }
+      end
+
+      expect(described_class.adapter).to be_a(ActiveContext::Databases::Postgresql::Adapter)
+    end
+  end
 end
diff --git a/gems/gitlab-active-context/spec/lib/active_context/databases/postgresql/adapter_spec.rb b/gems/gitlab-active-context/spec/lib/active_context/databases/postgresql/adapter_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..059efe66f485596a9753a878ac18a3e7540d6b22
--- /dev/null
+++ b/gems/gitlab-active-context/spec/lib/active_context/databases/postgresql/adapter_spec.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+RSpec.describe ActiveContext::Databases::Postgresql::Adapter do
+  let(:options) do
+    {
+      host: 'localhost',
+      port: 5432,
+      database: 'test_db',
+      username: 'user',
+      password: 'pass'
+    }
+  end
+
+  subject(:adapter) { described_class.new(options) }
+
+  it 'delegates search to client' do
+    query = ActiveContext::Query.filter(foo: :bar)
+    expect(adapter.client).to receive(:search).with(query)
+
+    adapter.search(query)
+  end
+end
diff --git a/gems/gitlab-active-context/spec/lib/active_context/databases/postgresql/client_spec.rb b/gems/gitlab-active-context/spec/lib/active_context/databases/postgresql/client_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..7b5e2e7191789fa941de37b0c21aca932f57aa86
--- /dev/null
+++ b/gems/gitlab-active-context/spec/lib/active_context/databases/postgresql/client_spec.rb
@@ -0,0 +1,55 @@
+# frozen_string_literal: true
+
+RSpec.describe ActiveContext::Databases::Postgresql::Client do
+  let(:options) do
+    {
+      host: 'localhost',
+      port: 5432,
+      database: 'test_db',
+      username: 'user',
+      password: 'pass',
+      pool_size: 2,
+      pool_timeout: 1
+    }
+  end
+
+  subject(:client) { described_class.new(options) }
+
+  describe '#initialize' do
+    it 'creates a connection pool' do
+      expect(ConnectionPool).to receive(:new)
+        .with(hash_including(size: 2, timeout: 1))
+
+      client
+    end
+  end
+
+  describe '#search' do
+    let(:connection) { instance_double(PG::Connection) }
+    let(:query_result) { instance_double(PG::Result) }
+
+    before do
+      allow(PG).to receive(:connect).and_return(connection)
+      allow(connection).to receive(:exec).and_return(query_result)
+    end
+
+    it 'executes query and returns QueryResult' do
+      expect(connection).to receive(:exec).with('SELECT * FROM pg_stat_activity')
+      expect(ActiveContext::Databases::Postgresql::QueryResult)
+        .to receive(:new).with(query_result)
+
+      client.search('test query')
+    end
+  end
+
+  describe '#prefix' do
+    it 'returns default prefix when not specified' do
+      expect(client.prefix).to eq('gitlab')
+    end
+
+    it 'returns configured prefix' do
+      client = described_class.new(options.merge(prefix: 'custom'))
+      expect(client.prefix).to eq('custom')
+    end
+  end
+end
diff --git a/gems/gitlab-active-context/spec/lib/active_context/databases/postgresql/query_result_spec.rb b/gems/gitlab-active-context/spec/lib/active_context/databases/postgresql/query_result_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..20f52b6efc1453bad0e5128543893ac14b21e321
--- /dev/null
+++ b/gems/gitlab-active-context/spec/lib/active_context/databases/postgresql/query_result_spec.rb
@@ -0,0 +1,55 @@
+# frozen_string_literal: true
+
+RSpec.describe ActiveContext::Databases::Postgresql::QueryResult do
+  let(:pg_result) { instance_double(PG::Result) }
+
+  subject(:query_result) { described_class.new(pg_result) }
+
+  describe '#each' do
+    it 'yields each row' do
+      rows = [
+        { 'id' => 1, 'name' => 'test1' },
+        { 'id' => 2, 'name' => 'test2' }
+      ]
+
+      allow(pg_result).to receive(:each).and_yield(rows[0]).and_yield(rows[1])
+
+      expect { |b| query_result.each(&b) }.to yield_successive_args(*rows)
+    end
+
+    it 'returns enumerator when no block given' do
+      expect(query_result.each).to be_a(Enumerator)
+    end
+  end
+
+  describe '#count' do
+    it 'returns number of tuples' do
+      allow(pg_result).to receive(:ntuples).and_return(5)
+      expect(query_result.count).to eq(5)
+    end
+  end
+
+  describe '#clear' do
+    context 'when pg_result responds to clear' do
+      before do
+        allow(pg_result).to receive(:respond_to?).with(:clear).and_return(true)
+      end
+
+      it 'clears the result' do
+        expect(pg_result).to receive(:clear)
+        query_result.clear
+      end
+    end
+
+    context 'when pg_result does not respond to clear' do
+      before do
+        allow(pg_result).to receive(:respond_to?).with(:clear).and_return(false)
+      end
+
+      it 'does nothing' do
+        expect(pg_result).not_to receive(:clear)
+        query_result.clear
+      end
+    end
+  end
+end