Lumosql

Top-level Files of tip
Login

Top-level Files of tip

Files in the top-level directory from the latest check-in


<!-- Copyright 2020 The LumoSQL Authors, see LICENCES/MIT --> <!-- SPDX-License-Identifier: MIT --> <!-- SPDX-FileCopyrightText: 2020 The LumoSQL Authors --> <!-- SPDX-ArtifactOfProjectName: LumoSQL --> <!-- SPDX-FileType: Documentation --> <!-- SPDX-FileComment: Original by Dan Shearer, December 2019 --> <!-- toc -->

Welcome to LumoSQL v0.85

LumoSQL is a modification of the SQLite embedded data storage library, made using the not-forking software reproducibility tool. SQLite is among the most-deployed software which is why LumoSQL goes to great lengths not to fork it. LumoSQL adds features to SQLite by combining multiple source trees as they change over time, notably the LMDB library, besides other bespoke features if users select them. Not all features involve an incompatible file format like LMDB does. This preview release is for curious technical developers wondering why anyone would want to touch trillion-scale software?. The big-picture reasons are in the LumoSQL motivations document, while some pragmatic reasons include:

Production use is not yet recommended although we are reasonably confident LumoSQL will not eat your data. We welcome all reports of success and failure.

LumoSQL exists today because of the efforts of many people since late 2019, as the minimum glue between famously well-tested code found on devices and computers everywhere: SQLite and LMDB. LumoSQL is an unusual feat of engineering, but if you're interested in the computer science theory behind it (especially the idea of intention when making code changes) please do get in touch because we have very active work in that area.

LumoSQL was supported by the NLNet Foundation, and we thank them very much.

Things you can do with LumoSQL:

LumoSQL source is at lumosql.org (Fossil) and Codeberg (git).

LumoSQL stays as close as possible to the SQLite way of software development, recognising that there is almost no room for change without becoming a fork. Therefore build tooling uses Tcl, we produce an SQLite-compatible amalgamation, and we use the MIT licence which is very close in spirit to the SQLite licensing situation, and our integration tool is literally called not-forking. It is also why Fossil was an early target to support, because Fossil and SQLite are symbiotic projects. If LumoSQL can work with Fossil, there's a good chance it can work with millions of other applications, as the other examples demonstrate.

Technical detail:

Dependencies

LumoSQL needs all of the following to build the default matrix of binaries, consisting of SQLite native + LMDB 0.9.x + LMDBv1.0 without encryption:

If you ever plan to enable encryption:

For the example applications in examples/:

So, assuming you have the above dependencies:

On Debian/Ubuntu:

sudo apt install build-essential git patch pkgconf tcl tclx \
    libtext-glob-perl libsodium-dev \
    zlib1g-dev libreadline-dev libncurses-dev

Optional, only to fetch from the canonical repositories instead of the git mirrors. Note that the packaged Fossil is often too old to clone sqlite.org, which rejects it with 426 Upgrade Required; build Fossil from source if you hit that. make doctor reports this as a warning, not an error, because git covers it.

sudo apt install fossil

On Fedora/RHEL:

sudo dnf install make gcc git patch pkgconf tcl8 tclx \
    perl-Text-Glob perl-ExtUtils-MakeMaker libsodium-devel \
    zlib-devel readline-devel ncurses-devel

Optional, as above: sudo dnf install fossil

On FreeBSD:

sudo pkg install git patch pkgconf tcl86 libsodium readline

FreeBSD does not package Tclx, so build it from source:

git clone https://github.com/flightaware/tclx
cd tclx
./configure --prefix=/usr/local --with-tcl="$(dirname $(find / -name tclConfig.sh 2>/dev/null | head -1))"
make && sudo make install

NetBSD packages it, as pkgin install tclx.

No distribution packages not-forking, so build it from source. It needs Perl plus Text::Glob, installed by the lines above:

git clone https://codeberg.org/not-forking/not-forking   # or: fossil clone https://lumosql.org/src/not-forking
cd not-forking
perl Makefile.PL && make && sudo make install

Then get LumoSQL itself and check the dependencies:

git clone https://codeberg.org/lumosql/lumosql    # or, with Fossil: fossil clone https://lumosql.org/src/lumosql
cd lumosql
make doctor

make doctor checks every required dependency and tells you how to fix each one it can't find. Iterate until it is happy, then see Build and benchmark below.

Containers and CI without a /etc/passwd entry for the effective UID need export USER=XXSOMESTRING before make otherwise fossil complains.

