You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Rack的PG网页客户端:非SQLite的连接持久化方案咨询

无需SQLite的PG连接持久化方案

针对你用Ruby+Rack构建网页PG客户端的需求,这里提供几个不用SQLite的高效方案,可根据服务器架构和并发场景选择:

方案1:单进程场景 - 内存线程安全存储+Rack Session

如果你的Rack服务器是单进程模式(比如Puma只开1个worker),直接用内存存储连接是最高效的方式,配合Rack Session绑定用户会话:

实现代码

require 'rack'
require 'pg'
require 'json'
require 'concurrent-ruby'

# 线程安全的全局连接存储,多线程环境下可安全读写
$connections = Concurrent::Hash.new

# 配置Rack Session,用Cookie存储会话ID(生产环境要替换为安全密钥)
use Rack::Session::Cookie, secret: 'your-strong-secret-key'

class ConnectionEndpoint
  def call(env)
    req = Rack::Request.new(env)
    return [405, {}, ['Method Not Allowed']] unless req.post?

    begin
      req_body = JSON.parse(req.body.read)
      connection_string = req_body["connection_string"].to_s
      # 建立并验证连接有效性
      conn = PG::Connection.new(connection_string)
      conn.exec('SELECT 1')

      # 将连接与当前会话绑定,存入全局存储
      session_id = req.session.id
      $connections[session_id] = conn

      [200, {'Content-Type' => 'application/json'}, ['{"status":"ok"}']]
    rescue PG::ConnectionBad, JSON::ParserError => e
      [400, {'Content-Type' => 'application/json'}, ["{\"error\":\"#{e.message}\"}"]]
    end
  end
end

class QueryEndpoint
  def call(env)
    req = Rack::Request.new(env)
    return [405, {}, ['Method Not Allowed']] unless req.post?

    session_id = req.session.id
    conn = $connections[session_id]

    unless conn
      return [401, {'Content-Type' => 'application/json'}, ['{"error":"No active connection found"}']]
    end

    begin
      req_body = JSON.parse(req.body.read)
      query = req_body["query"].to_s
      result = conn.exec(query)
      [200, {'Content-Type' => 'application/json'}, [result.to_a.to_json]]
    rescue PG::Error, JSON::ParserError => e
      [400, {'Content-Type' => 'application/json'}, ["{\"error\":\"#{e.message}\"}"]]
    end
  end
end

# 定时清理失效连接,避免内存泄漏(每小时检查一次)
Thread.new do
  loop do
    sleep 3600
    $connections.each do |session_id, conn|
      begin
        conn.exec('SELECT 1')
      rescue PG::Error
        conn.close
        $connections.delete(session_id)
      end
    end
  end
end

# 路由配置
map '/connection' do
  run ConnectionEndpoint.new
end

map '/query' do
  run QueryEndpoint.new
end

优缺点

  • ✅ 完全无需额外存储,内存操作性能拉满
  • ✅ 线程安全,适配多线程服务器(比如Puma的threads配置)
  • ❌ 不支持多进程模式(多个worker进程内存不共享,会话连接无法跨进程访问)

方案2:多进程/高并发场景 - Redis+连接池

如果你的服务器是多进程模式,或者需要高并发支持,用Redis存储会话对应的连接字符串,配合连接池复用连接,兼顾分布式和性能:

实现代码

require 'rack'
require 'pg'
require 'json'
require 'redis'
require 'connection_pool'

# 初始化Redis客户端(根据你的Redis配置调整)
$redis = Redis.new(host: 'localhost', port: 6379)

# 线程安全的连接池存储,每个连接字符串对应一个连接池
$connection_pools = Concurrent::Hash.new do |h, conn_str|
  h[conn_str] = ConnectionPool.new(size: 5, timeout: 5) do
    PG::Connection.new(conn_str)
  end
end

use Rack::Session::Cookie, secret: 'your-strong-secret-key'

