sst_file_writer.cc 16.5 KB
Newer Older
1
//  Copyright (c) 2011-present, Facebook, Inc.  All rights reserved.
S
Siying Dong 已提交
2 3 4
//  This source code is licensed under both the GPLv2 (found in the
//  COPYING file in the root directory) and Apache 2.0 License
//  (found in the LICENSE.Apache file in the root directory).
5 6 7 8

#include "rocksdb/sst_file_writer.h"

#include <vector>
9

10
#include "db/db_impl/db_impl.h"
11
#include "db/dbformat.h"
12
#include "file/writable_file_writer.h"
13
#include "rocksdb/file_system.h"
14
#include "rocksdb/table.h"
15
#include "table/block_based/block_based_table_builder.h"
16
#include "table/sst_file_writer_collectors.h"
17
#include "test_util/sync_point.h"
18

19
namespace ROCKSDB_NAMESPACE {
20 21 22

const std::string ExternalSstFilePropertyNames::kVersion =
    "rocksdb.external_sst_file.version";
23 24
const std::string ExternalSstFilePropertyNames::kGlobalSeqno =
    "rocksdb.external_sst_file.global_seqno";
25 26


27
const size_t kFadviseTrigger = 1024 * 1024;  // 1MB
28 29

struct SstFileWriter::Rep {
A
Aaron Gao 已提交
30
  Rep(const EnvOptions& _env_options, const Options& options,
31
      Env::IOPriority _io_priority, const Comparator* _user_comparator,
32 33
      ColumnFamilyHandle* _cfh, bool _invalidate_page_cache, bool _skip_filters,
      std::string _db_session_id)
34
      : env_options(_env_options),
A
Aaron Gao 已提交
35
        ioptions(options),
Y
Yi Wu 已提交
36
        mutable_cf_options(options),
37
        io_priority(_io_priority),
38
        internal_comparator(_user_comparator),
39 40
        cfh(_cfh),
        invalidate_page_cache(_invalidate_page_cache),
41 42
        skip_filters(_skip_filters),
        db_session_id(_db_session_id) {}
43 44 45 46

  std::unique_ptr<WritableFileWriter> file_writer;
  std::unique_ptr<TableBuilder> builder;
  EnvOptions env_options;
47
  ImmutableOptions ioptions;
A
Aaron Gao 已提交
48
  MutableCFOptions mutable_cf_options;
49
  Env::IOPriority io_priority;
50 51
  InternalKeyComparator internal_comparator;
  ExternalSstFileInfo file_info;
52
  InternalKey ikey;
53 54
  std::string column_family_name;
  ColumnFamilyHandle* cfh;
55
  // If true, We will give the OS a hint that this file pages is not needed
56
  // every time we write 1MB to the file.
57
  bool invalidate_page_cache;
58
  // The size of the file during the last time we called Fadvise to remove
59
  // cached pages from page cache.
60
  uint64_t last_fadvise_size = 0;
61
  bool skip_filters;
62 63 64
  std::string db_session_id;
  uint64_t next_file_number = 1;

65 66
  Status AddImpl(const Slice& user_key, const Slice& value,
                 ValueType value_type) {
67 68 69 70 71 72 73 74 75 76
    if (!builder) {
      return Status::InvalidArgument("File is not opened");
    }

    if (file_info.num_entries == 0) {
      file_info.smallest_key.assign(user_key.data(), user_key.size());
    } else {
      if (internal_comparator.user_comparator()->Compare(
              user_key, file_info.largest_key) <= 0) {
        // Make sure that keys are added in order
77 78
        return Status::InvalidArgument(
            "Keys must be added in strict ascending order.");
79 80 81
      }
    }

82 83 84 85 86 87 88 89
    assert(value_type == kTypeValue || value_type == kTypeMerge ||
           value_type == kTypeDeletion ||
           value_type == kTypeDeletionWithTimestamp);

    constexpr SequenceNumber sequence_number = 0;

    ikey.Set(user_key, sequence_number, value_type);

90 91 92 93 94 95 96
    builder->Add(ikey.Encode(), value);

    // update file info
    file_info.num_entries++;
    file_info.largest_key.assign(user_key.data(), user_key.size());
    file_info.file_size = builder->FileSize();

97 98
    InvalidatePageCache(false /* closing */).PermitUncheckedError();
    return Status::OK();
99 100
  }

101
  Status Add(const Slice& user_key, const Slice& value, ValueType value_type) {
102
    if (internal_comparator.user_comparator()->timestamp_size() != 0) {
103 104 105 106 107 108 109 110 111 112
      return Status::InvalidArgument("Timestamp size mismatch");
    }

    return AddImpl(user_key, value, value_type);
  }

