Try<Action> LevelDBStorage::read(uint64_t position) { Stopwatch stopwatch; stopwatch.start(); leveldb::ReadOptions options; string value; leveldb::Status status = db->Get(options, encode(position), &value); if (!status.ok()) { return Error(status.ToString()); } google::protobuf::io::ArrayInputStream stream(value.data(), value.size()); Record record; if (!record.ParseFromZeroCopyStream(&stream)) { return Error("Failed to deserialize record"); } if (record.type() != Record::ACTION) { return Error("Bad record"); } LOG(INFO) << "Reading position from leveldb took " << stopwatch.elapsed(); return record.action(); }
Try<Storage::State> LevelDBStorage::restore(const string& path) { leveldb::Options options; options.create_if_missing = true; // TODO(benh): Can't use varint comparator until bug discussed at // groups.google.com/group/leveldb/browse_thread/thread/17eac39168909ba7 // gets fixed. For now, we are using the default byte-wise // comparator and *assuming* that the encoding from unsigned long to // string produces a stable ordering. Checks below. // options.comparator = &comparator; const string& one = encode(1); const string& two = encode(2); const string& ten = encode(10); CHECK(leveldb::BytewiseComparator()->Compare(one, two) < 0); CHECK(leveldb::BytewiseComparator()->Compare(two, one) > 0); CHECK(leveldb::BytewiseComparator()->Compare(one, ten) < 0); CHECK(leveldb::BytewiseComparator()->Compare(ten, two) > 0); CHECK(leveldb::BytewiseComparator()->Compare(ten, ten) == 0); Stopwatch stopwatch; stopwatch.start(); leveldb::Status status = leveldb::DB::Open(options, path, &db); if (!status.ok()) { // TODO(benh): Consider trying to repair the DB. return Error(status.ToString()); } LOG(INFO) << "Opened db in " << stopwatch.elapsed(); stopwatch.start(); // Restart the stopwatch. // TODO(benh): Conditionally compact to avoid long recovery times? db->CompactRange(nullptr, nullptr); LOG(INFO) << "Compacted db in " << stopwatch.elapsed(); State state; state.begin = 0; state.end = 0; // TODO(benh): Consider just reading the "promise" record (e.g., // 'encode(0, false)') and then iterating over the rest of the // records and confirming that they are all indeed of type // Record::Action. stopwatch.start(); // Restart the stopwatch. leveldb::Iterator* iterator = db->NewIterator(leveldb::ReadOptions()); LOG(INFO) << "Created db iterator in " << stopwatch.elapsed(); stopwatch.start(); // Restart the stopwatch. iterator->SeekToFirst(); LOG(INFO) << "Seeked to beginning of db in " << stopwatch.elapsed(); stopwatch.start(); // Restart the stopwatch. uint64_t keys = 0; while (iterator->Valid()) { keys++; const leveldb::Slice& slice = iterator->value(); google::protobuf::io::ArrayInputStream stream(slice.data(), slice.size()); Record record; if (!record.ParseFromZeroCopyStream(&stream)) { return Error("Failed to deserialize record"); } switch (record.type()) { case Record::METADATA: { CHECK(record.has_metadata()); state.metadata.CopyFrom(record.metadata()); break; } // DEPRECATED! case Record::PROMISE: { CHECK(record.has_promise()); // This replica is in old format. Set its status to VOTING // since there is no catch-up logic in the old code and this // replica is obviously not empty. state.metadata.set_status(Metadata::VOTING); state.metadata.set_promised(record.promise().proposal()); break; } case Record::ACTION: { CHECK(record.has_action()); const Action& action = record.action(); if (action.has_learned() && action.learned()) { state.learned.insert(action.position()); state.unlearned.erase(action.position()); if (action.has_type() && action.type() == Action::TRUNCATE) { state.begin = std::max(state.begin, action.truncate().to()); } } else { state.learned.erase(action.position()); state.unlearned.insert(action.position()); } state.end = std::max(state.end, action.position()); // Cache the first position in this replica so during a // truncation, we can attempt to delete all positions from the // first position up to the truncate position. Note that this // is not the beginning position of the log, but rather the // first position that remains (i.e., hasn't been deleted) in // leveldb. first = min(first, action.position()); break; } default: { return Error("Bad record"); } } iterator->Next(); } LOG(INFO) << "Iterated through " << keys << " keys in the db in " << stopwatch.elapsed(); delete iterator; return state; }