class ConnectionEndpoint
  def call(env)
    req = Rack::Request.new(env)
    return [405, {}, ['Method Not Allowed']] unless req.post?

    begin
      req_body = JSON.parse(req.body.read)
      connection_string = req_body["connection_string"].to_s
      # 验证连接有效性
      PG::Connection.new(connection_string).exec('SELECT 1')

      # 将连接字符串存入Redis,绑定会话ID并设置1小时过期
      session_id = req.session.id
      $redis.setex("pg_conn:#{session_id}", 3600, connection_string)

      [200, {'Content-Type' => 'application/json'}, ['{"status":"ok"}']]
    rescue PG::ConnectionBad, JSON::ParserError => e
      [400, {'Content-Type' => 'application/json'}, ["{\"error\":\"#{e.message}\"}"]]
    end
  end
end

class QueryEndpoint
  def call(env)
    req = Rack::Request.new(env)
    return [405, {}, ['Method Not Allowed']] unless req.post?

    session_id = req.session.id
    connection_string = $redis.get("pg_conn:#{session_id}")

    unless connection_string
      return [401, {'Content-Type' => 'application/json'}, ['{"error":"No active connection found"}']]
    end

    begin
      req_body = JSON.parse(req.body.read)
      query = req_body["query"].to_s

      # 从连接池获取连接执行查询,自动回收连接
      $connection_pools[connection_string].with do |conn|
        result = conn.exec(query)
        [200, {'Content-Type' => 'application/json'}, [result.to_a.to_json]]
      end
    rescue PG::Error, JSON::ParserError, ConnectionPool::TimeoutError => e
      [400, {'Content-Type' => 'application/json'}, ["{\"error\":\"#{e.message}\"}"]]
    end
  end
end

map '/connection' do
  run ConnectionEndpoint.new
end

map '/query' do
  run QueryEndpoint.new
end

优缺点

  • ✅ 支持多进程/分布式场景,Redis跨进程共享连接信息
  • ✅ 连接池复用连接,减少重复建立连接的开销
  • ✅ 自动过期连接字符串,避免无效存储
  • ❌ 需要额外部署Redis服务(但性能比SQLite更高)

方案3:轻量低并发场景 - 直接存储连接字符串到Session

如果你的客户端用户量小、并发低,直接把连接字符串存在Rack Session里,每次查询时临时建立连接,实现最简单:

实现代码

require 'rack'
require 'pg'
require 'json'

use Rack::Session::Cookie, secret: 'your-strong-secret-key'

class ConnectionEndpoint
  def call(env)
    req = Rack::Request.new(env)
    return [405, {}, ['Method Not Allowed']] unless req.post?

    begin
      req_body = JSON.parse(req.body.read)
      connection_string = req_body["connection_string"].to_s
      # 验证连接
      PG::Connection.new(connection_string).exec('SELECT 1')

      # 直接把连接字符串存在当前会话中
      req.session[:pg_connection_string] = connection_string

      [200, {'Content-Type' => 'application/json'}, ['{"status":"ok"}']]
    rescue PG::ConnectionBad, JSON::ParserError => e
      [400, {'Content-Type' => 'application/json'}, ["{\"error\":\"#{e.message}\"}"]]
    end
  end
end

class QueryEndpoint
  def call(env)
    req = Rack::Request.new(env)
    return [405, {}, ['Method Not Allowed']] unless req.post?

    connection_string = req.session[:pg_connection_string]
    unless connection_string
      return [401, {'Content-Type' => 'application/json'}, ['{"error":"No active connection found"}']]
    end

    begin
      req_body = JSON.parse(req.body.read)
      query = req_body["query"].to_s

      # 每次查询建立新连接,用完自动关闭
      PG::Connection.new(connection_string) do |conn|
        result = conn.exec(query)
        [200, {'Content-Type' => 'application/json'}, [result.to_a.to_json]]
      end
    rescue PG::Error, JSON::ParserError => e
      [400, {'Content-Type' => 'application/json'}, ["{\"error\":\"#{e.message}\"}"]]
    end
  end
end

map '/connection' do
  run ConnectionEndpoint.new
end

map '/query' do
  run QueryEndpoint.new
end

优缺点

  • ✅ 零额外依赖,实现最简单
  • ❌ 每次查询都要建立新连接,性能开销大,不适合高并发
  • ❌ 连接字符串存在Cookie Session中(默认加密,但仍需注意安全风险)

内容的提问来源于stack exchange,提问作者Dedy Puji

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 11:54:52