// record_store_v1_simple_test.cpp /** * Copyright (C) 2014 MongoDB Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * * As a special exception, the copyright holders give permission to link the * code of portions of this program with the OpenSSL library under certain * conditions as described in each individual source file and distribute * linked combinations including the program with the OpenSSL library. You * must comply with the GNU Affero General Public License in all respects for * all of the code used other than as permitted herein. If you modify file(s) * with this exception, you may extend this exception to your version of the * file(s), but you are not obligated to do so. If you do not wish to do so, * delete this exception statement from your version. If you delete this * exception statement from all source files in the program, then also delete * it in the license file. */ #include "mongo/db/storage/mmap_v1/record_store_v1_simple.h" #include "mongo/db/operation_context_noop.h" #include "mongo/db/storage/mmap_v1/extent.h" #include "mongo/db/storage/mmap_v1/record.h" #include "mongo/db/storage/mmap_v1/record_store_v1_test_help.h" #include "mongo/unittest/unittest.h" using namespace mongo; namespace { using std::string; TEST(SimpleRecordStoreV1, quantizeAllocationSpaceSimple) { ASSERT_EQUALS(RecordStoreV1Base::quantizeAllocationSpace(33), 64); ASSERT_EQUALS(RecordStoreV1Base::quantizeAllocationSpace(1000), 1024); ASSERT_EQUALS(RecordStoreV1Base::quantizeAllocationSpace(10001), 16 * 1024); ASSERT_EQUALS(RecordStoreV1Base::quantizeAllocationSpace(100000), 128 * 1024); ASSERT_EQUALS(RecordStoreV1Base::quantizeAllocationSpace(1000001), 1024 * 1024); ASSERT_EQUALS(RecordStoreV1Base::quantizeAllocationSpace(10000000), 10 * 1024 * 1024); ASSERT_EQUALS(RecordStoreV1Base::quantizeAllocationSpace(14 * 1024 * 1024 - 1), 14 * 1024 * 1024); ASSERT_EQUALS(RecordStoreV1Base::quantizeAllocationSpace(14 * 1024 * 1024), 14 * 1024 * 1024); ASSERT_EQUALS(RecordStoreV1Base::quantizeAllocationSpace(14 * 1024 * 1024 + 1), 16 * 1024 * 1024 + 512 * 1024); ASSERT_EQUALS(RecordStoreV1Base::quantizeAllocationSpace(16 * 1024 * 1024 + 512 * 1024), 16 * 1024 * 1024 + 512 * 1024); } TEST(SimpleRecordStoreV1, quantizeAllocationMinMaxBound) { const int maxSize = RecordStoreV1Base::MaxAllowedAllocation; ASSERT_EQUALS(RecordStoreV1Base::quantizeAllocationSpace(1), 32); ASSERT_EQUALS(RecordStoreV1Base::quantizeAllocationSpace(maxSize), maxSize); } /** * Tests quantization of sizes around all valid bucket sizes. */ TEST(SimpleRecordStoreV1, quantizeAroundBucketSizes) { for (int bucket = 0; bucket < RecordStoreV1Base::Buckets - 2; bucket++) { const int size = RecordStoreV1Base::bucketSizes[bucket]; const int nextSize = RecordStoreV1Base::bucketSizes[bucket + 1]; // size - 1 is quantized to size. ASSERT_EQUALS(size, RecordStoreV1Base::quantizeAllocationSpace(size - 1)); // size is quantized to size. ASSERT_EQUALS(size, RecordStoreV1Base::quantizeAllocationSpace(size)); // size + 1 is quantized to nextSize (if it is a valid allocation) if (size + 1 <= RecordStoreV1Base::MaxAllowedAllocation) { ASSERT_EQUALS(nextSize, RecordStoreV1Base::quantizeAllocationSpace(size + 1)); } } } BSONObj docForRecordSize(int size) { BSONObjBuilder b; b.append("_id", 5); b.append("x", string(size - MmapV1RecordHeader::HeaderSize - 22, 'x')); BSONObj x = b.obj(); ASSERT_EQUALS(MmapV1RecordHeader::HeaderSize + x.objsize(), size); return x; } class BsonDocWriter final : public DocWriter { public: BsonDocWriter(const BSONObj& obj, bool padding) : _obj(obj), _padding(padding) {} virtual void writeDocument(char* buf) const { memcpy(buf, _obj.objdata(), _obj.objsize()); } virtual size_t documentSize() const { return _obj.objsize(); } virtual bool addPadding() const { return _padding; } private: BSONObj _obj; bool _padding; }; /** alloc() quantizes the requested size using quantizeAllocationSpace() rules. */ TEST(SimpleRecordStoreV1, AllocQuantized) { OperationContextNoop txn; DummyExtentManager em; DummyRecordStoreV1MetaData* md = new DummyRecordStoreV1MetaData(false, 0); string myns = "test.AllocQuantized"; SimpleRecordStoreV1 rs(&txn, myns, md, &em, false); BSONObj obj = docForRecordSize(300); StatusWith result = rs.insertRecord(&txn, obj.objdata(), obj.objsize(), false); ASSERT(result.isOK()); // The length of the allocated record is quantized. ASSERT_EQUALS(512, rs.dataFor(&txn, result.getValue()).size() + MmapV1RecordHeader::HeaderSize); } TEST(SimpleRecordStoreV1, AllocNonQuantized) { OperationContextNoop txn; DummyExtentManager em; DummyRecordStoreV1MetaData* md = new DummyRecordStoreV1MetaData(false, 0); md->setUserFlag(&txn, CollectionOptions::Flag_NoPadding); string myns = "test.AllocQuantized"; SimpleRecordStoreV1 rs(&txn, myns, md, &em, false); BSONObj obj = docForRecordSize(300); StatusWith result = rs.insertRecord(&txn, obj.objdata(), obj.objsize(), false); ASSERT(result.isOK()); // The length of the allocated record is quantized. ASSERT_EQUALS(300, rs.dataFor(&txn, result.getValue()).size() + MmapV1RecordHeader::HeaderSize); } TEST(SimpleRecordStoreV1, AllocNonQuantizedStillAligned) { OperationContextNoop txn; DummyExtentManager em; DummyRecordStoreV1MetaData* md = new DummyRecordStoreV1MetaData(false, 0); md->setUserFlag(&txn, CollectionOptions::Flag_NoPadding); string myns = "test.AllocQuantized"; SimpleRecordStoreV1 rs(&txn, myns, md, &em, false); BSONObj obj = docForRecordSize(298); StatusWith result = rs.insertRecord(&txn, obj.objdata(), obj.objsize(), false); ASSERT(result.isOK()); // The length of the allocated record is quantized. ASSERT_EQUALS(300, rs.dataFor(&txn, result.getValue()).size() + MmapV1RecordHeader::HeaderSize); } /** alloc() quantizes the requested size if DocWriter::addPadding() returns true. */ TEST(SimpleRecordStoreV1, AllocQuantizedWithDocWriter) { OperationContextNoop txn; DummyExtentManager em; DummyRecordStoreV1MetaData* md = new DummyRecordStoreV1MetaData(false, 0); string myns = "test.AllocQuantized"; SimpleRecordStoreV1 rs(&txn, myns, md, &em, false); BsonDocWriter docWriter(docForRecordSize(300), true); StatusWith result = rs.insertRecordWithDocWriter(&txn, &docWriter); ASSERT(result.isOK()); // The length of the allocated record is quantized. ASSERT_EQUALS(512, rs.dataFor(&txn, result.getValue()).size() + MmapV1RecordHeader::HeaderSize); } /** * alloc() does not quantize records if DocWriter::addPadding() returns false */ TEST(SimpleRecordStoreV1, AllocNonQuantizedDocWriter) { OperationContextNoop txn; DummyExtentManager em; DummyRecordStoreV1MetaData* md = new DummyRecordStoreV1MetaData(false, 0); string myns = "test.AllocIndexNamespaceNotQuantized"; SimpleRecordStoreV1 rs(&txn, myns + "$x", md, &em, false); BsonDocWriter docWriter(docForRecordSize(300), false); StatusWith result = rs.insertRecordWithDocWriter(&txn, &docWriter); ASSERT(result.isOK()); // The length of the allocated record is not quantized. ASSERT_EQUALS(300, rs.dataFor(&txn, result.getValue()).size() + MmapV1RecordHeader::HeaderSize); } /** alloc() aligns record sizes up to 4 bytes even if DocWriter::addPadding returns false. */ TEST(SimpleRecordStoreV1, AllocAlignedDocWriter) { OperationContextNoop txn; DummyExtentManager em; DummyRecordStoreV1MetaData* md = new DummyRecordStoreV1MetaData(false, 0); string myns = "test.AllocIndexNamespaceNotQuantized"; SimpleRecordStoreV1 rs(&txn, myns + "$x", md, &em, false); BsonDocWriter docWriter(docForRecordSize(298), false); StatusWith result = rs.insertRecordWithDocWriter(&txn, &docWriter); ASSERT(result.isOK()); ASSERT_EQUALS(300, rs.dataFor(&txn, result.getValue()).size() + MmapV1RecordHeader::HeaderSize); } /** * alloc() with quantized size doesn't split if enough room left over. */ TEST(SimpleRecordStoreV1, AllocUseQuantizedDeletedRecordWithoutSplit) { OperationContextNoop txn; DummyExtentManager em; DummyRecordStoreV1MetaData* md = new DummyRecordStoreV1MetaData(false, 0); SimpleRecordStoreV1 rs(&txn, "test.foo", md, &em, false); { LocAndSize drecs[] = {{DiskLoc(0, 1000), 512 + 31}, {}}; initializeV1RS(&txn, NULL, drecs, NULL, &em, md); } BsonDocWriter docWriter(docForRecordSize(300), true); StatusWith actualLocation = rs.insertRecordWithDocWriter(&txn, &docWriter); ASSERT_OK(actualLocation.getStatus()); { LocAndSize recs[] = {{DiskLoc(0, 1000), 512 + 31}, {}}; LocAndSize drecs[] = {{}}; assertStateV1RS(&txn, recs, drecs, NULL, &em, md); } } /** * alloc() with quantized size splits if enough room left over. */ TEST(SimpleRecordStoreV1, AllocUseQuantizedDeletedRecordWithSplit) { OperationContextNoop txn; DummyExtentManager em; DummyRecordStoreV1MetaData* md = new DummyRecordStoreV1MetaData(false, 0); SimpleRecordStoreV1 rs(&txn, "test.foo", md, &em, false); { LocAndSize drecs[] = {{DiskLoc(0, 1000), 512 + 32}, {}}; initializeV1RS(&txn, NULL, drecs, NULL, &em, md); } BsonDocWriter docWriter(docForRecordSize(300), true); StatusWith actualLocation = rs.insertRecordWithDocWriter(&txn, &docWriter); ASSERT_OK(actualLocation.getStatus()); { LocAndSize recs[] = {{DiskLoc(0, 1000), 512}, {}}; LocAndSize drecs[] = {{DiskLoc(0, 1512), 32}, {}}; assertStateV1RS(&txn, recs, drecs, NULL, &em, md); } } /** * alloc() with non quantized size doesn't split if enough room left over. */ TEST(SimpleRecordStoreV1, AllocUseNonQuantizedDeletedRecordWithoutSplit) { OperationContextNoop txn; DummyExtentManager em; DummyRecordStoreV1MetaData* md = new DummyRecordStoreV1MetaData(false, 0); SimpleRecordStoreV1 rs(&txn, "test.foo", md, &em, false); { LocAndSize drecs[] = {{DiskLoc(0, 1000), 331}, {}}; initializeV1RS(&txn, NULL, drecs, NULL, &em, md); } BsonDocWriter docWriter(docForRecordSize(300), false); StatusWith actualLocation = rs.insertRecordWithDocWriter(&txn, &docWriter); ASSERT_OK(actualLocation.getStatus()); { LocAndSize recs[] = {{DiskLoc(0, 1000), 331}, {}}; LocAndSize drecs[] = {{}}; assertStateV1RS(&txn, recs, drecs, NULL, &em, md); } } /** * alloc() with non quantized size splits if enough room left over. */ TEST(SimpleRecordStoreV1, AllocUseNonQuantizedDeletedRecordWithSplit) { OperationContextNoop txn; DummyExtentManager em; DummyRecordStoreV1MetaData* md = new DummyRecordStoreV1MetaData(false, 0); SimpleRecordStoreV1 rs(&txn, "test.foo", md, &em, false); { LocAndSize drecs[] = {{DiskLoc(0, 1000), 332}, {}}; initializeV1RS(&txn, NULL, drecs, NULL, &em, md); } BsonDocWriter docWriter(docForRecordSize(300), false); StatusWith actualLocation = rs.insertRecordWithDocWriter(&txn, &docWriter); ASSERT_OK(actualLocation.getStatus()); { LocAndSize recs[] = {{DiskLoc(0, 1000), 300}, {}}; LocAndSize drecs[] = {{DiskLoc(0, 1300), 32}, {}}; assertStateV1RS(&txn, recs, drecs, NULL, &em, md); } } /** * alloc() will use from the legacy grab bag if it can. */ TEST(SimpleRecordStoreV1, GrabBagIsUsed) { OperationContextNoop txn; DummyExtentManager em; DummyRecordStoreV1MetaData* md = new DummyRecordStoreV1MetaData(false, 0); SimpleRecordStoreV1 rs(&txn, "test.foo", md, &em, false); { LocAndSize drecs[] = {{}}; LocAndSize grabBag[] = { {DiskLoc(0, 1000), 4 * 1024 * 1024}, {DiskLoc(1, 1000), 4 * 1024 * 1024}, {}}; initializeV1RS(&txn, NULL, drecs, grabBag, &em, md); } BsonDocWriter docWriter(docForRecordSize(256), false); StatusWith actualLocation = rs.insertRecordWithDocWriter(&txn, &docWriter); ASSERT_OK(actualLocation.getStatus()); { LocAndSize recs[] = {{DiskLoc(0, 1000), 256}, {}}; LocAndSize drecs[] = {{DiskLoc(0, 1256), 4 * 1024 * 1024 - 256}, {}}; LocAndSize grabBag[] = {{DiskLoc(1, 1000), 4 * 1024 * 1024}, {}}; assertStateV1RS(&txn, recs, drecs, grabBag, &em, md); } } /** * alloc() will pull from the legacy grab bag even if it isn't needed. */ TEST(SimpleRecordStoreV1, GrabBagIsPoppedEvenIfUnneeded) { OperationContextNoop txn; DummyExtentManager em; DummyRecordStoreV1MetaData* md = new DummyRecordStoreV1MetaData(false, 0); SimpleRecordStoreV1 rs(&txn, "test.foo", md, &em, false); { LocAndSize drecs[] = {{DiskLoc(0, 1000), 1000}, {}}; LocAndSize grabBag[] = { {DiskLoc(1, 1000), 4 * 1024 * 1024}, {DiskLoc(2, 1000), 4 * 1024 * 1024}, {}}; initializeV1RS(&txn, NULL, drecs, grabBag, &em, md); } BsonDocWriter docWriter(docForRecordSize(1000), false); StatusWith actualLocation = rs.insertRecordWithDocWriter(&txn, &docWriter); ASSERT_OK(actualLocation.getStatus()); { LocAndSize recs[] = {{DiskLoc(0, 1000), 1000}, {}}; LocAndSize drecs[] = {{DiskLoc(1, 1000), 4 * 1024 * 1024}, {}}; LocAndSize grabBag[] = {{DiskLoc(2, 1000), 4 * 1024 * 1024}, {}}; assertStateV1RS(&txn, recs, drecs, grabBag, &em, md); } } /** * alloc() will pull from the legacy grab bag even if it can't be used */ TEST(SimpleRecordStoreV1, GrabBagIsPoppedEvenIfUnusable) { OperationContextNoop txn; DummyExtentManager em; DummyRecordStoreV1MetaData* md = new DummyRecordStoreV1MetaData(false, 0); SimpleRecordStoreV1 rs(&txn, "test.foo", md, &em, false); { LocAndSize drecs[] = {{DiskLoc(0, 1000), 8 * 1024 * 1024}, {}}; LocAndSize grabBag[] = { {DiskLoc(1, 1000), 4 * 1024 * 1024}, {DiskLoc(2, 1000), 4 * 1024 * 1024}, {}}; initializeV1RS(&txn, NULL, drecs, grabBag, &em, md); } BsonDocWriter docWriter(docForRecordSize(8 * 1024 * 1024), false); StatusWith actualLocation = rs.insertRecordWithDocWriter(&txn, &docWriter); ASSERT_OK(actualLocation.getStatus()); { LocAndSize recs[] = {{DiskLoc(0, 1000), 8 * 1024 * 1024}, {}}; LocAndSize drecs[] = {{DiskLoc(1, 1000), 4 * 1024 * 1024}, {}}; LocAndSize grabBag[] = {{DiskLoc(2, 1000), 4 * 1024 * 1024}, {}}; assertStateV1RS(&txn, recs, drecs, grabBag, &em, md); } } // ----------------- TEST(SimpleRecordStoreV1, FullSimple1) { OperationContextNoop txn; DummyExtentManager em; DummyRecordStoreV1MetaData* md = new DummyRecordStoreV1MetaData(false, 0); SimpleRecordStoreV1 rs(&txn, "test.foo", md, &em, false); ASSERT_EQUALS(0, md->numRecords()); StatusWith result = rs.insertRecord(&txn, "abc", 4, 1000); ASSERT_TRUE(result.isOK()); ASSERT_EQUALS(1, md->numRecords()); RecordData recordData = rs.dataFor(&txn, result.getValue()); ASSERT_EQUALS(string("abc"), string(recordData.data())); } // ----------------- TEST(SimpleRecordStoreV1, Truncate) { OperationContextNoop txn; DummyExtentManager em; DummyRecordStoreV1MetaData* md = new DummyRecordStoreV1MetaData(false, 0); SimpleRecordStoreV1 rs(&txn, "test.foo", md, &em, false); { LocAndSize recs[] = {{DiskLoc(0, 1000), 100}, {DiskLoc(0, 1100), 100}, {DiskLoc(0, 1300), 100}, {DiskLoc(2, 1100), 100}, {}}; LocAndSize drecs[] = { {DiskLoc(0, 1200), 100}, {DiskLoc(2, 1000), 100}, {DiskLoc(1, 1000), 1000}, {}}; initializeV1RS(&txn, recs, drecs, NULL, &em, md); ASSERT_EQUALS(em.getExtent(DiskLoc(0, 0))->length, em.minSize()); } rs.truncate(&txn); { LocAndSize recs[] = {{}}; LocAndSize drecs[] = { // One extent filled with a single deleted record. {DiskLoc(0, Extent::HeaderSize()), em.minSize() - Extent::HeaderSize()}, {}}; assertStateV1RS(&txn, recs, drecs, NULL, &em, md); } } }