switched things up

This commit is contained in:
Gregory Burd 2024-05-01 11:44:32 -04:00
parent cb3c7df552
commit e24f034fd9
11 changed files with 183 additions and 43 deletions

View file

@ -13,7 +13,7 @@ AlwaysBreakAfterReturnType: None
BreakBeforeBinaryOperators: All BreakBeforeBinaryOperators: All
BreakBeforeBraces: Attach BreakBeforeBraces: Attach
BreakBeforeConceptDeclarations: Always BreakBeforeConceptDeclarations: Always
ColumnLimit: 80 ColumnLimit: 120
ContinuationIndentWidth: 2 ContinuationIndentWidth: 2
Cpp11BracedListStyle: true Cpp11BracedListStyle: true
FixNamespaceComments: true FixNamespaceComments: true

4
.envrc
View file

@ -3,7 +3,3 @@ if ! has nix_direnv_version || ! nix_direnv_version 3.0.4; then
fi fi
watch_file devShell.nix shell.nix flake.nix watch_file devShell.nix shell.nix flake.nix
use flake || use nix use flake || use nix
CMAKE_GENERATOR=Ninja
CMAKE_MAKE_PROGRAM=Ninja

1
.gdbinit Normal file
View file

@ -0,0 +1 @@
handle SIG35 nostop noprint

1
.gitmodules vendored
View file

@ -1,3 +1,4 @@
[submodule "seastar"] [submodule "seastar"]
path = seastar path = seastar
url = https://github.com/scylladb/seastar.git url = https://github.com/scylladb/seastar.git
branch = 2b43417d210edbd7a3c3065bcfe3c0a9aea27f75

View file

@ -40,7 +40,10 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake") # Make our cmak
#################### ####################
## Dependencies ## ## Dependencies ##
#################### ####################
find_package(Boost REQUIRED COMPONENTS system)
if(Boost_FOUND)
include_directories(${Boost_INCLUDE_DIRS})
endif()
############### ###############
## Options ## ## Options ##

View file