  Status Add(const Slice& user_key, const Slice& timestamp, const Slice& value,
             ValueType value_type) {
    const size_t timestamp_size = timestamp.size();

113 114
    if (internal_comparator.user_comparator()->timestamp_size() !=
        timestamp_size) {
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
      return Status::InvalidArgument("Timestamp size mismatch");
    }

    const size_t user_key_size = user_key.size();

    if (user_key.data() + user_key_size == timestamp.data()) {
      Slice user_key_with_ts(user_key.data(), user_key_size + timestamp_size);
      return AddImpl(user_key_with_ts, value, value_type);
    }

    std::string user_key_with_ts;
    user_key_with_ts.reserve(user_key_size + timestamp_size);
    user_key_with_ts.append(user_key.data(), user_key_size);
    user_key_with_ts.append(timestamp.data(), timestamp_size);

    return AddImpl(user_key_with_ts, value, value_type);
  }

133
  Status DeleteRangeImpl(const Slice& begin_key, const Slice& end_key) {
134 135 136
    if (!builder) {
      return Status::InvalidArgument("File is not opened");
    }
137 138 139 140 141 142 143 144 145 146 147
    int cmp = internal_comparator.user_comparator()->CompareWithoutTimestamp(
        begin_key, end_key);
    if (cmp > 0) {
      // It's an empty range where endpoints appear mistaken. Don't bother
      // applying it to the DB, and return an error to the user.
      return Status::InvalidArgument("end key comes before start key");
    } else if (cmp == 0) {
      // It's an empty range. Don't bother applying it to the DB.
      return Status::OK();
    }

148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173
    RangeTombstone tombstone(begin_key, end_key, 0 /* Sequence Number */);
    if (file_info.num_range_del_entries == 0) {
      file_info.smallest_range_del_key.assign(tombstone.start_key_.data(),
                                              tombstone.start_key_.size());
      file_info.largest_range_del_key.assign(tombstone.end_key_.data(),
                                             tombstone.end_key_.size());
    } else {
      if (internal_comparator.user_comparator()->Compare(
              tombstone.start_key_, file_info.smallest_range_del_key) < 0) {
        file_info.smallest_range_del_key.assign(tombstone.start_key_.data(),
                                                tombstone.start_key_.size());
      }
      if (internal_comparator.user_comparator()->Compare(
              tombstone.end_key_, file_info.largest_range_del_key) > 0) {
        file_info.largest_range_del_key.assign(tombstone.end_key_.data(),
                                               tombstone.end_key_.size());
      }
    }

    auto ikey_and_end_key = tombstone.Serialize();
    builder->Add(ikey_and_end_key.first.Encode(), ikey_and_end_key.second);

    // update file info
    file_info.num_range_del_entries++;
    file_info.file_size = builder->FileSize();

174 175
    InvalidatePageCache(false /* closing */).PermitUncheckedError();
    return Status::OK();
176 177
  }

178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216
  Status DeleteRange(const Slice& begin_key, const Slice& end_key) {
    if (internal_comparator.user_comparator()->timestamp_size() != 0) {
      return Status::InvalidArgument("Timestamp size mismatch");
    }
    return DeleteRangeImpl(begin_key, end_key);
  }

