Commit ab997839 authored by pavely's avatar pavely Committed by Commit bot

Add per attachment metadata records.

Currently metadata record only contains size and it is not used anywhere.
In the future size will be used for storage management by datatypes.

Next step is to add crc to metadata record.

BUG=424304
R=maniscalco@chromium.org

Review URL: https://codereview.chromium.org/690723004

Cr-Commit-Position: refs/heads/master@{#302166}
parent b8cf2351
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
#include "third_party/leveldatabase/src/include/leveldb/options.h" #include "third_party/leveldatabase/src/include/leveldb/options.h"
#include "third_party/leveldatabase/src/include/leveldb/slice.h" #include "third_party/leveldatabase/src/include/leveldb/slice.h"
#include "third_party/leveldatabase/src/include/leveldb/status.h" #include "third_party/leveldatabase/src/include/leveldb/status.h"
#include "third_party/leveldatabase/src/include/leveldb/write_batch.h"
namespace syncer { namespace syncer {
...@@ -23,6 +24,9 @@ namespace { ...@@ -23,6 +24,9 @@ namespace {
// Prefix for records containing attachment data. // Prefix for records containing attachment data.
const char kDataPrefix[] = "data-"; const char kDataPrefix[] = "data-";
// Prefix for records containing attachment metadata.
const char kMetadataPrefix[] = "metadata-";
const char kDatabaseMetadataKey[] = "database-metadata"; const char kDatabaseMetadataKey[] = "database-metadata";
const int32 kCurrentSchemaVersion = 1; const int32 kCurrentSchemaVersion = 1;
...@@ -36,16 +40,29 @@ leveldb::WriteOptions MakeWriteOptions() { ...@@ -36,16 +40,29 @@ leveldb::WriteOptions MakeWriteOptions() {
return write_options; return write_options;
} }
leveldb::Status ReadStoreMetadata( leveldb::ReadOptions MakeDataReadOptions() {
leveldb::DB* db,
attachment_store_pb::AttachmentStoreMetadata* metadata) {
std::string data_str;
leveldb::ReadOptions read_options; leveldb::ReadOptions read_options;
// Attachment content is typically large and only read once. Don't cache it on
// db level.
read_options.fill_cache = false; read_options.fill_cache = false;
read_options.verify_checksums = true; read_options.verify_checksums = true;
return read_options;
}
leveldb::ReadOptions MakeMetadataReadOptions() {
leveldb::ReadOptions read_options;
read_options.fill_cache = true;
read_options.verify_checksums = true;
return read_options;
}
leveldb::Status ReadStoreMetadata(
leveldb::DB* db,
attachment_store_pb::StoreMetadata* metadata) {
std::string data_str;
leveldb::Status status = leveldb::Status status =
db->Get(read_options, kDatabaseMetadataKey, &data_str); db->Get(MakeMetadataReadOptions(), kDatabaseMetadataKey, &data_str);
if (!status.ok()) if (!status.ok())
return status; return status;
if (!metadata->ParseFromString(data_str)) if (!metadata->ParseFromString(data_str))
...@@ -55,7 +72,7 @@ leveldb::Status ReadStoreMetadata( ...@@ -55,7 +72,7 @@ leveldb::Status ReadStoreMetadata(
leveldb::Status WriteStoreMetadata( leveldb::Status WriteStoreMetadata(
leveldb::DB* db, leveldb::DB* db,
const attachment_store_pb::AttachmentStoreMetadata& metadata) { const attachment_store_pb::StoreMetadata& metadata) {
std::string data_str; std::string data_str;
metadata.SerializeToString(&data_str); metadata.SerializeToString(&data_str);
...@@ -79,27 +96,15 @@ void OnDiskAttachmentStore::Read(const AttachmentIdList& ids, ...@@ -79,27 +96,15 @@ void OnDiskAttachmentStore::Read(const AttachmentIdList& ids,
scoped_ptr<AttachmentMap> result_map(new AttachmentMap()); scoped_ptr<AttachmentMap> result_map(new AttachmentMap());
scoped_ptr<AttachmentIdList> unavailable_attachments(new AttachmentIdList()); scoped_ptr<AttachmentIdList> unavailable_attachments(new AttachmentIdList());
leveldb::ReadOptions read_options;
// Attachment content is typically large and only read once. Don't cache it on
// db level.
read_options.fill_cache = false;
read_options.verify_checksums = true;
AttachmentIdList::const_iterator iter = ids.begin(); AttachmentIdList::const_iterator iter = ids.begin();
const AttachmentIdList::const_iterator end = ids.end(); const AttachmentIdList::const_iterator end = ids.end();
for (; iter != end; ++iter) { for (; iter != end; ++iter) {
const std::string key = MakeDataKeyFromAttachmentId(*iter); scoped_ptr<Attachment> attachment = ReadSingleAttachment(*iter);
std::string data_str; if (attachment) {
leveldb::Status status = db_->Get(read_options, key, &data_str); result_map->insert(std::make_pair(*iter, *attachment));
if (!status.ok()) { } else {
DVLOG(1) << "DB::Get failed: status=" << status.ToString();
unavailable_attachments->push_back(*iter); unavailable_attachments->push_back(*iter);
continue;
} }
scoped_refptr<base::RefCountedMemory> data =
base::RefCountedString::TakeString(&data_str);
Attachment attachment = Attachment::CreateWithId(*iter, data);
result_map->insert(std::make_pair(*iter, attachment));
} }
Result result_code = Result result_code =
...@@ -118,40 +123,11 @@ void OnDiskAttachmentStore::Write(const AttachmentList& attachments, ...@@ -118,40 +123,11 @@ void OnDiskAttachmentStore::Write(const AttachmentList& attachments,
DCHECK(db_); DCHECK(db_);
Result result_code = SUCCESS; Result result_code = SUCCESS;
leveldb::ReadOptions read_options;
read_options.fill_cache = false;
read_options.verify_checksums = true;
leveldb::WriteOptions write_options = MakeWriteOptions();
AttachmentList::const_iterator iter = attachments.begin(); AttachmentList::const_iterator iter = attachments.begin();
const AttachmentList::const_iterator end = attachments.end(); const AttachmentList::const_iterator end = attachments.end();
for (; iter != end; ++iter) { for (; iter != end; ++iter) {
const std::string key = MakeDataKeyFromAttachmentId(iter->GetId()); if (!WriteSingleAttachment(*iter))
std::string data_str;
// TODO(pavely): crbug/424304 This read is expensive. When I add metadata
// records this read will target metadata record instead of payload record.
leveldb::Status status = db_->Get(read_options, key, &data_str);
if (status.ok()) {
// Entry exists, don't overwrite.
continue;
} else if (!status.IsNotFound()) {
// Entry exists but failed to read.
DVLOG(1) << "DB::Get failed: status=" << status.ToString();
result_code = UNSPECIFIED_ERROR; result_code = UNSPECIFIED_ERROR;
continue;
}
DCHECK(status.IsNotFound());
scoped_refptr<base::RefCountedMemory> data = iter->GetData();
leveldb::Slice data_slice(data->front_as<char>(), data->size());
status = db_->Put(write_options, key, data_slice);
if (!status.ok()) {
// Failed to write.
DVLOG(1) << "DB::Put failed: status=" << status.ToString();
result_code = UNSPECIFIED_ERROR;
}
} }
callback_task_runner_->PostTask(FROM_HERE, base::Bind(callback, result_code)); callback_task_runner_->PostTask(FROM_HERE, base::Bind(callback, result_code));
} }
...@@ -165,12 +141,15 @@ void OnDiskAttachmentStore::Drop(const AttachmentIdList& ids, ...@@ -165,12 +141,15 @@ void OnDiskAttachmentStore::Drop(const AttachmentIdList& ids,
AttachmentIdList::const_iterator iter = ids.begin(); AttachmentIdList::const_iterator iter = ids.begin();
const AttachmentIdList::const_iterator end = ids.end(); const AttachmentIdList::const_iterator end = ids.end();
for (; iter != end; ++iter) { for (; iter != end; ++iter) {
const std::string key = MakeDataKeyFromAttachmentId(*iter); leveldb::WriteBatch write_batch;
leveldb::Status status = db_->Delete(write_options, key); write_batch.Delete(MakeDataKeyFromAttachmentId(*iter));
write_batch.Delete(MakeMetadataKeyFromAttachmentId(*iter));
leveldb::Status status = db_->Write(write_options, &write_batch);
if (!status.ok()) { if (!status.ok()) {
// DB::Delete doesn't check if record exists, it returns ok just like // DB::Delete doesn't check if record exists, it returns ok just like
// AttachmentStore::Drop should. // AttachmentStore::Drop should.
DVLOG(1) << "DB::Delete failed: status=" << status.ToString(); DVLOG(1) << "DB::Write failed: status=" << status.ToString();
result_code = UNSPECIFIED_ERROR; result_code = UNSPECIFIED_ERROR;
} }
} }
...@@ -199,7 +178,7 @@ AttachmentStore::Result OnDiskAttachmentStore::OpenOrCreate( ...@@ -199,7 +178,7 @@ AttachmentStore::Result OnDiskAttachmentStore::OpenOrCreate(
db.reset(db_raw); db.reset(db_raw);
attachment_store_pb::AttachmentStoreMetadata metadata; attachment_store_pb::StoreMetadata metadata;
status = ReadStoreMetadata(db.get(), &metadata); status = ReadStoreMetadata(db.get(), &metadata);
if (!status.ok() && !status.IsNotFound()) { if (!status.ok() && !status.IsNotFound()) {
DVLOG(1) << "ReadStoreMetadata failed: status=" << status.ToString(); DVLOG(1) << "ReadStoreMetadata failed: status=" << status.ToString();
...@@ -227,10 +206,73 @@ AttachmentStore::Result OnDiskAttachmentStore::OpenOrCreate( ...@@ -227,10 +206,73 @@ AttachmentStore::Result OnDiskAttachmentStore::OpenOrCreate(
return SUCCESS; return SUCCESS;
} }
scoped_ptr<Attachment> OnDiskAttachmentStore::ReadSingleAttachment(
const AttachmentId& attachment_id) {
scoped_ptr<Attachment> attachment;
const std::string key = MakeDataKeyFromAttachmentId(attachment_id);
std::string data_str;
leveldb::Status status = db_->Get(MakeDataReadOptions(), key, &data_str);
if (status.ok()) {
scoped_refptr<base::RefCountedMemory> data =
base::RefCountedString::TakeString(&data_str);
attachment.reset(
new Attachment(Attachment::CreateWithId(attachment_id, data)));
} else {
DVLOG(1) << "DB::Get failed: status=" << status.ToString();
}
return attachment.Pass();
}
bool OnDiskAttachmentStore::WriteSingleAttachment(
const Attachment& attachment) {
const std::string metadata_key =
MakeMetadataKeyFromAttachmentId(attachment.GetId());
const std::string data_key = MakeDataKeyFromAttachmentId(attachment.GetId());
std::string metadata_str;
leveldb::Status status =
db_->Get(MakeMetadataReadOptions(), metadata_key, &metadata_str);
if (status.ok()) {
// Entry exists, don't overwrite.
return true;
} else if (!status.IsNotFound()) {
// Entry exists but failed to read.
DVLOG(1) << "DB::Get failed: status=" << status.ToString();
return false;
}
DCHECK(status.IsNotFound());
leveldb::WriteBatch write_batch;
// Write metadata.
attachment_store_pb::RecordMetadata metadata;
metadata.set_attachment_size(attachment.GetData()->size());
metadata_str = metadata.SerializeAsString();
write_batch.Put(metadata_key, metadata_str);
// Write data.
scoped_refptr<base::RefCountedMemory> data = attachment.GetData();
leveldb::Slice data_slice(data->front_as<char>(), data->size());
write_batch.Put(data_key, data_slice);
status = db_->Write(MakeWriteOptions(), &write_batch);
if (!status.ok()) {
// Failed to write.
DVLOG(1) << "DB::Write failed: status=" << status.ToString();
return false;
}
return true;
}
std::string OnDiskAttachmentStore::MakeDataKeyFromAttachmentId( std::string OnDiskAttachmentStore::MakeDataKeyFromAttachmentId(
const AttachmentId& attachment_id) { const AttachmentId& attachment_id) {
std::string key = kDataPrefix + attachment_id.GetProto().unique_id(); std::string key = kDataPrefix + attachment_id.GetProto().unique_id();
return key; return key;
} }
std::string OnDiskAttachmentStore::MakeMetadataKeyFromAttachmentId(
const AttachmentId& attachment_id) {
std::string key = kMetadataPrefix + attachment_id.GetProto().unique_id();
return key;
}
} // namespace syncer } // namespace syncer
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
#include "sync/api/attachments/attachment_store.h" #include "sync/internal_api/public/attachments/on_disk_attachment_store.h"
#include "base/files/file_path.h" #include "base/files/file_path.h"
#include "base/files/file_util.h" #include "base/files/file_util.h"
...@@ -109,6 +109,28 @@ class OnDiskAttachmentStoreSpecificTest : public testing::Test { ...@@ -109,6 +109,28 @@ class OnDiskAttachmentStoreSpecificTest : public testing::Test {
return content; return content;
} }
void VerifyAttachmentRecordsPresent(const base::FilePath& path,
const AttachmentId& attachment_id,
bool expect_records_present) {
scoped_ptr<leveldb::DB> db = OpenLevelDB(path);
std::string metadata_key =
OnDiskAttachmentStore::MakeMetadataKeyFromAttachmentId(attachment_id);
std::string data_key =
OnDiskAttachmentStore::MakeDataKeyFromAttachmentId(attachment_id);
std::string data;
leveldb::Status s = db->Get(leveldb::ReadOptions(), data_key, &data);
if (expect_records_present)
EXPECT_TRUE(s.ok());
else
EXPECT_TRUE(s.IsNotFound());
s = db->Get(leveldb::ReadOptions(), metadata_key, &data);
if (expect_records_present)
EXPECT_TRUE(s.ok());
else
EXPECT_TRUE(s.IsNotFound());
}
void RunLoop() { void RunLoop() {
base::RunLoop run_loop; base::RunLoop run_loop;
run_loop.RunUntilIdle(); run_loop.RunUntilIdle();
...@@ -211,7 +233,7 @@ TEST_F(OnDiskAttachmentStoreSpecificTest, StoreMetadata) { ...@@ -211,7 +233,7 @@ TEST_F(OnDiskAttachmentStoreSpecificTest, StoreMetadata) {
// AttachmentStore should create metadata record. // AttachmentStore should create metadata record.
std::string data = ReadStoreMetadataRecord(db_path); std::string data = ReadStoreMetadataRecord(db_path);
attachment_store_pb::AttachmentStoreMetadata metadata; attachment_store_pb::StoreMetadata metadata;
EXPECT_TRUE(metadata.ParseFromString(data)); EXPECT_TRUE(metadata.ParseFromString(data));
EXPECT_EQ(metadata.schema_version(), 1); EXPECT_EQ(metadata.schema_version(), 1);
...@@ -244,4 +266,51 @@ TEST_F(OnDiskAttachmentStoreSpecificTest, StoreMetadata) { ...@@ -244,4 +266,51 @@ TEST_F(OnDiskAttachmentStoreSpecificTest, StoreMetadata) {
EXPECT_EQ(store_.get(), nullptr); EXPECT_EQ(store_.get(), nullptr);
} }
TEST_F(OnDiskAttachmentStoreSpecificTest, RecordMetadata) {
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
base::FilePath db_path =
temp_dir_.path().Append(FILE_PATH_LITERAL("leveldb"));
// Create attachment store.
AttachmentStore::Result result = AttachmentStore::UNSPECIFIED_ERROR;
AttachmentStore::CreateOnDiskStore(
temp_dir_.path(),
base::ThreadTaskRunnerHandle::Get(),
base::Bind(&AttachmentStoreCreated, &store_, &result));
RunLoop();
EXPECT_EQ(result, AttachmentStore::SUCCESS);
// Write two attachments.
std::string some_data;
AttachmentList attachments;
some_data = "data1";
attachments.push_back(
Attachment::Create(base::RefCountedString::TakeString(&some_data)));
some_data = "data2";
attachments.push_back(
Attachment::Create(base::RefCountedString::TakeString(&some_data)));
store_->Write(attachments,
base::Bind(&OnDiskAttachmentStoreSpecificTest::CopyResult,
base::Unretained(this),
&result));
RunLoop();
EXPECT_EQ(result, AttachmentStore::SUCCESS);
// Delete one of written attachments.
AttachmentIdList attachment_ids;
attachment_ids.push_back(attachments[0].GetId());
store_->Drop(attachment_ids,
base::Bind(&OnDiskAttachmentStoreSpecificTest::CopyResult,
base::Unretained(this),
&result));
RunLoop();
EXPECT_EQ(result, AttachmentStore::SUCCESS);
store_ = nullptr;
RunLoop();
// Verify that attachment store contains only records for second attachment.
VerifyAttachmentRecordsPresent(db_path, attachments[0].GetId(), false);
VerifyAttachmentRecordsPresent(db_path, attachments[1].GetId(), true);
}
} // namespace syncer } // namespace syncer
...@@ -10,9 +10,18 @@ option retain_unknown_fields = true; ...@@ -10,9 +10,18 @@ option retain_unknown_fields = true;
package attachment_store_pb; package attachment_store_pb;
// Metadata for leveldb attachment store database. // Metadata for leveldb attachment store database.
message AttachmentStoreMetadata { message StoreMetadata {
// |schema_version| indicates format in which data is written in attachment // |schema_version| indicates format in which data is written in attachment
// store. Needed for upgrade and to prevent newer data from being loaded by // store. Needed for upgrade and to prevent newer data from being loaded by
// older code that doesn't understand it. // older code that doesn't understand it.
optional int32 schema_version = 1; optional int32 schema_version = 1;
} }
// Metadata for attachment in attachment store. Storing metadata in separate
// record from actual data allows us to enumerate attachments in the store
// without incurring cost to read actual data. It also allows us to update
// attachment metadata independent of the data.
message RecordMetadata {
// Size of attachment data. Useful for attachment store space management.
optional int64 attachment_size = 1;
}
...@@ -47,7 +47,18 @@ class SYNC_EXPORT OnDiskAttachmentStore : public AttachmentStoreBase, ...@@ -47,7 +47,18 @@ class SYNC_EXPORT OnDiskAttachmentStore : public AttachmentStoreBase,
Result OpenOrCreate(const base::FilePath& path); Result OpenOrCreate(const base::FilePath& path);
private: private:
std::string MakeDataKeyFromAttachmentId(const AttachmentId& attachment_id); friend class OnDiskAttachmentStoreSpecificTest;
// Reads single attachment from store. Returns nullptr in case of errors.
scoped_ptr<Attachment> ReadSingleAttachment(
const AttachmentId& attachment_id);
// Writes single attachment to store. Returns false in case of errors.
bool WriteSingleAttachment(const Attachment& attachment);
static std::string MakeDataKeyFromAttachmentId(
const AttachmentId& attachment_id);
static std::string MakeMetadataKeyFromAttachmentId(
const AttachmentId& attachment_id);
scoped_refptr<base::SequencedTaskRunner> callback_task_runner_; scoped_refptr<base::SequencedTaskRunner> callback_task_runner_;
scoped_ptr<leveldb::DB> db_; scoped_ptr<leveldb::DB> db_;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment