diff options
-rw-r--r-- | store/database.cpp | 239 | ||||
-rw-r--r-- | store/database.h | 35 | ||||
-rw-r--r-- | store/test/storagebenchmark.cpp | 32 |
3 files changed, 186 insertions, 120 deletions
diff --git a/store/database.cpp b/store/database.cpp index 2d93266..22d0693 100644 --- a/store/database.cpp +++ b/store/database.cpp | |||
@@ -1,134 +1,188 @@ | |||
1 | #include "database.h" | 1 | #include "database.h" |
2 | 2 | ||
3 | #include <iostream> | 3 | #include <iostream> |
4 | |||
5 | #include <QAtomicInt> | ||
6 | #include <QDebug> | ||
4 | #include <QDir> | 7 | #include <QDir> |
8 | #include <QReadWriteLock> | ||
5 | #include <QString> | 9 | #include <QString> |
6 | #include <QTime> | 10 | #include <QTime> |
7 | #include <qdebug.h> | ||
8 | 11 | ||
9 | Database::Database(const QString &path) | 12 | #include <lmdb.h> |
13 | |||
14 | class Database::Private | ||
10 | { | 15 | { |
11 | int rc; | 16 | public: |
17 | Private(const QString &path); | ||
18 | ~Private(); | ||
12 | 19 | ||
20 | MDB_dbi dbi; | ||
21 | MDB_env *env; | ||
22 | MDB_txn *transaction; | ||
23 | bool readTransaction; | ||
24 | }; | ||
25 | |||
26 | Database::Private::Private(const QString &path) | ||
27 | : transaction(0), | ||
28 | readTransaction(false) | ||
29 | { | ||
13 | QDir dir; | 30 | QDir dir; |
14 | dir.mkdir(path); | 31 | dir.mkdir(path); |
15 | 32 | ||
16 | //create file | 33 | //create file |
17 | rc = mdb_env_create(&env); | 34 | if (mdb_env_create(&env)) { |
18 | rc = mdb_env_open(env, path.toStdString().data(), 0, 0664); | 35 | // TODO: handle error |
19 | const int dbSize = 10485760*100; //10MB * 100 | 36 | } else { |
20 | mdb_env_set_mapsize(env, dbSize); | 37 | int rc = mdb_env_open(env, path.toStdString().data(), 0, 0664); |
21 | 38 | ||
22 | if (rc) { | 39 | if (rc) { |
23 | std::cerr << "mdb_env_open: " << rc << mdb_strerror(rc) << std::endl; | 40 | std::cerr << "mdb_env_open: " << rc << mdb_strerror(rc) << std::endl; |
41 | mdb_env_close(env); | ||
42 | env = 0; | ||
43 | } else { | ||
44 | const int dbSize = 10485760*100; //10MB * 100 | ||
45 | mdb_env_set_mapsize(env, dbSize); | ||
46 | } | ||
24 | } | 47 | } |
25 | } | 48 | } |
26 | 49 | ||
27 | Database::~Database() | 50 | Database::Private::~Private() |
28 | { | 51 | { |
29 | mdb_close(env, dbi); | 52 | if (transaction) { |
53 | mdb_txn_abort(transaction); | ||
54 | } | ||
55 | |||
56 | // it is still there and still unused, so we can shut it down | ||
57 | mdb_dbi_close(env, dbi); | ||
30 | mdb_env_close(env); | 58 | mdb_env_close(env); |
31 | } | 59 | } |
32 | 60 | ||
33 | MDB_txn *Database::startTransaction() | 61 | Database::Database(const QString &path) |
62 | : d(new Private(path)) | ||
63 | { | ||
64 | } | ||
65 | |||
66 | Database::~Database() | ||
67 | { | ||
68 | delete d; | ||
69 | } | ||
70 | |||
71 | bool Database::isInTransaction() const | ||
72 | { | ||
73 | return d->transaction; | ||
74 | } | ||
75 | |||
76 | bool Database::startTransaction(TransactionType type) | ||
34 | { | 77 | { |
78 | if (!d->env) { | ||
79 | return false; | ||
80 | } | ||
81 | |||
82 | bool requestedRead = type == ReadOnly; | ||
83 | if (d->transaction && (!d->readTransaction || requestedRead)) { | ||
84 | return true; | ||
85 | } | ||
86 | |||
87 | if (d->transaction) { | ||
88 | // we are about to turn a read transaction into a writable one | ||
89 | abortTransaction(); | ||
90 | } | ||
91 | |||
92 | //TODO handle errors | ||
35 | int rc; | 93 | int rc; |
36 | MDB_txn *transaction; | 94 | rc = mdb_txn_begin(d->env, NULL, requestedRead ? MDB_RDONLY : 0, &d->transaction); |
37 | rc = mdb_txn_begin(env, NULL, 0, &transaction); | 95 | if (!rc) { |
38 | rc = mdb_open(transaction, NULL, 0, &dbi); | 96 | rc = mdb_dbi_open(d->transaction, NULL, 0, &d->dbi); |
39 | return transaction; | 97 | } |
98 | |||
99 | return !rc; | ||
40 | } | 100 | } |
41 | 101 | ||
42 | void Database::endTransaction(MDB_txn *transaction) | 102 | bool Database::commitTransaction() |
43 | { | 103 | { |
104 | if (!d->env) { | ||
105 | return false; | ||
106 | } | ||
107 | |||
108 | if (!d->transaction) { | ||
109 | return false; | ||
110 | } | ||
111 | |||
44 | int rc; | 112 | int rc; |
45 | rc = mdb_txn_commit(transaction); | 113 | rc = mdb_txn_commit(d->transaction); |
114 | d->transaction = 0; | ||
115 | |||
46 | if (rc) { | 116 | if (rc) { |
47 | std::cerr << "mdb_txn_commit: " << rc << mdb_strerror(rc) << std::endl; | 117 | std::cerr << "mdb_txn_commit: " << rc << mdb_strerror(rc) << std::endl; |
48 | } | 118 | } |
119 | |||
120 | return !rc; | ||
49 | } | 121 | } |
50 | 122 | ||
123 | void Database::abortTransaction() | ||
124 | { | ||
125 | if (!d->env || !d->transaction) { | ||
126 | return; | ||
127 | } | ||
128 | |||
129 | mdb_txn_abort(d->transaction); | ||
130 | d->transaction = 0; | ||
131 | } | ||
51 | 132 | ||
52 | void Database::write(const std::string &sKey, const std::string &sValue, MDB_txn *transaction) | 133 | bool Database::write(const std::string &sKey, const std::string &sValue) |
53 | { | 134 | { |
135 | if (!d->env) { | ||
136 | return false; | ||
137 | } | ||
138 | |||
139 | const bool implicitTransaction = !d->transaction || d->readTransaction; | ||
140 | if (implicitTransaction) { | ||
141 | // TODO: if this fails, still try the write below? | ||
142 | if (!startTransaction()) { | ||
143 | return false; | ||
144 | } | ||
145 | } | ||
146 | |||
54 | int rc; | 147 | int rc; |
55 | MDB_val key, data; | 148 | MDB_val key, data; |
56 | key.mv_size = sKey.size(); | 149 | key.mv_size = sKey.size(); |
57 | key.mv_data = (void*)sKey.data(); | 150 | key.mv_data = (void*)sKey.data(); |
58 | data.mv_size = sValue.size(); | 151 | data.mv_size = sValue.size(); |
59 | data.mv_data = (void*)sValue.data(); | 152 | data.mv_data = (void*)sValue.data(); |
60 | rc = mdb_put(transaction, dbi, &key, &data, 0); | 153 | rc = mdb_put(d->transaction, d->dbi, &key, &data, 0); |
154 | |||
61 | if (rc) { | 155 | if (rc) { |
62 | std::cerr << "mdb_put: " << rc << mdb_strerror(rc) << std::endl; | 156 | std::cerr << "mdb_put: " << rc << mdb_strerror(rc) << std::endl; |
63 | } | 157 | } |
64 | } | ||
65 | 158 | ||
66 | void Database::read(const std::string &sKey, const std::function<void(const std::string)> &resultHandler) | 159 | if (implicitTransaction) { |
67 | { | 160 | if (rc) { |
68 | int rc; | 161 | abortTransaction(); |
69 | MDB_txn *txn; | ||
70 | MDB_val key; | ||
71 | MDB_val data; | ||
72 | MDB_cursor *cursor; | ||
73 | |||
74 | key.mv_size = sKey.size(); | ||
75 | key.mv_data = (void*)sKey.data(); | ||
76 | |||
77 | rc = mdb_txn_begin(env, NULL, MDB_RDONLY, &txn); | ||
78 | rc = mdb_cursor_open(txn, dbi, &cursor); | ||
79 | if (sKey.empty()) { | ||
80 | std::cout << "Iterating over all values of store!" << std::endl; | ||
81 | while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) { | ||
82 | const std::string resultKey(static_cast<char*>(key.mv_data), key.mv_size); | ||
83 | const std::string resultValue(static_cast<char*>(data.mv_data), data.mv_size); | ||
84 | // std::cout << "key: " << resultKey << " data: " << resultValue << std::endl; | ||
85 | resultHandler(resultValue); | ||
86 | } | ||
87 | } else { | ||
88 | if ((rc = mdb_cursor_get(cursor, &key, &data, MDB_SET)) == 0) { | ||
89 | const std::string resultKey(static_cast<char*>(key.mv_data), key.mv_size); | ||
90 | const std::string resultValue(static_cast<char*>(data.mv_data), data.mv_size); | ||
91 | // std::cout << "key: " << resultKey << " data: " << resultValue << std::endl; | ||
92 | resultHandler(resultValue); | ||
93 | } else { | 162 | } else { |
94 | std::cout << "couldn't find value " << sKey << std::endl; | 163 | rc = commitTransaction(); |
95 | } | 164 | } |
96 | } | 165 | } |
97 | if (rc) { | ||
98 | std::cerr << "mdb_cursor_get: " << rc << mdb_strerror(rc) << std::endl; | ||
99 | } | ||
100 | mdb_cursor_close(cursor); | ||
101 | mdb_txn_abort(txn); | ||
102 | } | ||
103 | 166 | ||
104 | 167 | return !rc; | |
105 | |||
106 | ReadTransaction::ReadTransaction(const QString &path) | ||
107 | { | ||
108 | int rc; | ||
109 | |||
110 | //create file | ||
111 | rc = mdb_env_create(&env); | ||
112 | //FIXME MDB_NOTLS required to so we can have multiple read-only transactions per resource? | ||
113 | rc = mdb_env_open(env, path.toStdString().data(), 0, 0664); | ||
114 | const int dbSize = 10485760*100; //10MB * 100 | ||
115 | mdb_env_set_mapsize(env, dbSize); | ||
116 | |||
117 | if (rc) { | ||
118 | std::cerr << "mdb_env_open: " << rc << mdb_strerror(rc) << std::endl; | ||
119 | } | ||
120 | } | 168 | } |
121 | 169 | ||
122 | ReadTransaction::~ReadTransaction() | 170 | void Database::read(const std::string &sKey, const std::function<void(const std::string &value)> &resultHandler) |
123 | { | 171 | { |
124 | mdb_txn_abort(txn); | 172 | read(sKey, |
125 | 173 | [&](void *ptr, int size) { | |
126 | mdb_dbi_close(env, dbi); | 174 | const std::string resultValue(static_cast<char*>(ptr), size); |
127 | mdb_env_close(env); | 175 | resultHandler(resultValue); |
176 | }); | ||
177 | // std::cout << "key: " << resultKey << " data: " << resultValue << std::endl; | ||
128 | } | 178 | } |
129 | 179 | ||
130 | void ReadTransaction::read(const std::string &sKey, const std::function<void(void *data, int size)> &resultHandler) | 180 | void Database::read(const std::string &sKey, const std::function<void(void *ptr, int size)> &resultHandler) |
131 | { | 181 | { |
182 | if (!d->env) { | ||
183 | return; | ||
184 | } | ||
185 | |||
132 | int rc; | 186 | int rc; |
133 | MDB_val key; | 187 | MDB_val key; |
134 | MDB_val data; | 188 | MDB_val data; |
@@ -137,6 +191,8 @@ void ReadTransaction::read(const std::string &sKey, const std::function<void(voi | |||
137 | key.mv_size = sKey.size(); | 191 | key.mv_size = sKey.size(); |
138 | key.mv_data = (void*)sKey.data(); | 192 | key.mv_data = (void*)sKey.data(); |
139 | 193 | ||
194 | /* | ||
195 | TODO: do we need a write transaction before a read transaction? only relevant for implicitTransactions in any case | ||
140 | { | 196 | { |
141 | //A write transaction is at least required the first time | 197 | //A write transaction is at least required the first time |
142 | rc = mdb_txn_begin(env, nullptr, 0, &txn); | 198 | rc = mdb_txn_begin(env, nullptr, 0, &txn); |
@@ -145,18 +201,27 @@ void ReadTransaction::read(const std::string &sKey, const std::function<void(voi | |||
145 | rc = mdb_dbi_open(txn, nullptr, 0, &dbi); | 201 | rc = mdb_dbi_open(txn, nullptr, 0, &dbi); |
146 | mdb_txn_abort(txn); | 202 | mdb_txn_abort(txn); |
147 | } | 203 | } |
204 | */ | ||
205 | const bool implicitTransaction = !d->transaction; | ||
206 | if (implicitTransaction) { | ||
207 | // TODO: if this fails, still try the write below? | ||
208 | if (!startTransaction(ReadOnly)) { | ||
209 | return; | ||
210 | } | ||
211 | } | ||
148 | 212 | ||
149 | rc = mdb_txn_begin(env, nullptr, MDB_RDONLY, &txn); | 213 | rc = mdb_cursor_open(d->transaction, d->dbi, &cursor); |
150 | rc = mdb_cursor_open(txn, dbi, &cursor); | ||
151 | if (rc) { | 214 | if (rc) { |
152 | std::cerr << "mdb_cursor_open: " << rc << mdb_strerror(rc) << std::endl; | 215 | std::cerr << "mdb_cursor_get: " << rc << mdb_strerror(rc) << std::endl; |
216 | return; | ||
153 | } | 217 | } |
218 | |||
154 | if (sKey.empty()) { | 219 | if (sKey.empty()) { |
155 | std::cout << "Iterating over all values of store!" << std::endl; | 220 | std::cout << "Iterating over all values of store!" << std::endl; |
156 | rc = mdb_cursor_get(cursor, &key, &data, MDB_FIRST); | ||
157 | while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) { | 221 | while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) { |
158 | resultHandler(data.mv_data, data.mv_size); | 222 | resultHandler(key.mv_data, data.mv_size); |
159 | } | 223 | } |
224 | |||
160 | //We never find the last value | 225 | //We never find the last value |
161 | if (rc == MDB_NOTFOUND) { | 226 | if (rc == MDB_NOTFOUND) { |
162 | rc = 0; | 227 | rc = 0; |
@@ -168,10 +233,18 @@ void ReadTransaction::read(const std::string &sKey, const std::function<void(voi | |||
168 | std::cout << "couldn't find value " << sKey << std::endl; | 233 | std::cout << "couldn't find value " << sKey << std::endl; |
169 | } | 234 | } |
170 | } | 235 | } |
236 | |||
171 | if (rc) { | 237 | if (rc) { |
172 | std::cerr << "mdb_cursor_get: " << rc << mdb_strerror(rc) << std::endl; | 238 | std::cerr << "mdb_cursor_get: " << rc << mdb_strerror(rc) << std::endl; |
173 | } | 239 | } |
240 | |||
174 | mdb_cursor_close(cursor); | 241 | mdb_cursor_close(cursor); |
175 | //We keep the transaction open since we want to keep the returned values alive | 242 | |
243 | /** | ||
244 | we don't abort the transaction since we need it for reading the values | ||
245 | if (implicitTransaction) { | ||
246 | abortTransaction(); | ||
247 | } | ||
248 | */ | ||
176 | } | 249 | } |
177 | 250 | ||
diff --git a/store/database.h b/store/database.h index ab398a4..2bf0556 100644 --- a/store/database.h +++ b/store/database.h | |||
@@ -1,38 +1,25 @@ | |||
1 | #pragma once | 1 | #pragma once |
2 | 2 | ||
3 | #include <lmdb.h> | ||
4 | #include <string> | 3 | #include <string> |
5 | #include <QString> | 4 | #include <QString> |
6 | 5 | ||
7 | class Database { | 6 | class Database { |
8 | public: | 7 | public: |
8 | enum TransactionType { ReadOnly, ReadWrite }; | ||
9 | |||
9 | Database(const QString &path); | 10 | Database(const QString &path); |
10 | ~Database(); | 11 | ~Database(); |
11 | MDB_txn *startTransaction(); | 12 | bool isInTransaction() const; |
12 | void endTransaction(MDB_txn *transaction); | 13 | bool startTransaction(TransactionType type = ReadWrite); |
13 | void write(const std::string &sKey, const std::string &sValue, MDB_txn *transaction); | 14 | bool commitTransaction(); |
15 | void abortTransaction(); | ||
16 | bool write(const std::string &sKey, const std::string &sValue); | ||
14 | //Perhaps prefer iterators (assuming we need to be able to match multiple values | 17 | //Perhaps prefer iterators (assuming we need to be able to match multiple values |
15 | void read(const std::string &sKey, const std::function<void(const std::string)> &); | 18 | void read(const std::string &sKey, const std::function<void(const std::string &value)> &); |
16 | |||
17 | private: | ||
18 | MDB_env *env; | ||
19 | MDB_dbi dbi; | ||
20 | }; | ||
21 | |||
22 | /* | ||
23 | * This opens the db for a single read transaction. | ||
24 | * | ||
25 | * The lifetime of all read values is tied to this transaction. | ||
26 | */ | ||
27 | class ReadTransaction { | ||
28 | public: | ||
29 | ReadTransaction(const QString &path); | ||
30 | ~ReadTransaction(); | ||
31 | |||
32 | void read(const std::string &sKey, const std::function<void(void *ptr, int size)> &); | 19 | void read(const std::string &sKey, const std::function<void(void *ptr, int size)> &); |
33 | 20 | ||
34 | private: | 21 | private: |
35 | MDB_env *env; | 22 | class Private; |
36 | MDB_dbi dbi; | 23 | Private * const d; |
37 | MDB_txn *txn; | ||
38 | }; | 24 | }; |
25 | |||
diff --git a/store/test/storagebenchmark.cpp b/store/test/storagebenchmark.cpp index 6fc50f6..6f9f18b 100644 --- a/store/test/storagebenchmark.cpp +++ b/store/test/storagebenchmark.cpp | |||
@@ -1,12 +1,14 @@ | |||
1 | #include <QtTest> | 1 | #include <QtTest> |
2 | 2 | ||
3 | #include "calendar_generated.h" | 3 | #include "calendar_generated.h" |
4 | |||
4 | #include <iostream> | 5 | #include <iostream> |
5 | #include <fstream> | 6 | #include <fstream> |
7 | |||
8 | #include <QDebug> | ||
6 | #include <QDir> | 9 | #include <QDir> |
7 | #include <QString> | 10 | #include <QString> |
8 | #include <QTime> | 11 | #include <QTime> |
9 | #include <qdebug.h> | ||
10 | 12 | ||
11 | #include "store/database.h" | 13 | #include "store/database.h" |
12 | 14 | ||
@@ -75,7 +77,10 @@ private Q_SLOTS: | |||
75 | QFETCH(bool, useDb); | 77 | QFETCH(bool, useDb); |
76 | QFETCH(int, count); | 78 | QFETCH(int, count); |
77 | 79 | ||
78 | Database db(dbPath); | 80 | Database *db = 0; |
81 | if (useDb) { | ||
82 | db = new Database(dbPath); | ||
83 | } | ||
79 | 84 | ||
80 | std::ofstream myfile; | 85 | std::ofstream myfile; |
81 | myfile.open(filePath.toStdString()); | 86 | myfile.open(filePath.toStdString()); |
@@ -85,21 +90,22 @@ private Q_SLOTS: | |||
85 | 90 | ||
86 | time.start(); | 91 | time.start(); |
87 | { | 92 | { |
88 | auto transaction = db.startTransaction(); | ||
89 | auto event = createEvent(); | 93 | auto event = createEvent(); |
90 | for (int i = 0; i < count; i++) { | 94 | for (int i = 0; i < count; i++) { |
91 | if (useDb && i > 0 && (i % 10000 == 0)) { | 95 | if (db) { |
92 | db.endTransaction(transaction); | 96 | if (i % 10000 == 0) { |
93 | transaction = db.startTransaction(); | 97 | db->commitTransaction(); |
94 | } | 98 | db->startTransaction(); |
95 | if (useDb) { | 99 | } |
96 | db.write(keyPrefix + std::to_string(i), event, transaction); | 100 | |
101 | db->write(keyPrefix + std::to_string(i), event); | ||
97 | } else { | 102 | } else { |
98 | myfile << event; | 103 | myfile << event; |
99 | } | 104 | } |
100 | } | 105 | } |
101 | if (useDb) { | 106 | |
102 | db.endTransaction(transaction); | 107 | if (db) { |
108 | db->commitTransaction(); | ||
103 | } else { | 109 | } else { |
104 | myfile.close(); | 110 | myfile.close(); |
105 | } | 111 | } |
@@ -110,8 +116,8 @@ private Q_SLOTS: | |||
110 | time.start(); | 116 | time.start(); |
111 | { | 117 | { |
112 | for (int i = 0; i < count; i++) { | 118 | for (int i = 0; i < count; i++) { |
113 | if (useDb) { | 119 | if (db) { |
114 | db.read(keyPrefix + std::to_string(i)); | 120 | db->read(keyPrefix + std::to_string(i), [](void *ptr, int size){}); |
115 | } | 121 | } |
116 | } | 122 | } |
117 | } | 123 | } |