-
-
Notifications
You must be signed in to change notification settings - Fork 3
/
SSTableCache.ts
111 lines (101 loc) · 3.18 KB
/
SSTableCache.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
/**
* Copyright (c) 2018-present, heineiuo.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import Status from './Status'
import { FileHandle, Env, Log } from './Env'
import Table from './SSTable'
import { getTableFilename } from './Filename'
import { Options, ReadOptions } from './Options'
import Slice from './Slice'
import { Entry } from './Format'
import IteratorHelper from './IteratorHelper'
import LRUCache from 'lru-cache'
// TODO use LRUCache
export interface TableAndFile {
file: FileHandle
table: Table
}
export class TableCache {
// TODO entries: LRUCache capacity
constructor(dbpath: string, options: Options, entries: number) {
this._env = options.env
this._dbpath = dbpath
this._options = options
this._cache = new LRUCache({
max: entries,
async dispose(key: number, tf: TableAndFile): Promise<void> {
try {
await tf.file.close()
} catch (e) {}
},
})
}
_env: Env
_dbpath: string
_options: Options
_cache: LRUCache<number, TableAndFile>
public async get(
options: ReadOptions,
fileNumber: number,
fileSize: number,
key: Slice,
arg: unknown, // state.saver, set kNotFound if not found
saveValue: (arg: unknown, key: Slice, value: Slice) => void
): Promise<Status> {
let status = await this.findTable(fileNumber, fileSize)
if (await status.ok()) {
const tf = (await status.promise) as TableAndFile
const table = tf.table
// get value from table file
status = await table.get(key)
}
if (await status.ok()) {
const { key, value } = (await status.promise) as Entry
saveValue(arg, key, value)
}
return status
}
async findTable(fileNumber: number, fileSize: number): Promise<Status> {
let status = new Status()
const cachedTf = this._cache.get(fileNumber)
if (!cachedTf) {
const tableFilename = getTableFilename(this._dbpath, fileNumber)
status = new Status(this._env.open(tableFilename, 'r+'))
const tf = {} as TableAndFile
if (await status.ok()) {
tf.file = (await status.promise) as FileHandle
status = new Status(Table.open(this._options, tf.file))
}
if (await status.ok()) {
tf.table = (await status.promise) as Table
this._cache.set(fileNumber, tf)
status = new Status(Promise.resolve(tf))
} else {
// We do not cache error results so that if the error is transient,
// or somebody repairs the file, we recover automatically.
}
} else {
status = new Status(Promise.resolve(cachedTf))
}
return status
}
async *entryIterator(
options: Options,
fileNumber: number,
fileSize: number
): AsyncIterableIterator<Entry> {
const status = await this.findTable(fileNumber, fileSize)
if (await status.ok()) {
const tf = (await status.promise) as TableAndFile
yield* IteratorHelper.wrap(tf.table.entryIterator(), () => {
tf.file.close()
})
} else {
Log(this._options.infoLog, `Open Table file(${fileNumber}) fail.`)
throw new Error(`Open Table file(${fileNumber}) fail.`)
}
}
}