1 //
2 // Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2021
3 //
4 // Distributed under the Boost Software License, Version 1.0. (See accompanying
5 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6 //
7 #include "td/db/binlog/Binlog.h"
8 #include "td/db/binlog/ConcurrentBinlog.h"
9 #include "td/db/BinlogKeyValue.h"
10 #include "td/db/DbKey.h"
11 #include "td/db/SeqKeyValue.h"
12 #include "td/db/SqliteConnectionSafe.h"
13 #include "td/db/SqliteDb.h"
14 #include "td/db/SqliteKeyValueAsync.h"
15 #include "td/db/SqliteKeyValueSafe.h"
16 
17 #include "td/actor/actor.h"
18 #include "td/actor/ConcurrentScheduler.h"
19 
20 #include "td/utils/benchmark.h"
21 #include "td/utils/common.h"
22 #include "td/utils/format.h"
23 #include "td/utils/logging.h"
24 #include "td/utils/SliceBuilder.h"
25 #include "td/utils/Status.h"
26 #include "td/utils/StringBuilder.h"
27 
28 #include <memory>
29 
30 template <class KeyValueT>
31 class TdKvBench final : public td::Benchmark {
32   td::ConcurrentScheduler sched;
33   td::string name_;
34 
35  public:
TdKvBench(td::string name)36   explicit TdKvBench(td::string name) {
37     name_ = std::move(name);
38   }
39 
get_description() const40   td::string get_description() const final {
41     return name_;
42   }
43 
44   class Main final : public td::Actor {
45    public:
Main(int n)46     explicit Main(int n) : n_(n) {
47     }
48 
49    private:
loop()50     void loop() final {
51       KeyValueT::destroy("test_tddb").ignore();
52 
53       class Worker final : public Actor {
54        public:
55         Worker(int n, td::string db_name) : n_(n) {
56           kv_.init(db_name).ensure();
57         }
58 
59        private:
60         void loop() final {
61           for (int i = 0; i < n_; i++) {
62             kv_.set(td::to_string(i % 10), td::to_string(i));
63           }
64           td::Scheduler::instance()->finish();
65         }
66         int n_;
67         KeyValueT kv_;
68       };
69       td::create_actor_on_scheduler<Worker>("Worker", 0, n_, "test_tddb").release();
70     }
71     int n_;
72   };
73 
start_up_n(int n)74   void start_up_n(int n) final {
75     sched.init(1);
76     sched.create_actor_unsafe<Main>(1, "Main", n).release();
77   }
78 
run(int n)79   void run(int n) final {
80     sched.start();
81     while (sched.run_main(10)) {
82       // empty
83     }
84     sched.finish();
85   }
86 
tear_down()87   void tear_down() final {
88   }
89 };
90 
91 template <bool is_encrypted = false>
92 class SqliteKVBench final : public td::Benchmark {
93   td::SqliteDb db;
get_description() const94   td::string get_description() const final {
95     return PSTRING() << "SqliteKV " << td::tag("is_encrypted", is_encrypted);
96   }
start_up()97   void start_up() final {
98     td::string path = "testdb.sqlite";
99     td::SqliteDb::destroy(path).ignore();
100     if (is_encrypted) {
101       db = td::SqliteDb::change_key(path, true, td::DbKey::password("cucumber"), td::DbKey::empty()).move_as_ok();
102     } else {
103       db = td::SqliteDb::open_with_key(path, true, td::DbKey::empty()).move_as_ok();
104     }
105     db.exec("PRAGMA encoding=\"UTF-8\"").ensure();
106     db.exec("PRAGMA synchronous=NORMAL").ensure();
107     db.exec("PRAGMA journal_mode=WAL").ensure();
108     db.exec("PRAGMA temp_store=MEMORY").ensure();
109     db.exec("DROP TABLE IF EXISTS KV").ensure();
110     db.exec("CREATE TABLE IF NOT EXISTS KV (k BLOB PRIMARY KEY, v BLOB)").ensure();
111   }
run(int n)112   void run(int n) final {
113     auto stmt = db.get_statement("REPLACE INTO KV (k, v) VALUES(?1, ?2)").move_as_ok();
114     db.exec("BEGIN TRANSACTION").ensure();
115     for (int i = 0; i < n; i++) {
116       auto key = td::to_string(i % 10);
117       auto value = td::to_string(i);
118       stmt.bind_blob(1, key).ensure();
119       stmt.bind_blob(2, value).ensure();
120       stmt.step().ensure();
121       CHECK(!stmt.can_step());
122       stmt.reset();
123 
124       if (i % 10 == 0) {
125         db.exec("COMMIT TRANSACTION").ensure();
126         db.exec("BEGIN TRANSACTION").ensure();
127       }
128     }
129     db.exec("COMMIT TRANSACTION").ensure();
130   }
131 };
132 
init_db(td::SqliteDb & db)133 static td::Status init_db(td::SqliteDb &db) {
134   TRY_STATUS(db.exec("PRAGMA encoding=\"UTF-8\""));
135   TRY_STATUS(db.exec("PRAGMA journal_mode=WAL"));
136 
137   TRY_STATUS(db.exec("PRAGMA synchronous=NORMAL"));
138   TRY_STATUS(db.exec("PRAGMA temp_store=MEMORY"));
139   // TRY_STATUS(db.exec("PRAGMA secure_delete=1"));
140 
141   return td::Status::OK();
142 }
143 
144 class SqliteKeyValueAsyncBench final : public td::Benchmark {
145  public:
get_description() const146   td::string get_description() const final {
147     return "SqliteKeyValueAsync";
148   }
start_up()149   void start_up() final {
150     do_start_up().ensure();
151     scheduler_->start();
152   }
run(int n)153   void run(int n) final {
154     auto guard = scheduler_->get_main_guard();
155 
156     for (int i = 0; i < n; i++) {
157       auto key = td::to_string(i % 10);
158       auto value = td::to_string(i);
159       sqlite_kv_async_->set(key, value, td::Auto());
160     }
161   }
tear_down()162   void tear_down() final {
163     scheduler_->run_main(0.1);
164     {
165       auto guard = scheduler_->get_main_guard();
166       sqlite_kv_async_.reset();
167       sqlite_kv_safe_.reset();
168       sql_connection_->close_and_destroy();
169     }
170 
171     scheduler_->finish();
172     scheduler_.reset();
173   }
174 
175  private:
176   td::unique_ptr<td::ConcurrentScheduler> scheduler_;
177   std::shared_ptr<td::SqliteConnectionSafe> sql_connection_;
178   std::shared_ptr<td::SqliteKeyValueSafe> sqlite_kv_safe_;
179   td::unique_ptr<td::SqliteKeyValueAsyncInterface> sqlite_kv_async_;
180 
do_start_up()181   td::Status do_start_up() {
182     scheduler_ = td::make_unique<td::ConcurrentScheduler>();
183     scheduler_->init(1);
184 
185     auto guard = scheduler_->get_main_guard();
186 
187     td::string sql_db_name = "testdb.sqlite";
188     td::SqliteDb::destroy(sql_db_name).ignore();
189 
190     sql_connection_ = std::make_shared<td::SqliteConnectionSafe>(sql_db_name, td::DbKey::empty());
191     auto &db = sql_connection_->get();
192     TRY_STATUS(init_db(db));
193 
194     sqlite_kv_safe_ = std::make_shared<td::SqliteKeyValueSafe>("common", sql_connection_);
195     sqlite_kv_async_ = create_sqlite_key_value_async(sqlite_kv_safe_, 0);
196 
197     return td::Status::OK();
198   }
199 };
200 
201 class SeqKvBench final : public td::Benchmark {
get_description() const202   td::string get_description() const final {
203     return "SeqKvBench";
204   }
205 
206   td::SeqKeyValue kv;
run(int n)207   void run(int n) final {
208     for (int i = 0; i < n; i++) {
209       kv.set(PSLICE() << i % 10, PSLICE() << i);
210     }
211   }
212 };
213 
214 template <bool is_encrypted = false>
215 class BinlogKeyValueBench final : public td::Benchmark {
get_description() const216   td::string get_description() const final {
217     return PSTRING() << "BinlogKeyValue " << td::tag("is_encrypted", is_encrypted);
218   }
219 
220   td::BinlogKeyValue<td::Binlog> kv;
start_up()221   void start_up() final {
222     td::SqliteDb::destroy("test_binlog").ignore();
223     kv.init("test_binlog", is_encrypted ? td::DbKey::password("cucumber") : td::DbKey::empty()).ensure();
224   }
run(int n)225   void run(int n) final {
226     for (int i = 0; i < n; i++) {
227       kv.set(td::to_string(i % 10), td::to_string(i));
228     }
229   }
230 };
231 
main()232 int main() {
233   SET_VERBOSITY_LEVEL(VERBOSITY_NAME(WARNING));
234   bench(BinlogKeyValueBench<true>());
235   bench(BinlogKeyValueBench<false>());
236   bench(SqliteKVBench<false>());
237   bench(SqliteKVBench<true>());
238   bench(SqliteKeyValueAsyncBench());
239   bench(TdKvBench<td::BinlogKeyValue<td::Binlog>>("BinlogKeyValue<Binlog>"));
240   bench(TdKvBench<td::BinlogKeyValue<td::ConcurrentBinlog>>("BinlogKeyValue<ConcurrentBinlog>"));
241   bench(SeqKvBench());
242 }
243