Skip to content

Commit a7e8a2d

Browse files
authored
Merge pull request #210 from MDA2AV/reso808
udpate results
2 parents b1258a6 + 4062970 commit a7e8a2d

119 files changed

Lines changed: 3739 additions & 1962 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

frameworks/drogon/Dockerfile

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
FROM ubuntu:24.04 AS build
22

33
RUN apt-get update && apt-get install -y --no-install-recommends \
4-
g++ cmake make git ca-certificates \
5-
libjsoncpp-dev uuid-dev zlib1g-dev libssl-dev libsqlite3-dev \
4+
g++ cmake make git ca-certificates pkg-config \
5+
libjsoncpp-dev uuid-dev zlib1g-dev libssl-dev libsqlite3-dev libpq-dev \
66
&& rm -rf /var/lib/apt/lists/*
77

88
# Build drogon from source
@@ -12,7 +12,7 @@ RUN git clone --depth 1 --branch v1.9.10 https://github.com/drogonframework/drog
1212
cmake -B build -DCMAKE_BUILD_TYPE=Release \
1313
-DCMAKE_CXX_FLAGS="-flto" \
1414
-DBUILD_EXAMPLES=OFF -DBUILD_CTL=OFF \
15-
-DBUILD_ORM=OFF -DBUILD_BROTLI=OFF \
15+
-DBUILD_ORM=ON -DBUILD_BROTLI=OFF \
1616
-S . && \
1717
cmake --build build -j$(nproc) && \
1818
cmake --install build
@@ -25,7 +25,7 @@ RUN cmake -B build -DCMAKE_BUILD_TYPE=Release -S . && \
2525

2626
FROM ubuntu:24.04
2727
RUN apt-get update && apt-get install -y --no-install-recommends \
28-
libjsoncpp25 libssl3t64 libuuid1 libsqlite3-0 && \
28+
libjsoncpp25 libssl3t64 libuuid1 libsqlite3-0 libpq5 && \
2929
rm -rf /var/lib/apt/lists/*
3030
COPY --from=build /usr/local/lib/libtrantor.so* /usr/local/lib/
3131
COPY --from=build /usr/local/lib/libdrogon.so* /usr/local/lib/

frameworks/drogon/main.cc

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include <drogon/drogon.h>
22
#include <sqlite3.h>
3+
#include <thread>
34
#include <dirent.h>
45
#include <fstream>
56
#include <sstream>
@@ -60,6 +61,11 @@ static sqlite3 *getDb()
6061
return tl_db;
6162
}
6263

64+
// ── PostgreSQL (async-db) ──
65+
66+
static bool pg_available = false;
67+
static drogon::orm::DbClientPtr pgClient;
68+
6369
static void loadDataset()
6470
{
6571
const char *path = getenv("DATASET_PATH");
@@ -171,6 +177,7 @@ class BenchmarkCtrl : public drogon::HttpController<BenchmarkCtrl>
171177
ADD_METHOD_TO(BenchmarkCtrl::upload, "/upload", Post);
172178
ADD_METHOD_TO(BenchmarkCtrl::baseline11, "/baseline11", Get, Post);
173179
ADD_METHOD_TO(BenchmarkCtrl::dbEndpoint, "/db", Get);
180+
ADD_METHOD_TO(BenchmarkCtrl::asyncDb, "/async-db", Get);
174181
ADD_METHOD_TO(BenchmarkCtrl::staticFile, "/static/{1}", Get);
175182
METHOD_LIST_END
176183

@@ -325,6 +332,66 @@ class BenchmarkCtrl : public drogon::HttpController<BenchmarkCtrl>
325332
callback(resp);
326333
}
327334

335+
void asyncDb(const HttpRequestPtr &req,
336+
std::function<void(const HttpResponsePtr &)> &&callback)
337+
{
338+
static const std::string empty_resp = "{\"items\":[],\"count\":0}";
339+
if (!pg_available || !pgClient) {
340+
auto resp = HttpResponse::newHttpResponse();
341+
resp->setBody(empty_resp);
342+
resp->setContentTypeCode(CT_APPLICATION_JSON);
343+
callback(resp);
344+
return;
345+
}
346+
double minPrice = 10.0, maxPrice = 50.0;
347+
for (auto &[k, v] : req->parameters()) {
348+
if (k == "min") { try { minPrice = std::stod(v); } catch (...) {} }
349+
else if (k == "max") { try { maxPrice = std::stod(v); } catch (...) {} }
350+
}
351+
pgClient->execSqlAsync(
352+
"SELECT id, name, category, price, quantity, active, tags, rating_score, rating_count FROM items WHERE price BETWEEN $1 AND $2 LIMIT 50",
353+
[callback](const drogon::orm::Result &result) {
354+
Json::Value respJson;
355+
Json::Value items(Json::arrayValue);
356+
for (const auto &row : result) {
357+
Json::Value item;
358+
item["id"] = static_cast<Json::Int64>(row["id"].as<int32_t>());
359+
item["name"] = row["name"].as<std::string>();
360+
item["category"] = row["category"].as<std::string>();
361+
item["price"] = row["price"].as<double>();
362+
item["quantity"] = static_cast<Json::Int64>(row["quantity"].as<int32_t>());
363+
item["active"] = row["active"].as<bool>();
364+
Json::CharReaderBuilder rb;
365+
Json::Value tags;
366+
std::string errs;
367+
std::string tagsStr = row["tags"].as<std::string>();
368+
std::istringstream tis(tagsStr);
369+
Json::parseFromStream(rb, tis, &tags, &errs);
370+
item["tags"] = tags;
371+
Json::Value rating;
372+
rating["score"] = row["rating_score"].as<double>();
373+
rating["count"] = static_cast<Json::Int64>(row["rating_count"].as<int32_t>());
374+
item["rating"] = rating;
375+
items.append(std::move(item));
376+
}
377+
respJson["items"] = std::move(items);
378+
respJson["count"] = static_cast<int>(result.size());
379+
Json::StreamWriterBuilder wb;
380+
wb["indentation"] = "";
381+
auto resp = HttpResponse::newHttpResponse();
382+
resp->setBody(Json::writeString(wb, respJson));
383+
resp->setContentTypeCode(CT_APPLICATION_JSON);
384+
callback(resp);
385+
},
386+
[callback](const drogon::orm::DrogonDbException &e) {
387+
auto resp = HttpResponse::newHttpResponse();
388+
resp->setBody("{\"items\":[],\"count\":0}");
389+
resp->setContentTypeCode(CT_APPLICATION_JSON);
390+
callback(resp);
391+
},
392+
minPrice, maxPrice);
393+
}
394+
328395
void staticFile(const HttpRequestPtr &req,
329396
std::function<void(const HttpResponsePtr &)> &&callback,
330397
const std::string &filename)
@@ -354,6 +421,37 @@ int main()
354421
sqlite3 *test = openDb();
355422
if (test) { db_available = true; sqlite3_close(test); }
356423
}
424+
{
425+
const char *dbUrl = getenv("DATABASE_URL");
426+
if (dbUrl) {
427+
// Parse postgres://user:pass@host:port/dbname
428+
std::string s(dbUrl);
429+
auto se = s.find("://");
430+
if (se != std::string::npos) {
431+
s = s.substr(se + 3);
432+
std::string user, pass, host = "localhost", port = "5432", dbname;
433+
auto at = s.find('@');
434+
if (at != std::string::npos) {
435+
auto up = s.substr(0, at);
436+
s = s.substr(at + 1);
437+
auto c = up.find(':');
438+
if (c != std::string::npos) { user = up.substr(0, c); pass = up.substr(c + 1); }
439+
else user = up;
440+
}
441+
auto sl = s.find('/');
442+
if (sl != std::string::npos) { dbname = s.substr(sl + 1); s = s.substr(0, sl); }
443+
auto c = s.find(':');
444+
if (c != std::string::npos) { host = s.substr(0, c); port = s.substr(c + 1); }
445+
else host = s;
446+
int nconns = std::thread::hardware_concurrency();
447+
if (nconns < 4) nconns = 4;
448+
pgClient = drogon::orm::DbClient::newPgClient(
449+
"host=" + host + " port=" + port + " dbname=" + dbname +
450+
" user=" + user + " password=" + pass, nconns);
451+
pg_available = true;
452+
}
453+
}
454+
}
357455

358456
app().setLogLevel(trantor::Logger::kWarn);
359457
app().setThreadNum(0);
@@ -372,6 +470,7 @@ int main()
372470
app().addListener("0.0.0.0", 8443, true, cert, key);
373471

374472
app().enableGzip(true);
473+
375474
app().run();
376475
return 0;
377476
}

frameworks/drogon/meta.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"compression",
1717
"mixed",
1818
"baseline-h2",
19-
"static-h2"
19+
"static-h2",
20+
"async-db"
2021
]
2122
}

frameworks/kemal/Dockerfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ FROM crystallang/crystal:1.14-alpine AS builder
44
WORKDIR /app
55

66
# Install SQLite dev headers, libpq, and gcc for reuseport shim
7-
RUN apk add --no-cache sqlite-dev sqlite-static gcc musl-dev
7+
RUN apk add --no-cache sqlite-dev sqlite-static gcc musl-dev libpq-dev git
88

99
# Build SO_REUSEPORT shim
1010
COPY reuseport.c /tmp/reuseport.c
@@ -21,7 +21,7 @@ RUN crystal build src/server.cr -o server --release --no-debug
2121
# Runtime stage
2222
FROM alpine:3.20
2323

24-
RUN apk add --no-cache ca-certificates libgcc gc libevent sqlite-libs pcre2
24+
RUN apk add --no-cache ca-certificates libgcc gc libevent sqlite-libs pcre2 libpq
2525

2626
WORKDIR /app
2727
COPY --from=builder /app/server /app/server

frameworks/kemal/meta.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"upload",
1515
"compression",
1616
"noisy",
17-
"mixed"
17+
"mixed",
18+
"async-db"
1819
]
1920
}

frameworks/kemal/shard.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,8 @@ dependencies:
1212
sqlite3:
1313
github: crystal-lang/crystal-sqlite3
1414
version: ~> 0.21
15+
pg:
16+
github: will/crystal-pg
17+
version: ~> 0.28
1518

1619
crystal: ">= 1.10.0"

frameworks/kemal/src/server.cr

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ require "kemal"
22
require "json"
33
require "compress/gzip"
44
require "sqlite3"
5+
require "db"
6+
require "pg"
57

68

79
# ---------------------------------------------------------------------------
@@ -50,9 +52,26 @@ end
5052

5153
DB_AVAILABLE = File.exists?(DB_PATH)
5254

53-
5455
PG_QUERY = "SELECT id, name, category, price, quantity, active, tags, rating_score, rating_count FROM items WHERE price BETWEEN $1 AND $2 LIMIT 50"
5556

57+
PG_POOL_SIZE = {System.cpu_count.to_i * 4, 64}.max
58+
59+
class PgPool
60+
@@db : DB::Database? = nil
61+
62+
def self.get : DB::Database?
63+
return @@db if @@db
64+
if url = ENV["DATABASE_URL"]?
65+
separator = url.includes?('?') ? '&' : '?'
66+
pg_url = "#{url}#{separator}max_pool_size=#{PG_POOL_SIZE}&max_idle_pool_size=#{PG_POOL_SIZE}"
67+
@@db = DB.open(pg_url)
68+
end
69+
@@db
70+
rescue
71+
nil
72+
end
73+
end
74+
5675
# ---------------------------------------------------------------------------
5776
# Helpers
5877
# ---------------------------------------------------------------------------
@@ -180,6 +199,43 @@ get "/db" do |env|
180199
{items: items, count: items.size}.to_json
181200
end
182201

202+
get "/async-db" do |env|
203+
server_header(env)
204+
env.response.content_type = "application/json"
205+
206+
unless pg = PgPool.get
207+
next %({"items":[],"count":0})
208+
end
209+
210+
min_val = (env.params.query["min"]?.try(&.to_f) || 10.0)
211+
max_val = (env.params.query["max"]?.try(&.to_f) || 50.0)
212+
213+
begin
214+
items = [] of Hash(String, JSON::Any)
215+
216+
pg.query(PG_QUERY, min_val, max_val) do |rs|
217+
rs.each do
218+
item = Hash(String, JSON::Any).new
219+
item["id"] = JSON::Any.new(rs.read(Int32).to_i64)
220+
item["name"] = JSON::Any.new(rs.read(String))
221+
item["category"] = JSON::Any.new(rs.read(String))
222+
item["price"] = JSON::Any.new(rs.read(Float64))
223+
item["quantity"] = JSON::Any.new(rs.read(Int32).to_i64)
224+
item["active"] = JSON::Any.new(rs.read(Bool))
225+
item["tags"] = rs.read(JSON::Any)
226+
rating_score = rs.read(Float64)
227+
rating_count = rs.read(Int32).to_i64
228+
item["rating"] = JSON::Any.new({"score" => JSON::Any.new(rating_score), "count" => JSON::Any.new(rating_count)})
229+
items << item
230+
end
231+
end
232+
233+
{items: items, count: items.size}.to_json
234+
rescue
235+
%({"items":[],"count":0})
236+
end
237+
end
238+
183239
post "/upload" do |env|
184240
server_header(env)
185241
body = env.request.body.try(&.gets_to_end) || ""
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ serde = { version = "1", features = ["derive"] }
1212
serde_json = "1"
1313
num_cpus = "1"
1414
rusqlite = { version = "0.31", features = ["bundled"] }
15+
tokio-postgres = { version = "0.7", features = ["with-serde_json-1"] }
16+
deadpool-postgres = { version = "0.14", features = ["rt_tokio_1"] }
1517

1618
[profile.release]
1719
opt-level = 3
File renamed without changes.

0 commit comments

Comments
 (0)