【问题标题】:LMDB: How to get back to a valid state after an MDB_MAP_FULL errorLMDB:如何在 MDB_MAP_FULL 错误后恢复到有效状态
【发布时间】:2019-08-14 08:34:54
【问题描述】:

我正在使用 LMDB 在我的应用程序中记录时间序列数据。条目会定期或在特定事件中添加到数据库中。我正在添加清除旧条目的机制,以避免数据库不成比例地增长。但是,我希望能够处理在执行任何清理之前达到地图大小限制的情况 (MDB_MAP_FULL)。

我的问题是,一旦达到 MDB_MAP_FULL,在删除条目时也会出现 MDB_MAP_FULL 错误。也就是说,我无法通过从数据库中删除条目来恢复到有效状态。

在获得 MDB_MAP_FULL 后增加环境映射大小似乎可以解决问题,但我不想每次达到大小限制时都必须增加映射大小。此外,这会导致线程安全问题,因为如果同一进程中有活动事务,则无法调用 mdb_env_set_mapsize。

有没有办法在不增加地图大小的情况下恢复到有效状态?我在代码中做错了什么使数据库处于无效状态吗?

以下代码 sn-p 重现了我的问题。我将条目添加到数据库中,直到出现 MDB_MAP_FULL 错误。然后,我尝试从数据库中删除所有条目。获得 MDB_MAP_FULL 后,我可以删除一些条目,但最终我会再次获得 MDB_MAP_FULL。

    // Create environment
    MDB_env* env;
    int ec = mdb_env_create(&env);
    if (ec != MDB_SUCCESS) {
        throw Lmdb::LmdbError("Unable to create database environment", ec);
    }

    // Set mapsize
    size_t maxSizeInBytes = 20*boost::interprocess::mapped_region::get_page_size();
    ec = mdb_env_set_mapsize(env, maxSizeInBytes);
    if (ec != MDB_SUCCESS) {
        throw Lmdb::LmdbError("Unable to configure database environment memory map size", ec);
    }

    // Open environment
    ec = mdb_env_open(env, dbPath.c_str(), 0, 0644);
    if (ec != MDB_SUCCESS) {
        throw Lmdb::LmdbError("Unable to open database environment", ec);
    }

    // Open database
    MDB_dbi dbi;
    {
        MDB_txn* txn;
        ec = mdb_txn_begin(env, nullptr, 0, &txn);
        if (ec != MDB_SUCCESS) {
            throw Lmdb::LmdbError("Unable to start database transaction", ec);
        }
        ec = mdb_dbi_open(txn, nullptr, MDB_INTEGERKEY, &dbi);
        if (ec != MDB_SUCCESS) {
            throw Lmdb::LmdbError("Unable to open database", ec);
        }
        ec = mdb_txn_commit(txn);
        if (ec != MDB_SUCCESS) {
            throw Lmdb::LmdbError("Unable to commit database transaction", ec);
        }
    }

    // Fill DB
    size_t elementsAdded = 0;
    size_t maxCount = 1000000; // Set limit to ensure test does not hang in case of some error
    for (size_t i = 0; i < maxCount; i++) {
        MDB_txn* txn;
        ec = mdb_txn_begin(env, nullptr, 0, &txn);
        if (ec != MDB_SUCCESS) {
            throw Lmdb::LmdbError("Unable to start database transaction", ec);
        }

        int value = std::rand();
        MDB_val db_key{sizeof(size_t), (void*)(&i)};
        MDB_val db_data{sizeof(int), (void*)(&value)};
        ec = mdb_put(txn, dbi, &db_key, &db_data, 0);
        if (ec != MDB_SUCCESS) {
            mdb_txn_abort(txn);
            throw Lmdb::LmdbError("Unable to add database entry", ec);
        }

        ec = mdb_txn_commit(txn);
        if (ec == MDB_MAP_FULL) {
            elementsAdded = i;
            SqPrintMessage("Reached max size on put commit at index: %d", i);
            break;
        } else if (ec != MDB_SUCCESS) {
            throw Lmdb::LmdbError("Unable to commit database transaction", ec);
        }
    }

    // Attempt deleting entries
    for (size_t i = 0; i < elementsAdded; i++) {
        MDB_txn* txn;
        ec = mdb_txn_begin(env, nullptr, 0, &txn);
        if (ec != MDB_SUCCESS) {
            throw Lmdb::LmdbError("Unable to start database transaction", ec);
        }

        MDB_val db_key{sizeof(size_t), (void*)(&i)};
        ec = mdb_del(txn, dbi, &db_key, nullptr);
        if (ec != MDB_SUCCESS) {
            mdb_txn_abort(txn);
            throw Lmdb::LmdbError("Unable to delete database entry", ec);
        }

        ec = mdb_txn_commit(txn);
        if (ec == MDB_MAP_FULL) {
            SqPrintMessage("Reached max size on delete commit at index: %d", i);
            break;
        } else if (ec != MDB_SUCCESS) {
            throw Lmdb::LmdbError("Unable to commit database transaction", ec);
        }
    }

    // Close environment
    mdb_env_close(env);

输出:

Reached max size on put commit at index: 1657
Reached max size on delete commit at index: 137

添加

    ec = mdb_env_set_mapsize(env, maxSizeInBytes + 2*boost::interprocess::mapped_region::get_page_size());
    if (ec != MDB_SUCCESS) {
        throw Lmdb::LmdbError("Unable to configure database environment memory map size", ec);
    }

在第一个循环之后,删除元素时我没有得到 MDB_MAP_FULL。

【问题讨论】:

    标签: lmdb


    【解决方案1】:

    我不确定您在删除记录后得到 MDB_MAP_FULL 的确切原因,但我的猜测是 LMDB 正在尝试分配页面以维护其“空闲列表”数据结构(这是未使用页面的列表本身存储在 LMDB DB 中)。如果它无法获取存储空闲列表条目所需的连续页面,它将尝试通过扩展地图来获取它们。

    正如您所提到的,在不停止正在运行的系统的情况下增加数据库的映射大小可能很困难,因此通常最好从非常大的映射大小开始。幸运的是,在大多数系统上(可能除了 Windows?)这实际上并不会创建这种大小的文件,因此通常应用程序会将地图大小设置为一个非常大的值(100GB 或更大)。

    【讨论】:

    • 感谢您的回答!我认为您对 LMDB 需要更多映射空间来删除​​条目是正确的。如果文档对此更清楚,那就太好了。
    • 正如您所说,似乎一般建议是设置足够大的地图大小。我想我的问题是我想涵盖清理无法按预期工作或太慢的情况,从而导致数据库占用比我想要的更多的空间。我当前的解决方案是添加我自己的截止值,检查每个写入事务的数据库条目计数。这似乎工作正常,但如果我可以直接使用 MDB_MAP_FULL 这样做会更好。
    • 据我所知,LMDB 并不是真正设计为按照您描述的方式由用户自行压缩。它总是假设它可以在需要时获得新的页面。但是,如果您确实让它像那样工作,那么请告诉我们。祝你好运!
    猜你喜欢
    • 2016-04-07
    • 2021-09-09
    • 2014-12-15
    • 2022-09-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-25
    相关资源
    最近更新 更多