NixOS has a unique link-based filesystem layout, so for example tools that expect /usr will fail. doc/lumo-build-nixos.md explains what we do about this.

If you are using a large shared machine (eg on a cluster) you may have outdated packages and no permissions to install software. Install locally, for example for Tcl or no Tclx, build them into $HOME/.local and prepend that prefix to PATH, LD_LIBRARY_PATH, LIBRARY_PATH, C_INCLUDE_PATH, PKG_CONFIG_PATH, and set TCLLIBPATH=$HOME/.local/lib. The benchmark script benchmark/lumosql-matrix-on-big-machine.sh shows one such environment.

About LMDB and mmap

LMDB and SQLite's native btree differ at the level of architecture. SQLite is a traditional userspace database program, with a pager, a page cache, a WAL (Write Ahead Log) to make transactions reliable and so on. LMDB takes an entirely different approach, by using the operating system's mmap system calls to do all memory management work for the database including managing buffers, COW (Copy on Write) and reliable persistence to disk. The part of the operating system that hands out memory, its virtual memory system, is tuned hard for the particular hardware, so LMDB is outsourcing one of the most difficult and performance-sensitive parts of a database. The benchmarking system does not yet measure CPU and RAM usage, but we can reasonably expect they are considerably lower under LMDB because the operating system VM has a global view whereas an ordinary userspace database application only knows about its own processes.

SQLite can also use mmap for databases, in three different contexts, but never as a page management mechanism for the BTree. There are many applications and some databases that use mmap, but only LMDB has tried to become the thinnest layer possible over the operating system's VM. This does come with some limitations, including that there is some hardware so limited that it doesn't really have an operating system at all, and certainly not a memory manager. LMDB cannot run on such systems.

mmap is unlikely ever to be supported by WASM because the memory models and system access requirements are incompatible. WASM is great at handling the memory requirements of ordinary userspace programs (see the handy SQLite Fiddle as an example). However anything that uses mmap including LMDB and the shm part of SQLite's WAL mode cannot work in WASM.

Because each LMDB environment reserves its address space up front, a process that opens several LumoSQL databases at once can exhaust the virtual address space even though it is nowhere near running out of RAM. The symptom is SQLITE_NOMEM and the message out of memory on opening the fourth or later database, or on concurrent sqlite3_backup operations, each of which opens a database of its own. This is a real ENOMEM from mmap, not a spurious one: the default reservation is 16TiB per environment, so roughly eight fit in the 128TiB user address space of x86-64.

Set LUMO_LMDB_MAPSIZE to the per-environment reservation in MiB to raise the number of databases that can be open at once. The value is a ceiling on how large each database may grow, so size it for your data, not for the default:

LUMO_LMDB_MAPSIZE=64 myapplication

The same variable is useful in containers and under cgroup memory limits, where the default reservation is harmless on bare metal but is accounted against a smaller limit. Values below 4MiB are ignored.

What doesn't work

There are plenty of details to fix. For example: the key handling for the encryption is rudimentary, and other items are listed in the LumoSQL TODO. LumoSQL builds and passes its tests on Debian, Ubuntu, Fedora, Arch, Alpine and NixOS, and on FreeBSD and NetBSD. We haven't tested on Windows although Windows is a first-class platform for both SQLite and LMDB so we do not expect much difficulty. We do not know of any crashbugs in LumoSQL, but as prerelease software there are doubtless some lurking.

Build and benchmark

make what lists the resolved option set. make targets resolves latest against upstream and lists the targets the matrix would build. make build builds them. make benchmark runs the speedtest1-derived tests against each built binary and appends to benchmarks.sqlite. make test-sql runs the LumoSQL-specific tests in test/sql/ and appends to test-sql.sqlite. See doc/lumo-build-benchmark.md for the detail.

A single-binary benchmark on a small machine takes a few minutes; the full matrix on a big machine takes hours, because by default the build system makes a number of binaries with different combinations. If you are just starting with LumoSQL, you probably want just two: unmodified SQLite of a given version (we will go with 3.53.2 for this example) and the same version backed by LMDBv1.0, with no encryption or other options to start with. Name them with TARGETS:

make build TARGETS="3.53.2 3.53.2+lmdbv1-1.0"

TARGETS accepts the target names make targets prints, and pins what gets built without modifying anything in the tree. Every make target that builds understands it, so you can test or benchmark the same pair:

