Try<Nothing> LevelDBStorage::persist(const Metadata& metadata) { Stopwatch stopwatch; stopwatch.start(); leveldb::WriteOptions options; options.sync = true; Record record; record.set_type(Record::METADATA); record.mutable_metadata()->CopyFrom(metadata); string value; if (!record.SerializeToString(&value)) { return Error("Failed to serialize record"); } leveldb::Status status = db->Put(options, encode(0, false), value); if (!status.ok()) { return Error(status.ToString()); } LOG(INFO) << "Persisting metadata (" << value.size() << " bytes) to leveldb took " << stopwatch.elapsed(); return Nothing(); }
Try<Nothing> LevelDBStorage::persist(const Action& action) { Stopwatch stopwatch; stopwatch.start(); Record record; record.set_type(Record::ACTION); record.mutable_action()->MergeFrom(action); string value; if (!record.SerializeToString(&value)) { return Error("Failed to serialize record"); } leveldb::WriteOptions options; options.sync = true; leveldb::Status status = db->Put(options, encode(action.position()), value); if (!status.ok()) { return Error(status.ToString()); } // Updated the first position. Notice that we use 'min' here instead // of checking 'isNone()' because it's likely that log entries are // written out of order during catch-up (e.g. if a random bulk // catch-up policy is used). first = min(first, action.position()); LOG(INFO) << "Persisting action (" << value.size() << " bytes) to leveldb took " << stopwatch.elapsed(); // Delete positions if a truncate action has been *learned*. Note // that we do this in a best-effort fashion (i.e., we ignore any // failures to the database since we can always try again). if (action.has_type() && action.type() == Action::TRUNCATE && action.has_learned() && action.learned()) { CHECK(action.has_truncate()); stopwatch.start(); // Restart the stopwatch. // To actually perform the truncation in leveldb we need to remove // all the keys that represent positions no longer in the log. We // do this by attempting to delete all keys that represent the // first position we know is still in leveldb up to (but // excluding) the truncate position. Note that this works because // the semantics of WriteBatch are such that even if the position // doesn't exist (which is possible because this replica has some // holes), we can attempt to delete the key that represents it and // it will just ignore that key. This is *much* cheaper than // actually iterating through the entire database instead (which // was, for posterity, the original implementation). In addition, // caching the "first" position we know is in the database is // cheaper than using an iterator to determine the first position // (which was, for posterity, the second implementation). leveldb::WriteBatch batch; CHECK_SOME(first); // Add positions up to (but excluding) the truncate position to // the batch starting at the first position still in leveldb. It's // likely that the first position is greater than the truncate // position (e.g., during catch-up). In that case, we do nothing // because there is nothing we can truncate. // TODO(jieyu): We might miss a truncation if we do random (i.e., // out of order) bulk catch-up and the truncate operation is // caught up first. uint64_t index = 0; while ((first.get() + index) < action.truncate().to()) { batch.Delete(encode(first.get() + index)); index++; } // If we added any positions, attempt to delete them! if (index > 0) { // We do this write asynchronously (e.g., using default options). leveldb::Status status = db->Write(leveldb::WriteOptions(), &batch); if (!status.ok()) { LOG(WARNING) << "Ignoring leveldb batch delete failure: " << status.ToString(); } else { // Save the new first position! CHECK_LT(first.get(), action.truncate().to()); first = action.truncate().to(); LOG(INFO) << "Deleting ~" << index << " keys from leveldb took " << stopwatch.elapsed(); } } } return Nothing(); }