1 //  Copyright (c) 2011-present, Facebook, Inc.  All rights reserved.
2 //  This source code is licensed under both the GPLv2 (found in the
3 //  COPYING file in the root directory) and Apache 2.0 License
4 //  (found in the LICENSE.Apache file in the root directory).
5 
6 #include "rocksdb/utilities/option_change_migration.h"
7 
8 #ifndef ROCKSDB_LITE
9 #include "rocksdb/db.h"
10 
11 namespace ROCKSDB_NAMESPACE {
12 namespace {
13 // Return a version of Options `opts` that allow us to open/write into a DB
14 // without triggering an automatic compaction or stalling. This is guaranteed
15 // by disabling automatic compactions and using huge values for stalling
16 // triggers.
GetNoCompactionOptions(const Options & opts)17 Options GetNoCompactionOptions(const Options& opts) {
18   Options ret_opts = opts;
19   ret_opts.disable_auto_compactions = true;
20   ret_opts.level0_slowdown_writes_trigger = 999999;
21   ret_opts.level0_stop_writes_trigger = 999999;
22   ret_opts.soft_pending_compaction_bytes_limit = 0;
23   ret_opts.hard_pending_compaction_bytes_limit = 0;
24   return ret_opts;
25 }
26 
OpenDb(const Options & options,const std::string & dbname,std::unique_ptr<DB> * db)27 Status OpenDb(const Options& options, const std::string& dbname,
28               std::unique_ptr<DB>* db) {
29   db->reset();
30   DB* tmpdb;
31   Status s = DB::Open(options, dbname, &tmpdb);
32   if (s.ok()) {
33     db->reset(tmpdb);
34   }
35   return s;
36 }
37 
CompactToLevel(const Options & options,const std::string & dbname,int dest_level,bool need_reopen)38 Status CompactToLevel(const Options& options, const std::string& dbname,
39                       int dest_level, bool need_reopen) {
40   std::unique_ptr<DB> db;
41   Options no_compact_opts = GetNoCompactionOptions(options);
42   if (dest_level == 0) {
43     // L0 has strict sequenceID requirements to files to it. It's safer
44     // to only put one compacted file to there.
45     // This is only used for converting to universal compaction with
46     // only one level. In this case, compacting to one file is also
47     // optimal.
48     no_compact_opts.target_file_size_base = 999999999999999;
49     no_compact_opts.max_compaction_bytes = 999999999999999;
50   }
51   Status s = OpenDb(no_compact_opts, dbname, &db);
52   if (!s.ok()) {
53     return s;
54   }
55   CompactRangeOptions cro;
56   cro.change_level = true;
57   cro.target_level = dest_level;
58   if (dest_level == 0) {
59     // cannot use kForceOptimized because the compaction is expected to
60     // generate one output file
61     cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
62   }
63   db->CompactRange(cro, nullptr, nullptr);
64 
65   if (need_reopen) {
66     // Need to restart DB to rewrite the manifest file.
67     // In order to open a DB with specific num_levels, the manifest file should
68     // contain no record that mentiones any level beyond num_levels. Issuing a
69     // full compaction will move all the data to a level not exceeding
70     // num_levels, but the manifest may still contain previous record mentioning
71     // a higher level. Reopening the DB will force the manifest to be rewritten
72     // so that those records will be cleared.
73     db.reset();
74     s = OpenDb(no_compact_opts, dbname, &db);
75   }
76   return s;
77 }
78 
MigrateToUniversal(std::string dbname,const Options & old_opts,const Options & new_opts)79 Status MigrateToUniversal(std::string dbname, const Options& old_opts,
80                           const Options& new_opts) {
81   if (old_opts.num_levels <= new_opts.num_levels ||
82       old_opts.compaction_style == CompactionStyle::kCompactionStyleFIFO) {
83     return Status::OK();
84   } else {
85     bool need_compact = false;
86     {
87       std::unique_ptr<DB> db;
88       Options opts = GetNoCompactionOptions(old_opts);
89       Status s = OpenDb(opts, dbname, &db);
90       if (!s.ok()) {
91         return s;
92       }
93       ColumnFamilyMetaData metadata;
94       db->GetColumnFamilyMetaData(&metadata);
95       if (!metadata.levels.empty() &&
96           metadata.levels.back().level >= new_opts.num_levels) {
97         need_compact = true;
98       }
99     }
100     if (need_compact) {
101       return CompactToLevel(old_opts, dbname, new_opts.num_levels - 1, true);
102     }
103     return Status::OK();
104   }
105 }
106 
MigrateToLevelBase(std::string dbname,const Options & old_opts,const Options & new_opts)107 Status MigrateToLevelBase(std::string dbname, const Options& old_opts,
108                           const Options& new_opts) {
109   if (!new_opts.level_compaction_dynamic_level_bytes) {
110     if (old_opts.num_levels == 1) {
111       return Status::OK();
112     }
113     // Compact everything to level 1 to guarantee it can be safely opened.
114     Options opts = old_opts;
115     opts.target_file_size_base = new_opts.target_file_size_base;
116     // Although sometimes we can open the DB with the new option without error,
117     // We still want to compact the files to avoid the LSM tree to stuck
118     // in bad shape. For example, if the user changed the level size
119     // multiplier from 4 to 8, with the same data, we will have fewer
120     // levels. Unless we issue a full comaction, the LSM tree may stuck
121     // with more levels than needed and it won't recover automatically.
122     return CompactToLevel(opts, dbname, 1, true);
123   } else {
124     // Compact everything to the last level to guarantee it can be safely
125     // opened.
126     if (old_opts.num_levels == 1) {
127       return Status::OK();
128     } else if (new_opts.num_levels > old_opts.num_levels) {
129       // Dynamic level mode requires data to be put in the last level first.
130       return CompactToLevel(new_opts, dbname, new_opts.num_levels - 1, false);
131     } else {
132       Options opts = old_opts;
133       opts.target_file_size_base = new_opts.target_file_size_base;
134       return CompactToLevel(opts, dbname, new_opts.num_levels - 1, true);
135     }
136   }
137 }
138 }  // namespace
139 
OptionChangeMigration(std::string dbname,const Options & old_opts,const Options & new_opts)140 Status OptionChangeMigration(std::string dbname, const Options& old_opts,
141                              const Options& new_opts) {
142   if (old_opts.compaction_style == CompactionStyle::kCompactionStyleFIFO) {
143     // LSM generated by FIFO compation can be opened by any compaction.
144     return Status::OK();
145   } else if (new_opts.compaction_style ==
146              CompactionStyle::kCompactionStyleUniversal) {
147     return MigrateToUniversal(dbname, old_opts, new_opts);
148   } else if (new_opts.compaction_style ==
149              CompactionStyle::kCompactionStyleLevel) {
150     return MigrateToLevelBase(dbname, old_opts, new_opts);
151   } else if (new_opts.compaction_style ==
152              CompactionStyle::kCompactionStyleFIFO) {
153     return CompactToLevel(old_opts, dbname, 0, true);
154   } else {
155     return Status::NotSupported(
156         "Do not how to migrate to this compaction style");
157   }
158 }
159 }  // namespace ROCKSDB_NAMESPACE
160 #else
161 namespace ROCKSDB_NAMESPACE {
OptionChangeMigration(std::string,const Options &,const Options &)162 Status OptionChangeMigration(std::string /*dbname*/,
163                              const Options& /*old_opts*/,
164                              const Options& /*new_opts*/) {
165   return Status::NotSupported();
166 }
167 }  // namespace ROCKSDB_NAMESPACE
168 #endif  // ROCKSDB_LITE
169