make test-sql TARGETS="3.53.2+lmdbv1-1.0"
make benchmark TARGETS="3.53.2 3.53.2+lmdbv1-1.0"

The default matrix, which is what you get when you leave TARGETS off, is declared in the not-fork.d/*/benchmark/versions files, one per upstream source. Editing those changes the default for every later command, and leaves your checkout modified. They also take precedence over the version variables below. Use TARGETS for everyday work, and change the versions files only when you mean to move the project default.

Build options are passed as OPTION_X=value or as a member of the target tuple. Examples switching on encryption and row-level checksums:

make build SQLITE_VERSIONS=3.53.2 LMDB_VERSIONS=0.9.35 \
    SQLITE_FOR_LMDB=3.53.2
make build SQLITE_VERSIONS=3.53.2 USE_LMDB=no USE_LMDBV1=yes \
    LMDBV1_VERSIONS=1.0 SQLITE_FOR_LMDBV1=3.53.2 \
    ROWSUM=on OPTION_LMDBV1_ENCRYPT=on

Build encryption with OPTION_LMDBV1_ENCRYPT=on, then supply the passphrase via the URI parameter at open time:

sqlite3 'file:/path/to/db?lumo_key=passphrase'

The first open generates a <db>-salt sibling file; subsequent opens reuse it. Back up <db>-salt with the database and keep the two together. The salt is an input to the key derivation, so an encrypted database cannot be opened without it, even with the correct passphrase. Losing the salt loses the data. Opening a database whose salt is missing is refused giving you the chance to restore the salt from a backup. URI-key is the way SQLite-based products pass keys today (SQLCipher, SEE, and similar follow this convention) but isn't really ideal.

An application can hide the URI handling from its users as shown in examples/fossil. Running make LUMO_ENCRYPT=on in that directory builds a Fossil that reads the passphrase from LUMO_KEY in the environment and rewrites its own filenames, so fossil commands work unchanged on an encrypted repository. See also examples/fossil/README.md. The is more key-management work discussed in TODO-CRYPTO.md.

If you just want to use the SQLite-compatible LumoSQL library to link against your own application, stop here and read LUMOSQL-AMALGAMATION.md, which covers generating the sqlite3.c/sqlite3.h pair with KEEP_SOURCES=1, the helper sources that must travel with it, and the defines each variant needs. The three worked examples in examples/ each link a real application this way: Fossil and libfossil, rpm and Node.js better-sqlite3.

If you want to do benchmarking, replace 'build' with 'benchmark' in the make commands above to run benchmarking on the binaries you just built (if they aren't built, the benchmarking target will build them first anyway.)

Benchmarking results are in SQLite databases queryable via tool/benchmark-filter.tcl and tool/test-sql-filter.tcl. Here are some commands to try on the file benchmarks.sqlite at the top of the tree:

# Compare LMDB-backed SQLite against the same SQLite with the native btree:
tclsh tool/benchmark-filter.tcl -db benchmarks.sqlite \
    -compare -A -backend lmdb -B -no-backend

and

# List the 20 most recent benchmark runs with target, date and total duration:
tclsh tool/benchmark-filter.tcl -db benchmarks.sqlite \
    -list -fields TARGET,DATE,DURATION,BACKEND_NAME

There's a lot more benchmarking to play with, including the hardware that tests were run on, basic statistics, and exporting as TSV to statistical and graphing programs.

Other details

Target names encode the option dimensions: 3.53.2+lmdbv1-1.0+lmdbv1_encrypt-on+rowsum-on is one binary at build/3.53.2+lmdbv1-1.0+lmdbv1_encrypt-on+rowsum-on/sqlite3. Built binaries are shell wrappers that set LD_LIBRARY_PATH to find the backend .so they were linked against.

The not-fork cache lives under $CACHE_DIR (default ~/.cache/LumoSQL/not-fork) and is content-addressed, so re-running make build after editing a .mod fragment only rebuilds affected targets.

3.43.0 is the effective lower bound for LMDBv1 or rowsum builds, and 3.48.0 is where SQLite switched from Autotools to Autosetup. sqlite-version-map.md maps each version range to the .mod recipes that apply and the upstream anchor each one depends on. Read it first when a build fails on an unusual SQLite version.

Where to go next

Getting involved

Bug reports, build failures on unusual platforms and benchmark result databases are all welcome, and you do not need to be a C programmer to help. CONTRIBUTING.md explains how we work and what we need; everyone taking part is asked to follow the code of conduct. Development happens in Fossil at lumosql.org, mirrored to Codeberg.