  // begin_key and end_key should be users keys without timestamp.
  Status DeleteRange(const Slice& begin_key, const Slice& end_key,
                     const Slice& timestamp) {
    const size_t timestamp_size = timestamp.size();

    if (internal_comparator.user_comparator()->timestamp_size() !=
        timestamp_size) {
      return Status::InvalidArgument("Timestamp size mismatch");
    }

    const size_t begin_key_size = begin_key.size();
    const size_t end_key_size = end_key.size();
    if (begin_key.data() + begin_key_size == timestamp.data() ||
        end_key.data() + begin_key_size == timestamp.data()) {
      assert(memcmp(begin_key.data() + begin_key_size,
                    end_key.data() + end_key_size, timestamp_size) == 0);
      Slice begin_key_with_ts(begin_key.data(),
                              begin_key_size + timestamp_size);
      Slice end_key_with_ts(end_key.data(), end_key.size() + timestamp_size);
      return DeleteRangeImpl(begin_key_with_ts, end_key_with_ts);
    }
    std::string begin_key_with_ts;
    begin_key_with_ts.reserve(begin_key_size + timestamp_size);
    begin_key_with_ts.append(begin_key.data(), begin_key_size);
    begin_key_with_ts.append(timestamp.data(), timestamp_size);
    std::string end_key_with_ts;
    end_key_with_ts.reserve(end_key_size + timestamp_size);
    end_key_with_ts.append(end_key.data(), end_key_size);
    end_key_with_ts.append(timestamp.data(), timestamp_size);
    return DeleteRangeImpl(begin_key_with_ts, end_key_with_ts);
  }

217 218
  Status InvalidatePageCache(bool closing) {
    Status s = Status::OK();
219 220
    if (invalidate_page_cache == false) {
      // Fadvise disabled
221
      return s;
222
    }
223
    uint64_t bytes_since_last_fadvise = builder->FileSize() - last_fadvise_size;
224 225 226
    if (bytes_since_last_fadvise > kFadviseTrigger || closing) {
      TEST_SYNC_POINT_CALLBACK("SstFileWriter::Rep::InvalidatePageCache",
                               &(bytes_since_last_fadvise));
227
      // Tell the OS that we don't need this file in page cache
228 229 230 231 232 233
      s = file_writer->InvalidateCache(0, 0);
      if (s.IsNotSupported()) {
        // NotSupported is fine as it could be a file type that doesn't use page
        // cache.
        s = Status::OK();
      }
234 235
      last_fadvise_size = builder->FileSize();
    }
236
    return s;
237
  }
238 239 240
};

SstFileWriter::SstFileWriter(const EnvOptions& env_options,
A
Aaron Gao 已提交
241
                             const Options& options,
242
                             const Comparator* user_comparator,
243
                             ColumnFamilyHandle* column_family,
244
                             bool invalidate_page_cache,
245
                             Env::IOPriority io_priority, bool skip_filters)
246
    : rep_(new Rep(env_options, options, io_priority, user_comparator,
247 248 249 250 251 252 253 254
                   column_family, invalidate_page_cache, skip_filters,
                   DBImpl::GenerateDbSessionId(options.env))) {
  // SstFileWriter is used to create sst files that can be added to database
  // later. Therefore, no real db_id and db_session_id are associated with it.
  // Here we mimic the way db_session_id behaves by getting a db_session_id
  // for each SstFileWriter, and (later below) assign unique file numbers
  // in the table properties. The db_id is set to be "SST Writer" for clarity.

255 256
  rep_->file_info.file_size = 0;
}
257

I
Islam AbdelRahman 已提交
258 259 260 261 262 263 264
SstFileWriter::~SstFileWriter() {
  if (rep_->builder) {
    // User did not call Finish() or Finish() failed, we need to
    // abandon the builder.
    rep_->builder->Abandon();
  }
}
265 266

Status SstFileWriter::Open(const std::string& file_path) {
267
  Rep* r = rep_.get();
268
  Status s;
269
  std::unique_ptr<FSWritableFile> sst_file;
270
  FileOptions cur_file_opts(r->env_options);
271
  s = r->ioptions.env->GetFileSystem()->NewWritableFile(
272
      file_path, cur_file_opts, &sst_file, nullptr);
273 274 275 276
  if (!s.ok()) {
    return s;
  }

277 278
  sst_file->SetIOPriority(r->io_priority);

279
  CompressionType compression_type;
280
  CompressionOptions compression_opts;
281 282 283 284 285
  if (r->mutable_cf_options.bottommost_compression !=
      kDisableCompressionOption) {
    compression_type = r->mutable_cf_options.bottommost_compression;
    if (r->mutable_cf_options.bottommost_compression_opts.enabled) {
      compression_opts = r->mutable_cf_options.bottommost_compression_opts;
286
    } else {
287
      compression_opts = r->mutable_cf_options.compression_opts;
288
    }
289
  } else if (!r->mutable_cf_options.compression_per_level.empty()) {
290
    // Use the compression of the last level if we have per level compression
291
    compression_type = *(r->mutable_cf_options.compression_per_level.rbegin());
292
    compression_opts = r->mutable_cf_options.compression_opts;
293 294
  } else {
    compression_type = r->mutable_cf_options.compression;
295
    compression_opts = r->mutable_cf_options.compression_opts;
296 297
  }

298
  IntTblPropCollectorFactories int_tbl_prop_collector_factories;
299 300

  // SstFileWriter properties collector to add SstFileWriter version.
301
  int_tbl_prop_collector_factories.emplace_back(
302 303
      new SstFileWriterPropertiesCollectorFactory(2 /* version */,
                                                  0 /* global_seqno*/));
304

305 306 307 308 309 310 311 312
  // User collector factories
  auto user_collector_factories =
      r->ioptions.table_properties_collector_factories;
  for (size_t i = 0; i < user_collector_factories.size(); i++) {
    int_tbl_prop_collector_factories.emplace_back(
        new UserKeyTablePropertiesCollectorFactory(
            user_collector_factories[i]));
  }
313
  int unknown_level = -1;
314 315 316 317 318 319 320 321 322 323 324
  uint32_t cf_id;

  if (r->cfh != nullptr) {
    // user explicitly specified that this file will be ingested into cfh,
    // we can persist this information in the file.
    cf_id = r->cfh->GetID();
    r->column_family_name = r->cfh->GetName();
  } else {
    r->column_family_name = "";
    cf_id = TablePropertiesCollectorFactory::Context::kUnknownColumnFamily;
  }
J
Jay Zhuang 已提交
325 326 327

  // TODO: it would be better to set oldest_key_time to be used for getting the
  //  approximate time of ingested keys.
328
  TableBuilderOptions table_builder_options(
329
      r->ioptions, r->mutable_cf_options, r->internal_comparator,
330
      &int_tbl_prop_collector_factories, compression_type, compression_opts,
331
      cf_id, r->column_family_name, unknown_level, false /* is_bottommost */,
J
Jay Zhuang 已提交
332 333 334
      TableFileCreationReason::kMisc, 0 /* oldest_key_time */,
      0 /* file_creation_time */, "SST Writer" /* db_id */, r->db_session_id,
      0 /* target_file_size */, r->next_file_number);
335 336 337 338 339
  // External SST files used to each get a unique session id. Now for
  // slightly better uniqueness probability in constructing cache keys, we
  // assign fake file numbers to each file (into table properties) and keep
  // the same session id for the life of the SstFileWriter.
  r->next_file_number++;
340 341 342
  // XXX: when we can remove skip_filters from the SstFileWriter public API
  // we can remove it from TableBuilderOptions.
  table_builder_options.skip_filters = r->skip_filters;
343
  FileTypeSet tmp_set = r->ioptions.checksum_handoff_file_types;
344
  r->file_writer.reset(new WritableFileWriter(
345 346
      std::move(sst_file), file_path, r->env_options, r->ioptions.clock,
      nullptr /* io_tracer */, nullptr /* stats */, r->ioptions.listeners,
347
      r->ioptions.file_checksum_gen_factory.get(),
348
      tmp_set.Contains(FileType::kTableFile), false));
349 350 351

  // TODO(tec) : If table_factory is using compressed block cache, we will
  // be adding the external sst file blocks into it, which is wasteful.
352
  r->builder.reset(r->ioptions.table_factory->NewTableBuilder(
353
      table_builder_options, r->file_writer.get()));
354

355
  r->file_info = ExternalSstFileInfo();
356
  r->file_info.file_path = file_path;
357
  r->file_info.version = 2;
358 359 360 361
  return s;
}

Status SstFileWriter::Add(const Slice& user_key, const Slice& value) {
362 363
  return rep_->Add(user_key, value, ValueType::kTypeValue);
}
364

365 366 367
Status SstFileWriter::Put(const Slice& user_key, const Slice& value) {
  return rep_->Add(user_key, value, ValueType::kTypeValue);
}
368

369 370 371 372 373
Status SstFileWriter::Put(const Slice& user_key, const Slice& timestamp,
                          const Slice& value) {
  return rep_->Add(user_key, timestamp, value, ValueType::kTypeValue);
}

374 375 376
Status SstFileWriter::Merge(const Slice& user_key, const Slice& value) {
  return rep_->Add(user_key, value, ValueType::kTypeMerge);
}
377

378 379
Status SstFileWriter::Delete(const Slice& user_key) {
  return rep_->Add(user_key, Slice(), ValueType::kTypeDeletion);
380 381
}

382 383 384 385 386
Status SstFileWriter::Delete(const Slice& user_key, const Slice& timestamp) {
  return rep_->Add(user_key, timestamp, Slice(),
                   ValueType::kTypeDeletionWithTimestamp);
}

387 388 389 390 391
Status SstFileWriter::DeleteRange(const Slice& begin_key,
                                  const Slice& end_key) {
  return rep_->DeleteRange(begin_key, end_key);
}

392 393 394 395 396
Status SstFileWriter::DeleteRange(const Slice& begin_key, const Slice& end_key,
                                  const Slice& timestamp) {
  return rep_->DeleteRange(begin_key, end_key, timestamp);
}

397
Status SstFileWriter::Finish(ExternalSstFileInfo* file_info) {
398
  Rep* r = rep_.get();
399 400 401
  if (!r->builder) {
    return Status::InvalidArgument("File is not opened");
  }
402 403
  if (r->file_info.num_entries == 0 &&
      r->file_info.num_range_del_entries == 0) {
404 405
    return Status::InvalidArgument("Cannot create sst file with no entries");
  }
406 407

  Status s = r->builder->Finish();
408 409
  r->file_info.file_size = r->builder->FileSize();

410
  if (s.ok()) {
S
Sagar Vemuri 已提交
411
    s = r->file_writer->Sync(r->ioptions.use_fsync);
412
    r->InvalidatePageCache(true /* closing */).PermitUncheckedError();
413 414 415 416
    if (s.ok()) {
      s = r->file_writer->Close();
    }
  }
417 418 419 420 421
  if (s.ok()) {
    r->file_info.file_checksum = r->file_writer->GetFileChecksum();
    r->file_info.file_checksum_func_name =
        r->file_writer->GetFileChecksumFuncName();
  }
422 423 424 425
  if (!s.ok()) {
    r->ioptions.env->DeleteFile(r->file_info.file_path);
  }

426
  if (file_info != nullptr) {
427 428 429 430 431 432
    *file_info = r->file_info;
  }

  r->builder.reset();
  return s;
}
D
Ding Ma 已提交
433

434
uint64_t SstFileWriter::FileSize() { return rep_->file_info.file_size; }
435

436
}  // namespace ROCKSDB_NAMESPACE