@@ -17,21 +17,28 @@ import { type MeasureContext, type Tx, WorkspaceUuid } from '@hcengineering/core
17
17
import { PlatformQueueProducer } from '@hcengineering/server-core'
18
18
import { Readable } from 'stream'
19
19
20
+ import { type Cache , type CacheEntry , createCache , streamToBuffer } from './cache'
20
21
import { type BlobDB , WorkspaceStatsResult } from './db'
21
22
import { digestToUUID , stringToUUID } from './encodings'
22
23
import { type BlobHead , type BlobBody , type BlobList , type BlobStorage , type Datalake , type Location } from './types'
23
24
import { type S3Bucket } from '../s3'
24
25
import { blobEvents } from './queue'
26
+ import { CacheConfig } from '../config'
25
27
26
28
export class DatalakeImpl implements Datalake {
29
+ private readonly cache : Cache
30
+
27
31
constructor (
28
32
private readonly db : BlobDB ,
29
33
private readonly buckets : Array < { location : Location , bucket : S3Bucket } > ,
30
34
private readonly producer : PlatformQueueProducer < Tx > ,
31
35
private readonly options : {
32
36
cacheControl : string
37
+ cache : CacheConfig
33
38
}
34
- ) { }
39
+ ) {
40
+ this . cache = createCache ( options . cache )
41
+ }
35
42
36
43
async list (
37
44
ctx : MeasureContext ,
@@ -82,6 +89,14 @@ export class DatalakeImpl implements Datalake {
82
89
return null
83
90
}
84
91
92
+ const cached = this . cache . get ( blob . hash )
93
+ if ( cached !== undefined ) {
94
+ return {
95
+ ...cached ,
96
+ body : Readable . from ( cached . body )
97
+ }
98
+ }
99
+
85
100
const { bucket } = await this . selectStorage ( ctx , workspace , blob . location )
86
101
87
102
const range = options . range
@@ -90,7 +105,7 @@ export class DatalakeImpl implements Datalake {
90
105
return null
91
106
}
92
107
93
- return {
108
+ const result = {
94
109
name : blob . name ,
95
110
etag : blob . hash ,
96
111
size : blob . size ,
@@ -102,6 +117,12 @@ export class DatalakeImpl implements Datalake {
102
117
lastModified : object . lastModified ,
103
118
cacheControl : object . cacheControl
104
119
}
120
+
121
+ if ( this . options . cache . enabled && object . size <= this . options . cache . blobSize ) {
122
+ this . cache . set ( blob . hash , { ...result , body : await streamToBuffer ( object . body ) } )
123
+ }
124
+
125
+ return result
105
126
}
106
127
107
128
async delete ( ctx : MeasureContext , workspace : WorkspaceUuid , name : string | string [ ] ) : Promise < void > {
@@ -136,10 +157,11 @@ export class DatalakeImpl implements Datalake {
136
157
137
158
const hash = digestToUUID ( sha256 )
138
159
const filename = hash
160
+ const etag = hash
139
161
140
162
// Check if we have the same blob already
141
163
if ( blob ?. hash === hash && blob ?. type === contentType ) {
142
- return { name, size, contentType, lastModified, etag : hash }
164
+ return { name, size, contentType, lastModified, etag }
143
165
}
144
166
145
167
const data = await this . db . getData ( ctx , { hash, location } )
@@ -151,35 +173,53 @@ export class DatalakeImpl implements Datalake {
151
173
try {
152
174
const event =
153
175
blob != null
154
- ? blobEvents . updated ( name , { contentType, lastModified, size, etag : hash } )
155
- : blobEvents . created ( name , { contentType, lastModified, size, etag : hash } )
176
+ ? blobEvents . updated ( name , { contentType, lastModified, size, etag } )
177
+ : blobEvents . created ( name , { contentType, lastModified, size, etag } )
156
178
await this . producer . send ( workspace , [ event ] )
157
179
} catch ( err ) {
158
180
ctx . error ( 'failed to send blob created event' , { workspace, name, err } )
159
181
}
160
182
161
- return { name, size, contentType, lastModified, etag : hash }
183
+ return { name, size, contentType, lastModified, etag }
162
184
} else {
163
185
const putOptions = {
164
186
contentLength : size ,
165
187
contentType,
166
188
cacheControl,
167
189
lastModified
168
190
}
169
- await bucket . put ( ctx , filename , body , putOptions )
191
+
192
+ let data : Readable | Buffer = body
193
+
194
+ if ( this . options . cache . enabled && size <= this . options . cache . blobSize ) {
195
+ data = await streamToBuffer ( body )
196
+ const entry : CacheEntry = {
197
+ body : data ,
198
+ bodyLength : data . length ,
199
+ bodyEtag : etag ,
200
+ size,
201
+ name,
202
+ etag,
203
+ ...putOptions
204
+ }
205
+ this . cache . set ( hash , entry )
206
+ }
207
+
208
+ await bucket . put ( ctx , filename , data , putOptions )
170
209
await this . db . createBlobData ( ctx , { workspace, name, hash, location, filename, size, type : contentType } )
171
210
172
211
try {
173
212
const event =
174
213
blob != null
175
- ? blobEvents . updated ( name , { contentType, lastModified, size, etag : hash } )
176
- : blobEvents . created ( name , { contentType, lastModified, size, etag : hash } )
214
+ ? blobEvents . updated ( name , { contentType, lastModified, size, etag } )
215
+ : blobEvents . created ( name , { contentType, lastModified, size, etag } )
177
216
await this . producer . send ( workspace , [ event ] )
178
217
} catch ( err ) {
218
+ this . cache . delete ( hash )
179
219
ctx . error ( 'failed to send blob created event' , { workspace, name, err } )
180
220
}
181
221
182
- return { name, size, contentType, lastModified, etag : hash }
222
+ return { name, size, contentType, lastModified, etag }
183
223
}
184
224
}
185
225
0 commit comments