@ -5,20 +5,87 @@ Formerly named "HanoiDB" but the C++ version needed a new name, so ^H^H and
voila, "NoiDB". voila, "NoiDB".
### History ### History
See [HanoiDB](https://github.com/krestenkrab/hanoidb) and the [lasp-lang](https://github.com/lasp-lang/hanoidb) fork. See [HanoiDB](https://github.com/krestenkrab/hanoidb) and the
[lasp-lang](https://github.com/lasp-lang/hanoidb) fork.
### Network API
HTTP REST CRUD API
#### Create/Update
| *Method* | *Path* | *Consumes* | HTTP Code |
| -------- | --------------- | ------------------------ | --------- |
| `PUT` | `/kv/:key?value`| `value` query parameter | 2xx, etc. |
| `POST` | `/kv/:key` | `application/text` body | 2xx, etc |
*Path Parameters*
* `key` (string: "") - Specifies the path of the key to read.
*Query Parameters*
* `value` (string: "") - Specifies the value to store for the key.
#### Read Key
| *Method* | *Path* | *Produces* | HTTP Code |
| -------- | --------------- | ------------------------ | --------- |
| `GET` | `/kv/:key` | `application/text` value | 2xx, etc. |
*Path Parameters*
* `key` (string: "") - Specifies the path of the key to read.
#### Delete Key
| *Method* | *Path* | *Produces* | HTTP Code |
| -------- | --------------- | ------------------------ | --------- |
| `DELETE` | `/kv/:key` | | 2xx, etc. |
*Path Parameters*
* `key` (string: "") - Specifies the path of the key to read.
### Seastar Specifics
* All REST requests serviable by any shard.
* Queries map/reduce nurseries, then contact owning shard (possibly triggering
incremental merge work).
* Owner of level 2<sup>n</sup> governed by random slicing.
* Every node runs a nursery.
* Each nursery is at more 2^8 KVPs
* Nurseries are:
* B-trees in memory,
* and logged to disk according to format below.
* Searching across nurseries is a map/reduce operation over the shards.
* Combining, merging Nurseries owned by shard closing the nursery.
### Basics ### Basics
If there are N records, there are in log<sub>2</sub>(N) levels (each being a plain B-tree in a file named "A-*level*.data"). The file `A-0.data` has 1 record, `A-1.data` has 2 records, `A-2.data` has 4 records, and so on: `A-n.data` has 2<sup>n</sup> records. If there are N records, there are in log<sub>2</sub>(N) levels (each being a
plain B-tree in a file named "A-*level*.data"). The file `A-0.data` has 1
record, `A-1.data` has 2 records, `A-2.data` has 4 records, and so on:
`A-n.data` has 2<sup>n</sup> records.
In "stable state", each level file is either full (there) or empty (not there); so if there are e.g. 20 records stored, then there are only data in filed `A-2.data` (4 records) and `A-4.data` (16 records). In "stable state", each level file is either full (there) or empty (not there);
so if there are e.g. 20 records stored, then there are only 2 data files
`A-2.data` (4 records) and `A-4.data` (16 records) required.
OK, I've told you a lie. In practice, it is not practical to create a new file for each insert (injection at level #0), so we allows you to define the "top level" to be a number higher that #0; currently defaulting to #5 (32 records). That means that you take the amortization "hit" for ever 32 inserts. In practice, it is not practical to create a new file for each insert (injection
at level #0), so we maintain a "top level" to be a number higher that #0;
currently defaulting to #5 (32 records). That means that you take the
amortization "hit" for ever 32 inserts. This first combined level is the
"Nursery".
### Lookup ### Lookup
Lookup is quite simple: starting at `A-0.data`, the sought for Key is searched in the B-tree there. If nothing is found, search continues to the next data file. So if there are *N* levels, then *N* disk-based B-tree lookups are performed. Each lookup is "guarded" by a bloom filter to improve the likelihood that disk-based searches are only done when likely to succeed. Lookup is quite simple: starting at `A-0.data`, the sought for key is searched
in the B-tree there. Finding nothing, the search continues to the next data
file. So if there are *N* levels, then *N* disk-based B-tree lookups are
performed. Each lookup is "guarded" by a bloom filter to improve the likelihood
that disk-based searches are only done when likely to succeed.
### Insertion ### Insertion
Insertion works by a mechanism known as B-tree injection. Insertion always starts by constructing a fresh B-tree with 1 element in it, and "injecting" that B-tree into level #0. So you always inject a B-tree of the same size as the size of the level you're injecting it into. Insertion works by a mechanism known as B-tree injection. Insertion always
starts by constructing a fresh B-tree with 1 element in it, and "injecting" that
B-tree into level #0. So you always inject a B-tree of the same size as the
size of the level you're injecting it into.
- If the level being injected into empty (there is no A-*level*.data file), then the injected B-tree becomes the contents for that level (we just rename the file). - If the level being injected into empty (there is no A-*level*.data file), then the injected B-tree becomes the contents for that level (we just rename the file).
- Otherwise, - Otherwise,

View file

@ -22,13 +22,31 @@
"utils": "utils" "utils": "utils"
} }
}, },
"utils": { "systems": {
"locked": { "locked": {
"lastModified": 1623875721, "lastModified": 1681028828,
"narHash": "sha256-A8BU7bjS5GirpAUv4QA+QnJ4CceLHkcXdRp4xITDB0s=", "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1710146030,
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
"owner": "numtide", "owner": "numtide",
"repo": "flake-utils", "repo": "flake-utils",
"rev": "f7e004a55b120c02ecb6219596820fcd32ca8772", "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
"type": "github" "type": "github"
}, },
"original": { "original": {

View file

@ -37,6 +37,7 @@
# Build time and Run time dependencies # Build time and Run time dependencies
boost boost
cryptopp
c-ares c-ares
fmt fmt
gnutls gnutls
@ -57,6 +58,10 @@
icon = "f121"; icon = "f121";
in '' in ''
export PS1="$(echo -e '\u${icon}') {\[$(tput sgr0)\]\[\033[38;5;228m\]\w\[$(tput sgr0)\]\[\033[38;5;15m\]} (${name}) \\$ \[$(tput sgr0)\]" export PS1="$(echo -e '\u${icon}') {\[$(tput sgr0)\]\[\033[38;5;228m\]\w\[$(tput sgr0)\]\[\033[38;5;15m\]} (${name}) \\$ \[$(tput sgr0)\]"
export CMAKE_GENERATOR=Ninja
export CMAKE_MAKE_PROGRAM=Ninja
export CC=clang
export CXX=clang++
''; '';
}; };
}); });

