////
Copyright (c) 2024 The C++ Alliance, Inc. (https://cppalliance.org)

Distributed under the Boost Software License, Version 1.0. (See accompanying
file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)

Official repository: https://github.com/boostorg/website-v2-docs
////
= High-Performance Database Engine
:navtitle: Database Engine

Creating a high-performance database application in pass:[C++] involves a range of tasks, including efficient data structures, shared and optimized memory management, safe message and network communication, persistent storage, and so much more. This section examines how to get started.

[square]
* <<Libraries>>
* <<Sample Database Engine using Containers>>
* <<Optimize Memory Allocation>>
* <<Use Persistent Shared Memory>>
* <<Safely Allow Access from Multiple Processes>>
* <<Add Serialization to Archive the Database>>
* <<Next Steps>>
* <<See Also>>

== Libraries

Here are some Boost libraries that might be useful when planning and building your database app:

[circle]
* boost:container[] : Provides STL-compatible containers, including stable vector, flat set/map and more. The containers provided by this library can offer performance benefits over their standard library equivalents, making them a good fit for a high-performance database application.

* boost:pool[] : This library is used for simple, fast memory allocation and can improve efficiency in some scenarios by managing memory in chunks.

* boost:interprocess[] : This library allows for shared memory communication and synchronization between processes. In a database context, this can be useful for inter-process communication (IPC) and shared memory databases.

* boost:lockfree[] : Provides lock-free data structures which could be useful in multi-threaded database applications where you want to avoid locking overhead.

* boost:serialization[] : If you need to serialize objects for storage, boost:serialization[] can be a useful tool. However, be aware that for many database applications, more specialized serialization formats (like Protocol Buffers, Thrift, etc.) might be more appropriate.

* boost:asio[] : Provides a consistent asynchronous model using a modern pass:[C++] approach for network and low-level I/O programming. It supports a variety of network protocols, which could be helpful if your database needs to communicate over a network.

* boost:thread[] : Provides a portable interface for multithreading, which can be crucial when creating a high-performance database that can handle multiple queries concurrently.

* boost:fiber[] : Allows you to write code that works with fibers, which are user-space threads that can be used to write concurrent code. This can be useful in situations where you have many tasks that need to run concurrently but are I/O-bound rather than CPU-bound.

* boost:polygon[] or boost:geometry[] : For storing and querying spatial data, these libraries can provide the necessary data types and algorithms.

* boost:filesystem[] : Provides a portable way of querying and manipulating paths, files, and directories.

Note:: The code in this tutorial was written and tested using Microsoft Visual Studio (Visual C++ 2022, Console App project) with Boost version 1.88.0.

== Sample Database Engine using Containers

A database engine requires efficient data structures for handling indexes, caches, and storage layouts. The boost:container[] library provides drop-in replacements for standard containers like `std::vector`, `std::map`, and `std::unordered_map`, but optimized for memory efficiency and performance.

In the following sample code, we will use in-memory indexing as the basis of a database engine. The boost:container[] `flat_map` feature is used to store a sorted index for quick lookups, and the `stable_vector` feature to store persistent records with stable pointers. The sample demonstrates inserting and retrieving records efficiently.

[source,cpp]
----
#include <boost/container/flat_map.hpp>
#include <boost/container/stable_vector.hpp>
#include <iostream>
#include <string>

// Define a Simple Database Table Structure
struct Record {
    int id;             // Primary Key
    std::string name;   // Represents record data

    Record(int id, std::string name) : id(id), name(std::move(name)) {}
};

// Implement a Database Table Class
class DatabaseTable {
public:
    using RecordStorage = boost::container::stable_vector<Record>;
    using IndexMap = boost::container::flat_map<int, size_t>; // Fast lookup

    void insert(int id, const std::string& name) {
        size_t index = records.size();
        records.emplace_back(id, name);
        index_map[id] = index;
    }

    const Record* find(int id) {
        auto it = index_map.find(id);
        if (it != index_map.end()) {
            return &records[it->second];
        }
        return nullptr;
    }

    void print_all() const {
        for (const auto& record : records) {
            std::cout << "ID: " << record.id << ", Name: " << record.name << "\n";
        }
    }

private:
    RecordStorage records;  // Stores records in a stable manner
    IndexMap index_map;     // Provides fast ID lookups
};

// Demonstrate Database Operations
int main() {
    DatabaseTable db;

    // Insert records
    db.insert(101, "Alice");
    db.insert(102, "Bob");
    db.insert(103, "Charlie");

    // Retrieve a record
    const Record* record = db.find(102);
    if (record) {
        std::cout << "Found: ID = " << record->id << ", Name = " << record->name << "\n";
    } else {
        std::cout << "Record not found!\n";
    }

    // Print all records
    std::cout << "All records:\n";
    db.print_all();

    return 0;
}

----

Note:: Key features of this sample are that it is memory-efficient (reducing fragmentation and with good performance), `stable_vector` prevents invalid references when resizing, and `flat_map` is faster than `std::map` for heavy use.

Run the program, the output should be:

[source,text]
----
Found: ID = 102, Name = Bob
All records:
ID: 101, Name: Alice
ID: 102, Name: Bob
ID: 103, Name: Charlie

----

== Optimize Memory Allocation

As we are dealing with frequent allocations of small objects (the database records) we'll enhance our database engine by using boost:pool[]. This library avoids repeated calls to `malloc`, `new` and `delete`.

[source,cpp]
----
#include <boost/container/flat_map.hpp>
#include <boost/pool/pool.hpp>
#include <iostream>
#include <string>

struct Record {
    int id;
    std::string name;

    Record(int id, std::string name) : id(id), name(std::move(name)) {}
};

class DatabaseTable {
public:
    using IndexMap = boost::container::flat_map<int, Record*>;

    DatabaseTable() : recordPool(sizeof(Record)) {}

    Record* insert(int id, const std::string& name) {
        void* memory = recordPool.malloc(); // Allocate memory from the pool
        if (!memory) {
            throw std::bad_alloc();
        }
        
        Record* newRecord = new (memory) Record(id, name); // Placement new
        index_map[id] = newRecord;
        return newRecord;
    }

    void remove(int id) {
        auto it = index_map.find(id);
        if (it != index_map.end()) {
            it->second->~Record(); // Call destructor
            recordPool.free(it->second); // Free memory back to the pool
            index_map.erase(it);
        }
    }

    Record* find(int id) {
        auto it = index_map.find(id);
        return (it != index_map.end()) ? it->second : nullptr;
    }

    void print_all() {
        for (const auto& pair : index_map) {
            std::cout << "ID: " << pair.first << ", Name: " << pair.second->name << "\n";
        }
    }

    ~DatabaseTable() {
        for (const auto& pair : index_map) {
            pair.second->~Record();
            recordPool.free(pair.second);
        }
    }

private:
    boost::pool<> recordPool;
    IndexMap index_map;
};

// Demonstrate Efficient Memory Use
int main() {
    DatabaseTable db;

    // Insert records
    db.insert(101, "Alice");
    db.insert(102, "Bob");
    db.insert(103, "Charlie");

    // Retrieve a record
    Record* record = db.find(102);
    if (record) {
        std::cout << "Found: ID = " << record->id << ", Name = " << record->name << "\n";
    }

    // Remove a record
    db.remove(102);
    if (!db.find(102)) {
        std::cout << "Record 102 removed successfully.\n";
    }

    // Print all records
    std::cout << "All records:\n";
    db.print_all();

    return 0;
}

----

Note:: Custom _Object Pools_ can be tuned for your specific object sizes.

The output should be:

[source,text]
----
Found: ID = 102, Name = Bob
Record 102 removed successfully.
All records:
ID: 101, Name: Alice
ID: 103, Name: Charlie

----

== Use Persistent Shared Memory

In a realistic database environment, you would probably want to enable a shared-memory database table that multiple processes can access simultaneously. For this, we need the features of boost:interprocess[]. This library enables multiple processes to share the same data faster than inter-process communication (IPC) via files or sockets, and includes mutexes and condition variables. 

[source,cpp]
----
#include <boost/interprocess/managed_shared_memory.hpp>
#include <boost/interprocess/containers/vector.hpp>
#include <iostream>

namespace bip = boost::interprocess;

const char* SHM_NAME = "SharedDatabase";
const char* TABLE_NAME = "UserTable";
const std::size_t MAX_USERS = 10;

struct UserRecord {
    int id;
    char name[32];
};

using ShmemAllocator = bip::allocator<UserRecord, bip::managed_shared_memory::segment_manager>;
using UserTable = bip::vector<UserRecord, ShmemAllocator>;

void create_table() {
    bip::shared_memory_object::remove(SHM_NAME);

    bip::managed_shared_memory segment(bip::create_only, SHM_NAME, 65536);
    const ShmemAllocator alloc_inst(segment.get_segment_manager());
    UserTable* table = segment.construct<UserTable>(TABLE_NAME)(alloc_inst);

    for (int i = 0; i < 3; ++i) {
        UserRecord user;
        user.id = 1 + table->size();
        std::snprintf(user.name, sizeof(user.name), "User%d", user.id);
        table->push_back(user);
    }

    std::cout << "Shared memory table created with 3 initial users.\n";
}

void show_table() {

    try
    {
        bip::managed_shared_memory segment(bip::open_only, SHM_NAME);
        UserTable* table = segment.find<UserTable>(TABLE_NAME).first;

        if (!table) {
            std::cerr << "Table not found.\n";
            return;
        }

        std::cout << "User Table:\n";
        for (const auto& user : *table) {
            std::cout << "  ID: " << user.id << ", Name: " << user.name << "\n";
        }
    }
    catch (...)
    {
        std::cerr << "Shared Memory error - create a table\n";
    }
}

void add_user() {

    try
    {
        bip::managed_shared_memory segment(bip::open_only, SHM_NAME);
        UserTable* table = segment.find<UserTable>(TABLE_NAME).first;

        if (!table) {
            std::cerr << "Table not found.\n";
            return;
        }

        if (table->size() >= MAX_USERS) {
            std::cerr << "Table is full (max " << MAX_USERS << " users).\n";
            return;
        }

        std::string name;

        std::cout << "Enter user name: ";
        std::getline(std::cin, name);

        UserRecord user;
        user.id = 1 + table->size();
        std::snprintf(user.name, sizeof(user.name) - 1, "%s", name.c_str());
        user.name[sizeof(user.name) - 1] = '\0';

        table->push_back(user);
        std::cout << "User added.\n";
    }
    catch (...)
    {
        std::cerr << "Shared Memory error - create a table\n";
    }
}

void print_menu() {
    std::cout << "\n=== Shared Memory User Table Menu ===\n";
    std::cout << "1. Create table   2. Show table   3. Add user   4. Clear shared memory   5. Exit: ";
}

int main() {
    while (true) {
        print_menu();

        int choice = 0;
        std::cin >> choice;
        std::cin.ignore(); // discard newline

        switch (choice) {
        case 1:
            create_table();
            show_table();
            break;
        case 2:
            show_table();
            break;
        case 3:
            add_user();
            break;
        case 4:
            bip::shared_memory_object::remove(SHM_NAME);
            break;
        case 5:
            std::cout << "Exiting...\n";
            return 0;
        default:
            std::cout << "Invalid option. Try again.\n";
        }
    }
}

----

Boost shared memory is persistent. Run the program, add some user records, and exit without choosing option `4`. Then run the program again and note the records you added have persisted.

First run:

[source,text]
----
=== Shared Memory User Table Menu ===
1. Create table   2. Show table   3. Add user   4. Clear shared memory   5. Exit: 1
Shared memory table created with 3 initial users.
User Table:
  ID: 1, Name: User1
  ID: 2, Name: User2
  ID: 3, Name: User3

=== Shared Memory User Table Menu ===
1. Create table   2. Show table   3. Add user   4. Clear shared memory   5. Exit: 3
Enter user name: Nigel
User added.

=== Shared Memory User Table Menu ===
1. Create table   2. Show table   3. Add user   4. Clear shared memory   5. Exit: 2
User Table:
  ID: 1, Name: User1
  ID: 2, Name: User2
  ID: 3, Name: User3
  ID: 4, Name: Nigel

=== Shared Memory User Table Menu ===
1. Create table   2. Show table   3. Add user   4. Clear shared memory   5. Exit: 5
Exiting...
----

Second run:

[source,text]
----
=== Shared Memory User Table Menu ===
1. Create table   2. Show table   3. Add user   4. Clear shared memory   5. Exit: 2
User Table:
  ID: 1, Name: User1
  ID: 2, Name: User2
  ID: 3, Name: User3
  ID: 4, Name: Nigel
----

== Safely Allow Access from Multiple Processes

To safely allow multiple processes to access and modify shared memory concurrently in your boost:interprocess[] program, you should use interprocess synchronization primitives — like `interprocess_mutex` to guard critical sections. 


[source,cpp]
----
#include <boost/interprocess/managed_shared_memory.hpp>
#include <boost/interprocess/containers/vector.hpp>
#include <iostream>

namespace bip = boost::interprocess;

const char* SHM_NAME = "SharedDatabase";
const std::size_t MAX_USERS = 10;

struct UserRecord {
    int id;
    char name[32];
};

using SegmentManager = bip::managed_shared_memory::segment_manager;
using ShmemAllocator = bip::allocator<UserRecord, SegmentManager>;
using UserTable = bip::vector<UserRecord, ShmemAllocator>;

// Wrap the shared data and the mutex
struct SharedData {
    bip::interprocess_mutex mutex;
    UserTable table;

    SharedData(const ShmemAllocator& alloc) : table(alloc) {}
};

const char* TABLE_NAME = "SharedUserTable";

void create_table() {
    bip::shared_memory_object::remove(SHM_NAME);

    bip::managed_shared_memory segment(bip::create_only, SHM_NAME, 65536);
    ShmemAllocator alloc_inst(segment.get_segment_manager());

    // Construct SharedData in shared memory
    segment.construct<SharedData>(TABLE_NAME)(alloc_inst);

    std::cout << "Shared memory table created.\n";
}

void show_table() {
    try {
        bip::managed_shared_memory segment(bip::open_only, SHM_NAME);
        SharedData* data = segment.find<SharedData>(TABLE_NAME).first;
        if (!data) {
            std::cerr << "Table not found.\n";
            return;
        }

        bip::scoped_lock<bip::interprocess_mutex> lock(data->mutex);
        std::cout << "User Table:\n";
        for (const auto& user : data->table) {
            std::cout << "  ID: " << user.id << ", Name: " << user.name << "\n";
        }
    }
    catch (...) {
        std::cerr << "Error accessing shared memory. Is it created?\n";
    }
}

void add_user() {
    try {
        bip::managed_shared_memory segment(bip::open_only, SHM_NAME);
        SharedData* data = segment.find<SharedData>(TABLE_NAME).first;
        if (!data) {
            std::cerr << "Table not found.\n";
            return;
        }

        bip::scoped_lock<bip::interprocess_mutex> lock(data->mutex);

        if (data->table.size() >= MAX_USERS) {
            std::cerr << "Table is full (max " << MAX_USERS << " users).\n";
            return;
        }

        std::string name;
        std::cout << "Enter user name: ";
        std::cin.ignore();
        std::getline(std::cin, name);

        UserRecord user;
        user.id = 1 + static_cast<int>(data->table.size());
        std::snprintf(user.name, sizeof(user.name) - 1, "%s", name.c_str());
        user.name[sizeof(user.name) - 1] = '\0';

        data->table.push_back(user);
        std::cout << "User added.\n";

    }
    catch (...) {
        std::cerr << "Error accessing shared memory. Is it created?\n";
    }
}

void print_menu() {
    std::cout << "\n=== Shared Memory User Table Menu ===\n";
    std::cout << "1. Create table   2. Show table   3. Add user   4. Clear shared memory   5. Exit\n";
    std::cout << "Choose an option: ";
}

int main() {
    while (true) {
        print_menu();

        int choice = 0;
        std::cin >> choice;

        switch (choice) {
        case 1:
            create_table();
            show_table();
            break;
        case 2:
            show_table();
            break;
        case 3:
            add_user();
            break;
        case 4:
            bip::shared_memory_object::remove(SHM_NAME);
            std::cout << "Shared memory cleared.\n";
            break;
        case 5:
            std::cout << "Exiting...\n";
            return 0;
        default:
            std::cout << "Invalid option. Try again.\n";
        }
    }
}

----

Now it is safe to run this program from two, or more, terminal sessions. 

== Add Serialization to Archive the Database

Finally, let's add the features of boost:serialization[] to allow us to save and restore snapshots of our shared-memory database, making it persistent across program runs even when the shared memory is cleared. We will extend our sample to serialize the records into an archive format.

[source,cpp]
----
#include <boost/interprocess/managed_shared_memory.hpp>   // For managing shared memory segments
#include <boost/interprocess/containers/vector.hpp>       // STL-like vector that works inside shared memory
#include <boost/interprocess/sync/named_mutex.hpp>        // Mutex across processes
#include <boost/serialization/vector.hpp>                 // Serialization support for std::vector
#include <boost/archive/text_oarchive.hpp>                // For saving serialized data to text files
#include <boost/archive/text_iarchive.hpp>                // For loading serialized data from text files
#include <iostream>
#include <fstream>

namespace bip = boost::interprocess;

// ---- Global configuration constants ----
const char* SHM_NAME = "SharedDatabase";                // Name of the shared memory segment
const char* TABLE_NAME = "UserTable";                   // Name of the container object inside shared memory
const char* MUTEX_NAME = "SharedTableMutex";            // Name of the interprocess mutex
const std::size_t MAX_USERS = 10;                       // Maximum number of users allowed in table

// ---- User Record structure, supports Boost.Serialization ----
struct UserRecord {
    int id;                 // Unique user ID
    char name[32];          // Fixed-size character buffer for username

    // Serialization function used by Boost.Archive
    template<class Archive>
    void serialize(Archive& ar, const unsigned int) {
        ar& id;

        // Wrap raw array in make_array so Boost knows how to handle it
        ar& boost::serialization::make_array(name, sizeof(name));
    }
};

// ---- Type aliases for clarity ----
using ShmemAllocator = bip::allocator<UserRecord, bip::managed_shared_memory::segment_manager>;

// Vector of UserRecords in shared memory
using UserTable = bip::vector<UserRecord, ShmemAllocator>;   

// ---- Create a new table in shared memory ----
void create_table() {

    // Remove any old shared memory segment and mutex (cleanup)
    bip::shared_memory_object::remove(SHM_NAME);
    bip::named_mutex::remove(MUTEX_NAME);

    // Create new shared memory segment of fixed size (64 KB here)
    bip::managed_shared_memory segment(bip::create_only, SHM_NAME, 65536);
    ShmemAllocator alloc(segment.get_segment_manager());

    // Construct a UserTable object inside shared memory
    UserTable* table = segment.construct<UserTable>(TABLE_NAME)(alloc);

    // Pre-populate with three sample users
    for (int i = 0; i < 3; ++i) {
        UserRecord user;
        user.id = 1 + table->size();
        std::snprintf(user.name, sizeof(user.name), "User%d", user.id);
        table->push_back(user);
    }

    std::cout << "Shared memory table created with 3 initial users.\n";
}

// ---- Display the contents of the table ----
void show_table() {
    try {
        bip::managed_shared_memory segment(bip::open_only, SHM_NAME);
        bip::named_mutex mutex(bip::open_or_create, MUTEX_NAME);

        // Lock table to prevent concurrent modifications
        bip::scoped_lock<bip::named_mutex> lock(mutex);

        // Find UserTable in shared memory
        UserTable* table = segment.find<UserTable>(TABLE_NAME).first;
        if (!table) {
            std::cerr << "Table not found.\n";
            return;
        }

        // Print all users
        std::cout << "User Table:\n";
        for (const auto& user : *table) {
            std::cout << "  ID: " << user.id << ", Name: " << user.name << "\n";
        }
    }
    catch (...) {
        std::cerr << "Unable to access shared memory.\n";
    }
}

// ---- Add a user to the shared memory table ----
void add_user() {
    try {
        bip::managed_shared_memory segment(bip::open_only, SHM_NAME);
        bip::named_mutex mutex(bip::open_or_create, MUTEX_NAME);
        bip::scoped_lock<bip::named_mutex> lock(mutex);

        UserTable* table = segment.find<UserTable>(TABLE_NAME).first;
        if (!table || table->size() >= MAX_USERS) {
            std::cerr << "Table not found or full.\n";
            return;
        }

        // Get new user name from console
        std::string name;

        // Discard leftover newline from previous input
        std::cin.ignore();

        std::cout << "Enter user name: ";
        std::getline(std::cin, name);

        // Create new record and append
        UserRecord user;
        user.id = 1 + table->size();
        std::snprintf(user.name, sizeof(user.name) - 1, "%s", name.c_str());
        table->push_back(user);

        std::cout << "User added.\n";
    }
    catch (...) {
        std::cerr << "Failed to add user.\n";
    }
}

// ---- Save snapshot of current table to a text file ----
void save_snapshot(const std::string& filename) {
    try {
        bip::managed_shared_memory segment(bip::open_only, SHM_NAME);
        bip::named_mutex mutex(bip::open_or_create, MUTEX_NAME);
        bip::scoped_lock<bip::named_mutex> lock(mutex);

        UserTable* table = segment.find<UserTable>(TABLE_NAME).first;
        if (!table) {
            std::cerr << "Table not found.\n";
            return;
        }

        // Copy data from shared memory into std::vector (heap memory)
        std::vector<UserRecord> snapshot(table->begin(), table->end());

        // Save serialized snapshot to file
        std::ofstream ofs(filename);
        boost::archive::text_oarchive oa(ofs);
        oa << snapshot;

        std::cout << "Snapshot saved to " << filename << "\n";
    }
    catch (...) {
        std::cerr << "Failed to save snapshot.\n";
    }
}

// ---- Load snapshot from text file into shared memory ----
void load_snapshot(const std::string& filename) {
    try {

        // Open file and load into vector
        std::ifstream ifs(filename);
        if (!ifs) {
            std::cerr << "Snapshot file not found.\n";
            return;
        }

        std::vector<UserRecord> snapshot;
        boost::archive::text_iarchive ia(ifs);
        ia >> snapshot;

        // Reset shared memory segment and mutex
        bip::shared_memory_object::remove(SHM_NAME);
        bip::managed_shared_memory segment(bip::create_only, SHM_NAME, 65536);
        bip::named_mutex::remove(MUTEX_NAME);
        bip::named_mutex mutex(bip::create_only, MUTEX_NAME);
        bip::scoped_lock<bip::named_mutex> lock(mutex);

        // Recreate UserTable and repopulate
        ShmemAllocator alloc(segment.get_segment_manager());
        UserTable* table = segment.construct<UserTable>(TABLE_NAME)(alloc);

        for (const auto& user : snapshot) {
            table->push_back(user);
        }

        std::cout << "Snapshot loaded from " << filename << "\n";
    }
    catch (...) {
        std::cerr << "Failed to load snapshot.\n";
    }
}

// ---- Clear all shared memory resources ----
void clear_shared_memory() {
    bip::shared_memory_object::remove(SHM_NAME);
    bip::named_mutex::remove(MUTEX_NAME);
    std::cout << "Shared memory cleared.\n";
}

// ---- Print the interactive menu ----
void print_menu() {
    std::cout << "\n=== Shared Memory Menu ===\n"
        << "1. Create table  2. Show table  3. Add user  4. Save snapshot  5. Load snapshot  6. Clear shared memory  7. Exit:";
}

// ---- Program entry point ----
int main() {
    while (true) {
        print_menu();
        int choice;
        std::cin >> choice;

        switch (choice) {
        case 1:
            create_table();

            // Show immediately after creation
            show_table();       
            break;
        case 2: show_table(); break;
        case 3: add_user(); break;
        case 4: save_snapshot("snapshot.txt"); break;
        case 5:
            load_snapshot("snapshot.txt");
            show_table();
            break;
        case 6: clear_shared_memory(); break;
        case 7: return 0;
        default: std::cout << "Invalid choice.\n";
        }
    }
}
----

Run the sample, and verify that the saved file persists after shared memory has been cleared.

[source,text]
----
=== Shared Memory Menu ===
1. Create table  2. Show table  3. Add user  4. Save snapshot  5. Load snapshot  6. Clear shared memory  7. Exit:1
Shared memory table created with 3 initial users.
User Table:
  ID: 1, Name: User1
  ID: 2, Name: User2
  ID: 3, Name: User3

=== Shared Memory Menu ===
1. Create table  2. Show table  3. Add user  4. Save snapshot  5. Load snapshot  6. Clear shared memory  7. Exit:3
Enter user name: Nigel
User added.

=== Shared Memory Menu ===
1. Create table  2. Show table  3. Add user  4. Save snapshot  5. Load snapshot  6. Clear shared memory  7. Exit:4
Snapshot saved to snapshot.txt

=== Shared Memory Menu ===
1. Create table  2. Show table  3. Add user  4. Save snapshot  5. Load snapshot  6. Clear shared memory  7. Exit:6
Shared memory cleared.

=== Shared Memory Menu ===
1. Create table  2. Show table  3. Add user  4. Save snapshot  5. Load snapshot  6. Clear shared memory  7. Exit:5
Snapshot loaded from snapshot.txt
User Table:
  ID: 1, Name: User1
  ID: 2, Name: User2
  ID: 3, Name: User3
  ID: 4, Name: Nigel

----

== Next Steps

In the design of a database, consider all the independent processes, and how they might access persistent memory, for example:

image::database-persistent-memory.png[]

Perhaps now consider boost:filesystem[] for file management, and for a heavier duty database engine - integrate boost:asio[] to handle remote database transactions. Referring to the xref:task-networking.adoc[] sample would be a good place to start.

The Boost libraries have a lot to offer this particular scenario!

== See Also

* https://www.boost.org/doc/libs/latest/libs/libraries.htm#Containers[Category: Containers] 
* https://www.boost.org/doc/libs/latest/libs/libraries.htm#Data[Category: Data structures]
* https://www.boost.org/doc/libs/latest/libs/libraries.htm#Memory[Category: Memory]

