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).
6 // Copyright (c) 2011 The LevelDB Authors. All rights reserved.
7 // Use of this source code is governed by a BSD-style license that can be
8 // found in the LICENSE file. See the AUTHORS file for names of contributors.
10 // Repairer does best effort recovery to recover as much data as possible after
11 // a disaster without compromising consistency. It does not guarantee bringing
12 // the database to a time consistent state.
14 // Repair process is broken into 4 phases:
16 // (b) Convert logs to tables
17 // (c) Extract metadata
18 // (d) Write Descriptor
22 // The repairer goes through all the files in the directory, and classifies them
23 // based on their file name. Any file that cannot be identified by name will be
26 // (b) Convert logs to table
28 // Every log file that is active is replayed. All sections of the file where the
29 // checksum does not match is skipped over. We intentionally give preference to
32 // (c) Extract metadata
34 // We scan every table to compute
35 // (1) smallest/largest for the table
36 // (2) largest sequence number in the table
37 // (3) oldest blob file referred to by the table (if applicable)
39 // If we are unable to scan the file, then we ignore the table.
41 // (d) Write Descriptor
43 // We generate descriptor contents:
44 // - log number is set to zero
45 // - next-file-number is set to 1 + largest file number we found
46 // - last-sequence-number is set to largest sequence# found across
47 // all tables (see 2c)
48 // - compaction pointers are cleared
49 // - every table file is added at level 0
51 // Possible optimization 1:
52 // (a) Compute total size and use to pick appropriate max-level M
53 // (b) Sort tables by largest sequence# in the table
54 // (c) For each table: if it overlaps earlier table, place in level-0,
55 // else place in level-M.
56 // (d) We can provide options for time consistent recovery and unsafe recovery
57 // (ignore checksum failure when applicable)
58 // Possible optimization 2:
59 // Store per-table metadata (smallest, largest, largest-seq#, ...)
60 // in the table's meta section to speed up ScanTable.
65 #include "db/builder.h"
66 #include "db/db_impl/db_impl.h"
67 #include "db/dbformat.h"
68 #include "db/log_reader.h"
69 #include "db/log_writer.h"
70 #include "db/memtable.h"
71 #include "db/table_cache.h"
72 #include "db/version_edit.h"
73 #include "db/write_batch_internal.h"
74 #include "env/composite_env_wrapper.h"
75 #include "file/filename.h"
76 #include "file/writable_file_writer.h"
77 #include "options/cf_options.h"
78 #include "rocksdb/comparator.h"
79 #include "rocksdb/db.h"
80 #include "rocksdb/env.h"
81 #include "rocksdb/options.h"
82 #include "rocksdb/write_buffer_manager.h"
83 #include "table/scoped_arena_iterator.h"
84 #include "util/string_util.h"
86 namespace ROCKSDB_NAMESPACE
{
92 Repairer(const std::string
& dbname
, const DBOptions
& db_options
,
93 const std::vector
<ColumnFamilyDescriptor
>& column_families
,
94 const ColumnFamilyOptions
& default_cf_opts
,
95 const ColumnFamilyOptions
& unknown_cf_opts
, bool create_unknown_cfs
)
99 db_options_(SanitizeOptions(dbname_
, db_options
)),
100 immutable_db_options_(ImmutableDBOptions(db_options_
)),
101 icmp_(default_cf_opts
.comparator
),
103 SanitizeOptions(immutable_db_options_
, default_cf_opts
)),
105 ImmutableCFOptions(immutable_db_options_
, default_cf_opts_
)),
107 SanitizeOptions(immutable_db_options_
, unknown_cf_opts
)),
108 create_unknown_cfs_(create_unknown_cfs
),
110 // TableCache can be small since we expect each table to be opened
112 NewLRUCache(10, db_options_
.table_cache_numshardbits
)),
113 table_cache_(new TableCache(default_cf_iopts_
, env_options_
,
114 raw_table_cache_
.get(),
115 /*block_cache_tracer=*/nullptr)),
116 wb_(db_options_
.db_write_buffer_size
),
117 wc_(db_options_
.delayed_write_rate
),
118 vset_(dbname_
, &immutable_db_options_
, env_options_
,
119 raw_table_cache_
.get(), &wb_
, &wc_
,
120 /*block_cache_tracer=*/nullptr),
121 next_file_number_(1),
123 for (const auto& cfd
: column_families
) {
124 cf_name_to_opts_
[cfd
.name
] = cfd
.options
;
128 const ColumnFamilyOptions
* GetColumnFamilyOptions(
129 const std::string
& cf_name
) {
130 if (cf_name_to_opts_
.find(cf_name
) == cf_name_to_opts_
.end()) {
131 if (create_unknown_cfs_
) {
132 return &unknown_cf_opts_
;
136 return &cf_name_to_opts_
[cf_name
];
139 // Adds a column family to the VersionSet with cf_options_ and updates
141 Status
AddColumnFamily(const std::string
& cf_name
, uint32_t cf_id
) {
142 const auto* cf_opts
= GetColumnFamilyOptions(cf_name
);
143 if (cf_opts
== nullptr) {
144 return Status::Corruption("Encountered unknown column family with name=" +
145 cf_name
+ ", id=" + ToString(cf_id
));
147 Options
opts(db_options_
, *cf_opts
);
148 MutableCFOptions
mut_cf_opts(opts
);
151 edit
.SetComparatorName(opts
.comparator
->Name());
152 edit
.SetLogNumber(0);
153 edit
.SetColumnFamily(cf_id
);
154 ColumnFamilyData
* cfd
;
156 edit
.AddColumnFamily(cf_name
);
159 Status status
= vset_
.LogAndApply(cfd
, mut_cf_opts
, &edit
, &mutex_
,
160 nullptr /* db_directory */,
161 false /* new_descriptor_log */, cf_opts
);
167 if (db_lock_
!= nullptr) {
168 env_
->UnlockFile(db_lock_
);
174 Status status
= env_
->LockFile(LockFileName(dbname_
), &db_lock_
);
178 status
= FindFiles();
180 // Discard older manifests and start a fresh one
181 for (size_t i
= 0; i
< manifests_
.size(); i
++) {
182 ArchiveFile(dbname_
+ "/" + manifests_
[i
]);
184 // Just create a DBImpl temporarily so we can reuse NewDB()
185 DBImpl
* db_impl
= new DBImpl(db_options_
, dbname_
);
186 status
= db_impl
->NewDB();
191 // Recover using the fresh manifest created by NewDB()
193 vset_
.Recover({{kDefaultColumnFamilyName
, default_cf_opts_
}}, false);
196 // Need to scan existing SST files first so the column families are
197 // created before we process WAL files
200 // ExtractMetaData() uses table_fds_ to know which SST files' metadata to
201 // extract -- we need to clear it here since metadata for existing SST
202 // files has been extracted already
204 ConvertLogFilesToTables();
206 status
= AddTables();
210 for (size_t i
= 0; i
< tables_
.size(); i
++) {
211 bytes
+= tables_
[i
].meta
.fd
.GetFileSize();
213 ROCKS_LOG_WARN(db_options_
.info_log
,
214 "**** Repaired rocksdb %s; "
215 "recovered %" ROCKSDB_PRIszt
" files; %" PRIu64
217 "Some data may have been lost. "
219 dbname_
.c_str(), tables_
.size(), bytes
);
227 uint32_t column_family_id
;
228 std::string column_family_name
;
231 std::string
const dbname_
;
233 const EnvOptions env_options_
;
234 const DBOptions db_options_
;
235 const ImmutableDBOptions immutable_db_options_
;
236 const InternalKeyComparator icmp_
;
237 const ColumnFamilyOptions default_cf_opts_
;
238 const ImmutableCFOptions default_cf_iopts_
; // table_cache_ holds reference
239 const ColumnFamilyOptions unknown_cf_opts_
;
240 const bool create_unknown_cfs_
;
241 std::shared_ptr
<Cache
> raw_table_cache_
;
242 TableCache
* table_cache_
;
243 WriteBufferManager wb_
;
246 std::unordered_map
<std::string
, ColumnFamilyOptions
> cf_name_to_opts_
;
247 InstrumentedMutex mutex_
;
249 std::vector
<std::string
> manifests_
;
250 std::vector
<FileDescriptor
> table_fds_
;
251 std::vector
<uint64_t> logs_
;
252 std::vector
<TableInfo
> tables_
;
253 uint64_t next_file_number_
;
254 // Lock over the persistent DB state. Non-nullptr iff successfully
259 std::vector
<std::string
> filenames
;
260 bool found_file
= false;
261 std::vector
<std::string
> to_search_paths
;
263 for (size_t path_id
= 0; path_id
< db_options_
.db_paths
.size(); path_id
++) {
264 to_search_paths
.push_back(db_options_
.db_paths
[path_id
].path
);
267 // search wal_dir if user uses a customize wal_dir
269 Status status
= env_
->AreFilesSame(db_options_
.wal_dir
, dbname_
, &same
);
270 if (status
.IsNotSupported()) {
271 same
= db_options_
.wal_dir
== dbname_
;
272 status
= Status::OK();
273 } else if (!status
.ok()) {
278 to_search_paths
.push_back(db_options_
.wal_dir
);
281 for (size_t path_id
= 0; path_id
< to_search_paths
.size(); path_id
++) {
282 status
= env_
->GetChildren(to_search_paths
[path_id
], &filenames
);
286 if (!filenames
.empty()) {
292 for (size_t i
= 0; i
< filenames
.size(); i
++) {
293 if (ParseFileName(filenames
[i
], &number
, &type
)) {
294 if (type
== kDescriptorFile
) {
295 manifests_
.push_back(filenames
[i
]);
297 if (number
+ 1 > next_file_number_
) {
298 next_file_number_
= number
+ 1;
300 if (type
== kLogFile
) {
301 logs_
.push_back(number
);
302 } else if (type
== kTableFile
) {
303 table_fds_
.emplace_back(number
, static_cast<uint32_t>(path_id
),
306 // Ignore other files
313 return Status::Corruption(dbname_
, "repair found no files");
318 void ConvertLogFilesToTables() {
319 for (size_t i
= 0; i
< logs_
.size(); i
++) {
320 // we should use LogFileName(wal_dir, logs_[i]) here. user might uses wal_dir option.
321 std::string logname
= LogFileName(db_options_
.wal_dir
, logs_
[i
]);
322 Status status
= ConvertLogToTable(logs_
[i
]);
324 ROCKS_LOG_WARN(db_options_
.info_log
,
325 "Log #%" PRIu64
": ignoring conversion error: %s",
326 logs_
[i
], status
.ToString().c_str());
328 ArchiveFile(logname
);
332 Status
ConvertLogToTable(uint64_t log
) {
333 struct LogReporter
: public log::Reader::Reporter
{
335 std::shared_ptr
<Logger
> info_log
;
337 void Corruption(size_t bytes
, const Status
& s
) override
{
338 // We print error messages for corruption, but continue repairing.
339 ROCKS_LOG_ERROR(info_log
, "Log #%" PRIu64
": dropping %d bytes; %s",
340 lognum
, static_cast<int>(bytes
), s
.ToString().c_str());
345 std::string logname
= LogFileName(db_options_
.wal_dir
, log
);
346 std::unique_ptr
<SequentialFile
> lfile
;
347 Status status
= env_
->NewSequentialFile(
348 logname
, &lfile
, env_
->OptimizeForLogRead(env_options_
));
352 std::unique_ptr
<SequentialFileReader
> lfile_reader(new SequentialFileReader(
353 NewLegacySequentialFileWrapper(lfile
), logname
));
355 // Create the log reader.
356 LogReporter reporter
;
358 reporter
.info_log
= db_options_
.info_log
;
359 reporter
.lognum
= log
;
360 // We intentionally make log::Reader do checksumming so that
361 // corruptions cause entire commits to be skipped instead of
362 // propagating bad information (like overly large sequence
364 log::Reader
reader(db_options_
.info_log
, std::move(lfile_reader
), &reporter
,
365 true /*enable checksum*/, log
);
367 // Initialize per-column family memtables
368 for (auto* cfd
: *vset_
.GetColumnFamilySet()) {
369 cfd
->CreateNewMemtable(*cfd
->GetLatestMutableCFOptions(),
372 auto cf_mems
= new ColumnFamilyMemTablesImpl(vset_
.GetColumnFamilySet());
374 // Read all the records and add to a memtable
379 while (reader
.ReadRecord(&record
, &scratch
)) {
380 if (record
.size() < WriteBatchInternal::kHeader
) {
382 record
.size(), Status::Corruption("log record too small"));
385 WriteBatchInternal::SetContents(&batch
, record
);
387 WriteBatchInternal::InsertInto(&batch
, cf_mems
, nullptr, nullptr);
389 counter
+= WriteBatchInternal::Count(&batch
);
391 ROCKS_LOG_WARN(db_options_
.info_log
, "Log #%" PRIu64
": ignoring %s",
392 log
, status
.ToString().c_str());
393 status
= Status::OK(); // Keep going with rest of file
397 // Dump a table for each column family with entries in this log file.
398 for (auto* cfd
: *vset_
.GetColumnFamilySet()) {
399 // Do not record a version edit for this conversion to a Table
400 // since ExtractMetaData() will also generate edits.
401 MemTable
* mem
= cfd
->mem();
402 if (mem
->IsEmpty()) {
407 meta
.fd
= FileDescriptor(next_file_number_
++, 0, 0);
409 ro
.total_order_seek
= true;
411 ScopedArenaIterator
iter(mem
->NewIterator(ro
, &arena
));
412 int64_t _current_time
= 0;
413 status
= env_
->GetCurrentTime(&_current_time
); // ignore error
414 const uint64_t current_time
= static_cast<uint64_t>(_current_time
);
415 SnapshotChecker
* snapshot_checker
= DisableGCSnapshotChecker::Instance();
417 auto write_hint
= cfd
->CalculateSSTWriteHint(0);
418 std::vector
<std::unique_ptr
<FragmentedRangeTombstoneIterator
>>
420 auto range_del_iter
=
421 mem
->NewRangeTombstoneIterator(ro
, kMaxSequenceNumber
);
422 if (range_del_iter
!= nullptr) {
423 range_del_iters
.emplace_back(range_del_iter
);
426 LegacyFileSystemWrapper
fs(env_
);
428 dbname_
, env_
, &fs
, *cfd
->ioptions(),
429 *cfd
->GetLatestMutableCFOptions(), env_options_
, table_cache_
,
430 iter
.get(), std::move(range_del_iters
), &meta
,
431 cfd
->internal_comparator(), cfd
->int_tbl_prop_collector_factories(),
432 cfd
->GetID(), cfd
->GetName(), {}, kMaxSequenceNumber
,
433 snapshot_checker
, kNoCompression
, 0 /* sample_for_compression */,
434 CompressionOptions(), false, nullptr /* internal_stats */,
435 TableFileCreationReason::kRecovery
, nullptr /* event_logger */,
436 0 /* job_id */, Env::IO_HIGH
, nullptr /* table_properties */,
437 -1 /* level */, current_time
, write_hint
);
438 ROCKS_LOG_INFO(db_options_
.info_log
,
439 "Log #%" PRIu64
": %d ops saved to Table #%" PRIu64
" %s",
440 log
, counter
, meta
.fd
.GetNumber(),
441 status
.ToString().c_str());
443 if (meta
.fd
.GetFileSize() > 0) {
444 table_fds_
.push_back(meta
.fd
);
454 void ExtractMetaData() {
455 for (size_t i
= 0; i
< table_fds_
.size(); i
++) {
457 t
.meta
.fd
= table_fds_
[i
];
458 Status status
= ScanTable(&t
);
460 std::string fname
= TableFileName(
461 db_options_
.db_paths
, t
.meta
.fd
.GetNumber(), t
.meta
.fd
.GetPathId());
462 char file_num_buf
[kFormatFileNumberBufSize
];
463 FormatFileNumber(t
.meta
.fd
.GetNumber(), t
.meta
.fd
.GetPathId(),
464 file_num_buf
, sizeof(file_num_buf
));
465 ROCKS_LOG_WARN(db_options_
.info_log
, "Table #%s: ignoring %s",
466 file_num_buf
, status
.ToString().c_str());
469 tables_
.push_back(t
);
474 Status
ScanTable(TableInfo
* t
) {
475 std::string fname
= TableFileName(
476 db_options_
.db_paths
, t
->meta
.fd
.GetNumber(), t
->meta
.fd
.GetPathId());
479 Status status
= env_
->GetFileSize(fname
, &file_size
);
480 t
->meta
.fd
= FileDescriptor(t
->meta
.fd
.GetNumber(), t
->meta
.fd
.GetPathId(),
482 std::shared_ptr
<const TableProperties
> props
;
484 status
= table_cache_
->GetTableProperties(env_options_
, icmp_
, t
->meta
.fd
,
488 t
->column_family_id
= static_cast<uint32_t>(props
->column_family_id
);
489 if (t
->column_family_id
==
490 TablePropertiesCollectorFactory::Context::kUnknownColumnFamily
) {
492 db_options_
.info_log
,
494 ": column family unknown (probably due to legacy format); "
495 "adding to default column family id 0.",
496 t
->meta
.fd
.GetNumber());
497 t
->column_family_id
= 0;
500 if (vset_
.GetColumnFamilySet()->GetColumnFamily(t
->column_family_id
) ==
503 AddColumnFamily(props
->column_family_name
, t
->column_family_id
);
505 t
->meta
.oldest_ancester_time
= props
->creation_time
;
507 ColumnFamilyData
* cfd
= nullptr;
509 cfd
= vset_
.GetColumnFamilySet()->GetColumnFamily(t
->column_family_id
);
510 if (cfd
->GetName() != props
->column_family_name
) {
512 db_options_
.info_log
,
514 ": inconsistent column family name '%s'; expected '%s' for column "
515 "family id %" PRIu32
".",
516 t
->meta
.fd
.GetNumber(), props
->column_family_name
.c_str(),
517 cfd
->GetName().c_str(), t
->column_family_id
);
518 status
= Status::Corruption(dbname_
, "inconsistent column family name");
523 ropts
.total_order_seek
= true;
524 InternalIterator
* iter
= table_cache_
->NewIterator(
525 ropts
, env_options_
, cfd
->internal_comparator(), t
->meta
,
526 nullptr /* range_del_agg */,
527 cfd
->GetLatestMutableCFOptions()->prefix_extractor
.get(),
528 /*table_reader_ptr=*/nullptr, /*file_read_hist=*/nullptr,
529 TableReaderCaller::kRepair
, /*arena=*/nullptr, /*skip_filters=*/false,
530 /*level=*/-1, /*smallest_compaction_key=*/nullptr,
531 /*largest_compaction_key=*/nullptr);
532 ParsedInternalKey parsed
;
533 for (iter
->SeekToFirst(); iter
->Valid(); iter
->Next()) {
534 Slice key
= iter
->key();
535 if (!ParseInternalKey(key
, &parsed
)) {
536 ROCKS_LOG_ERROR(db_options_
.info_log
,
537 "Table #%" PRIu64
": unparsable key %s",
538 t
->meta
.fd
.GetNumber(), EscapeString(key
).c_str());
544 t
->meta
.UpdateBoundaries(key
, iter
->value(), parsed
.sequence
,
547 if (!iter
->status().ok()) {
548 status
= iter
->status();
552 ROCKS_LOG_INFO(db_options_
.info_log
, "Table #%" PRIu64
": %d entries %s",
553 t
->meta
.fd
.GetNumber(), counter
,
554 status
.ToString().c_str());
560 std::unordered_map
<uint32_t, std::vector
<const TableInfo
*>> cf_id_to_tables
;
561 SequenceNumber max_sequence
= 0;
562 for (size_t i
= 0; i
< tables_
.size(); i
++) {
563 cf_id_to_tables
[tables_
[i
].column_family_id
].push_back(&tables_
[i
]);
564 if (max_sequence
< tables_
[i
].meta
.fd
.largest_seqno
) {
565 max_sequence
= tables_
[i
].meta
.fd
.largest_seqno
;
568 vset_
.SetLastAllocatedSequence(max_sequence
);
569 vset_
.SetLastPublishedSequence(max_sequence
);
570 vset_
.SetLastSequence(max_sequence
);
572 for (const auto& cf_id_and_tables
: cf_id_to_tables
) {
574 vset_
.GetColumnFamilySet()->GetColumnFamily(cf_id_and_tables
.first
);
576 edit
.SetComparatorName(cfd
->user_comparator()->Name());
577 edit
.SetLogNumber(0);
578 edit
.SetNextFile(next_file_number_
);
579 edit
.SetColumnFamily(cfd
->GetID());
581 // TODO(opt): separate out into multiple levels
582 for (const auto* table
: cf_id_and_tables
.second
) {
584 0, table
->meta
.fd
.GetNumber(), table
->meta
.fd
.GetPathId(),
585 table
->meta
.fd
.GetFileSize(), table
->meta
.smallest
,
586 table
->meta
.largest
, table
->meta
.fd
.smallest_seqno
,
587 table
->meta
.fd
.largest_seqno
, table
->meta
.marked_for_compaction
,
588 table
->meta
.oldest_blob_file_number
,
589 table
->meta
.oldest_ancester_time
, table
->meta
.file_creation_time
,
590 table
->meta
.file_checksum
, table
->meta
.file_checksum_func_name
);
592 assert(next_file_number_
> 0);
593 vset_
.MarkFileNumberUsed(next_file_number_
- 1);
595 Status status
= vset_
.LogAndApply(
596 cfd
, *cfd
->GetLatestMutableCFOptions(), &edit
, &mutex_
,
597 nullptr /* db_directory */, false /* new_descriptor_log */);
606 void ArchiveFile(const std::string
& fname
) {
607 // Move into another directory. E.g., for
611 const char* slash
= strrchr(fname
.c_str(), '/');
613 if (slash
!= nullptr) {
614 new_dir
.assign(fname
.data(), slash
- fname
.data());
616 new_dir
.append("/lost");
617 env_
->CreateDir(new_dir
); // Ignore error
618 std::string new_file
= new_dir
;
619 new_file
.append("/");
620 new_file
.append((slash
== nullptr) ? fname
.c_str() : slash
+ 1);
621 Status s
= env_
->RenameFile(fname
, new_file
);
622 ROCKS_LOG_INFO(db_options_
.info_log
, "Archiving %s: %s\n", fname
.c_str(),
623 s
.ToString().c_str());
627 Status
GetDefaultCFOptions(
628 const std::vector
<ColumnFamilyDescriptor
>& column_families
,
629 ColumnFamilyOptions
* res
) {
630 assert(res
!= nullptr);
631 auto iter
= std::find_if(column_families
.begin(), column_families
.end(),
632 [](const ColumnFamilyDescriptor
& cfd
) {
633 return cfd
.name
== kDefaultColumnFamilyName
;
635 if (iter
== column_families
.end()) {
636 return Status::InvalidArgument(
637 "column_families", "Must contain entry for default column family");
639 *res
= iter
->options
;
642 } // anonymous namespace
644 Status
RepairDB(const std::string
& dbname
, const DBOptions
& db_options
,
645 const std::vector
<ColumnFamilyDescriptor
>& column_families
647 ColumnFamilyOptions default_cf_opts
;
648 Status status
= GetDefaultCFOptions(column_families
, &default_cf_opts
);
650 Repairer
repairer(dbname
, db_options
, column_families
,
652 ColumnFamilyOptions() /* unknown_cf_opts */,
653 false /* create_unknown_cfs */);
654 status
= repairer
.Run();
659 Status
RepairDB(const std::string
& dbname
, const DBOptions
& db_options
,
660 const std::vector
<ColumnFamilyDescriptor
>& column_families
,
661 const ColumnFamilyOptions
& unknown_cf_opts
) {
662 ColumnFamilyOptions default_cf_opts
;
663 Status status
= GetDefaultCFOptions(column_families
, &default_cf_opts
);
665 Repairer
repairer(dbname
, db_options
,
666 column_families
, default_cf_opts
,
667 unknown_cf_opts
, true /* create_unknown_cfs */);
668 status
= repairer
.Run();
673 Status
RepairDB(const std::string
& dbname
, const Options
& options
) {
674 Options
opts(options
);
675 if (opts
.file_system
== nullptr) {
676 opts
.file_system
.reset(new LegacyFileSystemWrapper(opts
.env
));
680 DBOptions
db_options(opts
);
681 ColumnFamilyOptions
cf_options(opts
);
682 Repairer
repairer(dbname
, db_options
,
683 {}, cf_options
/* default_cf_opts */,
684 cf_options
/* unknown_cf_opts */,
685 true /* create_unknown_cfs */);
686 return repairer
.Run();
689 } // namespace ROCKSDB_NAMESPACE
691 #endif // ROCKSDB_LITE