@ -1 +1 @@
Subproject commit a965080ec0bb895e5c6196d3b082fa8d8f49b512 Subproject commit 2b43417d210edbd7a3c3065bcfe3c0a9aea27f75

View file

@ -1,3 +1,3 @@
add_executable(noidb) add_executable(noidb)
target_sources(noidb PRIVATE noidb.cc) target_sources(noidb PRIVATE noidb.cc Database.cc)
target_link_libraries(noidb PRIVATE Seastar::seastar) target_link_libraries(noidb PRIVATE Seastar::seastar)

View file

@ -1,36 +1,85 @@
#include "Database.hh"
#include <seastar/apps/lib/stop_signal.hh>
#include <seastar/core/app-template.hh> #include <seastar/core/app-template.hh>
#include <seastar/core/coroutine.hh> #include <seastar/core/reactor.hh>
#include <seastar/core/memory.hh> #include <seastar/core/sleep.hh>
#include <seastar/core/thread.hh>
#include <seastar/http/file_handler.hh>
#include <seastar/http/function_handlers.hh>
#include <seastar/http/httpd.hh>
#include <seastar/http/request.hh>
#include <seastar/http/routes.hh>
#include <seastar/util/log.hh> #include <seastar/util/log.hh>
// using namespace seastar; #include <stdexcept>
seastar::logger lg("hanoidb"); using namespace seastar;
using namespace httpd;
static seastar::future<> hello_from_all_cores_serial() { logger lg("noidb");
for (unsigned i = 0; i < seastar::smp::count; ++i) {
co_await seastar::smp::submit_to(
i, [] { lg.info("serial - Hello from every core"); });
};
co_return;
}
static seastar::future<> hello_from_all_cores_parallel() { namespace bpo = boost::program_options;
co_await seastar::smp::invoke_on_all([]() -> seastar::future<> {
auto memory = seastar::memory::get_memory_layout();
lg.info(
"parallel - memory layout start={} end={} size={}", memory.start, memory.end, memory.end - memory.start);
co_return;
});
co_return;
}
int main(int argc, char** argv) { int main(int argc, char** argv) {
seastar::app_template app; seastar::app_template app;
return app.run(argc, argv, [&]() -> seastar::future<int> { // Options
co_await hello_from_all_cores_serial(); app.add_options()("address", bpo::value<seastar::sstring>()->default_value("0.0.0.0"), "HTTP Server address");
co_await hello_from_all_cores_parallel(); app.add_options()("port", bpo::value<uint16_t>()->default_value(8080), "HTTP Server port");
co_return 0; app.add_options()("data", bpo::value<std::string>()->required(), "Data directory");
});
try {
return app.run(argc, argv, [&app] {
return seastar::async([&app] {
seastar_apps_lib::stop_signal stop_signal;
const auto& config = app.configuration();
// Start Server
seastar::net::inet_address addr(config["address"].as<seastar::sstring>());
uint16_t port = config["port"].as<uint16_t>();
seastar::httpd::http_server_control srv;
srv.start().get();
Database db(srv);
srv
.set_routes([](seastar::httpd::routes& r) {
r.add(
seastar::httpd::operation_type::GET,
seastar::httpd::url("/hello"),
new seastar::httpd::function_handler(
[]([[maybe_unused]] seastar::httpd::const_req req) { return "hi"; }));
})
.get();
srv
.set_routes([](seastar::httpd::routes& r) {
r.add(
seastar::httpd::operation_type::GET,
seastar::httpd::url("").remainder("path"),
new seastar::httpd::directory_handler("./public/"));
})
.get();
srv.listen(seastar::socket_address{addr, port}).get();
lg.info("NoiDB HTTP server listening on {}:{}\n", addr, port);
seastar::engine().at_exit([&srv, &db]() -> seastar::future<> {
lg.info("Stopping NoiDB HTTP server");
auto status = co_await db.stop();
if (status) lg.info("Stopped NoiDB Database");
co_await srv.stop();
co_return;
});
stop_signal.wait().get(); // block waiting for SIGINT or SIGTERM signal
});
});
} catch (...) {
lg.error("Failed to start NoiDB: {}\n", std::current_exception());
return 1;
}
} }