pixl-server-storage 4.0.3 → 4.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/engines/Redis.js CHANGED
@@ -9,6 +9,7 @@ const Class = require("pixl-class");
9
9
  const Component = require("pixl-server/component");
10
10
  const Redis = require('ioredis');
11
11
  const Tools = require("pixl-tools");
12
+ const Cache = require("pixl-cache");
12
13
 
13
14
  module.exports = Class.create({
14
15
 
@@ -47,6 +48,23 @@ module.exports = Class.create({
47
48
  this.keyTemplate = (r_config.keyTemplate || '').replace(/^\//, '').replace(/\/$/, '');
48
49
  delete r_config.keyTemplate;
49
50
 
51
+ // optional LRU cache
52
+ this.cache = null;
53
+ var cache_opts = r_config.cache;
54
+ if (cache_opts && cache_opts.enabled) {
55
+ this.logDebug(3, "Setting up LRU cache", cache_opts);
56
+ this.cache = new Cache( Tools.copyHashRemoveKeys(cache_opts, { enabled: 1 }) );
57
+ this.cache.on('expire', function(item, reason) {
58
+ self.logDebug(9, "Expiring LRU cache object: " + item.key + " due to: " + reason, {
59
+ key: item.key,
60
+ reason: reason,
61
+ totalCount: self.cache.count,
62
+ totalBytes: self.cache.bytes
63
+ });
64
+ });
65
+ }
66
+ delete r_config.cache;
67
+
50
68
  if (!r_config.username.length) delete r_config.username;
51
69
  if (!r_config.password.length) delete r_config.password;
52
70
 
@@ -93,13 +111,14 @@ module.exports = Class.create({
93
111
  // store key+value in Redis
94
112
  var self = this;
95
113
  key = this.prepKey(key);
114
+ var is_binary = this.storage.isBinaryKey(key);
96
115
 
97
- if (this.storage.isBinaryKey(key)) {
116
+ if (is_binary) {
98
117
  this.logDebug(9, "Storing Redis Binary Object: " + key, '' + value.length + ' bytes');
99
118
  }
100
119
  else {
101
120
  this.logDebug(9, "Storing Redis JSON Object: " + key, this.debugLevel(10) ? value : null);
102
- value = JSON.stringify( value );
121
+ value = Buffer.from( JSON.stringify( value ) );
103
122
  }
104
123
 
105
124
  this.redis.set( key, value, function(err) {
@@ -109,6 +128,11 @@ module.exports = Class.create({
109
128
  }
110
129
  else self.logDebug(9, "Store complete: " + key);
111
130
 
131
+ // possibly cache in LRU
132
+ if (!err && self.cache && !is_binary) {
133
+ self.cache.set( key, value, { date: Tools.timeNow(true) } );
134
+ }
135
+
112
136
  if (callback) callback(err);
113
137
  } );
114
138
  },
@@ -138,7 +162,30 @@ module.exports = Class.create({
138
162
  // The Redis API has no way to head / ping an object.
139
163
  // So, we have to do this the RAM-hard way...
140
164
 
141
- this.redis.get( key, function(err, data) {
165
+ // check cache first
166
+ if (this.cache && this.cache.has(key)) {
167
+ var item = this.cache.getMeta(key);
168
+
169
+ if (item.value.length == 0) {
170
+ process.nextTick( function() {
171
+ var err = new Error("Failed to head key: " + key + ": Not found");
172
+ err.code = "NoSuchKey";
173
+ callback( err );
174
+ } );
175
+ }
176
+ else {
177
+ process.nextTick( function() {
178
+ self.logDebug(9, "Cached head complete: " + key);
179
+ callback( null, {
180
+ mod: item.date,
181
+ len: item.value.length
182
+ } );
183
+ } );
184
+ }
185
+ return;
186
+ } // cache
187
+
188
+ this.redis.getBuffer( key, function(err, data) {
142
189
  if (err) {
143
190
  // an actual error
144
191
  err.message = "Failed to head key: " + key + ": " + err;
@@ -150,7 +197,12 @@ module.exports = Class.create({
150
197
  // always use "NoSuchKey" in error code
151
198
  var err = new Error("Failed to head key: " + key + ": Not found");
152
199
  err.code = "NoSuchKey";
153
-
200
+
201
+ if (self.cache && !self.storage.isBinaryKey(key)) {
202
+ // store 'empty' stub in cache
203
+ self.cache.set( key, Buffer.alloc(0), { date: 0 } );
204
+ }
205
+
154
206
  callback( err, null );
155
207
  }
156
208
  else {
@@ -163,44 +215,79 @@ module.exports = Class.create({
163
215
  // fetch Redis value given key
164
216
  var self = this;
165
217
  key = this.prepKey(key);
218
+ var is_binary = this.storage.isBinaryKey(key);
166
219
 
167
220
  this.logDebug(9, "Fetching Redis Object: " + key);
168
221
 
169
- var func = this.storage.isBinaryKey(key) ? 'getBuffer' : 'get';
170
- this.redis[func]( key, function(err, result) {
171
- if (!result) {
172
- if (err) {
173
- // an actual error
174
- err.message = "Failed to fetch key: " + key + ": " + err;
175
- self.logError('redis', ''+err);
176
- callback( err, null );
177
- }
178
- else {
179
- // record not found
180
- // always use "NoSuchKey" in error code
222
+ // check cache first
223
+ if (this.cache && !is_binary && this.cache.has(key)) {
224
+ var item = this.cache.getMeta(key);
225
+
226
+ if (item.value.length == 0) {
227
+ process.nextTick( function() {
181
228
  var err = new Error("Failed to fetch key: " + key + ": Not found");
182
229
  err.code = "NoSuchKey";
183
-
184
- callback( err, null );
185
- }
230
+ callback( err );
231
+ } );
186
232
  }
187
233
  else {
188
- if (self.storage.isBinaryKey(key)) {
189
- self.logDebug(9, "Binary fetch complete: " + key, '' + result.length + ' bytes');
190
- }
191
- else {
192
- try { result = JSON.parse( result.toString() ); }
234
+ process.nextTick( function() {
235
+ var data = null;
236
+ try { data = JSON.parse( item.value.toString() ); }
193
237
  catch (err) {
194
238
  self.logError('redis', "Failed to parse JSON record: " + key + ": " + err);
195
239
  callback( err, null );
196
240
  return;
197
241
  }
198
- self.logDebug(9, "JSON fetch complete: " + key, self.debugLevel(10) ? result : null);
242
+ self.logDebug(9, "Cached JSON fetch complete: " + key, self.debugLevel(10) ? data : null);
243
+
244
+ callback( null, data );
245
+ } );
246
+ }
247
+ return;
248
+ } // cache
249
+
250
+ this.redis.getBuffer( key, function(err, result) {
251
+ if (err) {
252
+ // an actual error
253
+ err.message = "Failed to fetch key: " + key + ": " + err;
254
+ self.logError('redis', ''+err);
255
+ return callback( err, null );
256
+ }
257
+ else if (!result || !result.length) {
258
+ // record not found
259
+ // always use "NoSuchKey" in error code
260
+ var err = new Error("Failed to fetch key: " + key + ": Not found");
261
+ err.code = "NoSuchKey";
262
+
263
+ if (self.cache && !is_binary) {
264
+ // store 'empty' stub in cache
265
+ self.cache.set( key, Buffer.alloc(0), { date: 0 } );
199
266
  }
200
267
 
201
- callback( null, result );
268
+ return callback( err, null );
202
269
  }
203
- } );
270
+
271
+ if (is_binary) {
272
+ self.logDebug(9, "Binary fetch complete: " + key, '' + result.length + ' bytes');
273
+ }
274
+ else {
275
+ // possibly cache in LRU
276
+ if (self.cache) {
277
+ self.cache.set( key, result, { date: Tools.timeNow(true) } );
278
+ }
279
+
280
+ try { result = JSON.parse( result.toString() ); }
281
+ catch (err) {
282
+ self.logError('redis', "Failed to parse JSON record: " + key + ": " + err);
283
+ callback( err, null );
284
+ return;
285
+ }
286
+ self.logDebug(9, "JSON fetch complete: " + key, self.debugLevel(10) ? result : null);
287
+ }
288
+
289
+ callback( null, result );
290
+ } ); // redis.getBuffer
204
291
  },
205
292
 
206
293
  getBuffer: function(key, callback) {
@@ -289,7 +376,6 @@ module.exports = Class.create({
289
376
  end = buf.length ? buf.length - 1 : 0;
290
377
  }
291
378
  if (isNaN(start) || isNaN(end) || (start < 0) || (start >= buf.length) || (end < start) || (end >= buf.length)) {
292
- download.destroy();
293
379
  callback( new Error("Invalid byte range (" + start + '-' + end + ") for key: " + key + " (len: " + buf.length + ")"), null );
294
380
  return;
295
381
  }
@@ -304,6 +390,7 @@ module.exports = Class.create({
304
390
  // delete Redis key given key
305
391
  var self = this;
306
392
  key = this.prepKey(key);
393
+ var is_binary = this.storage.isBinaryKey(key);
307
394
 
308
395
  this.logDebug(9, "Deleting Redis Object: " + key);
309
396
 
@@ -311,11 +398,24 @@ module.exports = Class.create({
311
398
  if (!err && !deleted) {
312
399
  err = new Error("Failed to fetch key: " + key + ": Not found");
313
400
  err.code = "NoSuchKey";
401
+
402
+ if (self.cache && !is_binary) {
403
+ // store 'empty' stub in cache
404
+ self.cache.set( key, Buffer.alloc(0), { date: 0 } );
405
+ }
314
406
  }
315
407
  if (err) {
316
408
  self.logError('redis', "Failed to delete object: " + key + ": " + err);
317
409
  }
318
- else self.logDebug(9, "Delete complete: " + key);
410
+ else {
411
+ self.logDebug(9, "Delete complete: " + key);
412
+
413
+ // possibly "delete" from LRU cache as well
414
+ if (self.cache && !is_binary) {
415
+ // store 'empty' stub in cache
416
+ self.cache.set( key, Buffer.alloc(0), { date: 0 } );
417
+ }
418
+ }
319
419
 
320
420
  callback(err);
321
421
  } );
@@ -9,6 +9,7 @@ const Class = require("pixl-class");
9
9
  const Component = require("pixl-server/component");
10
10
  const Redis = require('ioredis');
11
11
  const Tools = require("pixl-tools");
12
+ const Cache = require("pixl-cache");
12
13
 
13
14
  module.exports = Class.create({
14
15
 
@@ -49,6 +50,22 @@ module.exports = Class.create({
49
50
 
50
51
  this.keyTemplate = (r_config.keyTemplate || '').replace(/^\//, '').replace(/\/$/, '');
51
52
 
53
+ // optional LRU cache
54
+ this.cache = null;
55
+ var cache_opts = r_config.cache;
56
+ if (cache_opts && cache_opts.enabled) {
57
+ this.logDebug(3, "Setting up LRU cache", cache_opts);
58
+ this.cache = new Cache( Tools.copyHashRemoveKeys(cache_opts, { enabled: 1 }) );
59
+ this.cache.on('expire', function(item, reason) {
60
+ self.logDebug(9, "Expiring LRU cache object: " + item.key + " due to: " + reason, {
61
+ key: item.key,
62
+ reason: reason,
63
+ totalCount: self.cache.count,
64
+ totalBytes: self.cache.bytes
65
+ });
66
+ });
67
+ }
68
+
52
69
  r_config.clusterOpts.clusterRetryStrategy = function(attempts) {
53
70
  if (attempts > r_config.connectRetries) return false;
54
71
  return attempts;
@@ -107,13 +124,14 @@ module.exports = Class.create({
107
124
  // store key+value in Redis
108
125
  var self = this;
109
126
  key = this.prepKey(key);
127
+ var is_binary = this.storage.isBinaryKey(key);
110
128
 
111
- if (this.storage.isBinaryKey(key)) {
129
+ if (is_binary) {
112
130
  this.logDebug(9, "Storing Redis Binary Object: " + key, '' + value.length + ' bytes');
113
131
  }
114
132
  else {
115
133
  this.logDebug(9, "Storing Redis JSON Object: " + key, this.debugLevel(10) ? value : null);
116
- value = JSON.stringify( value );
134
+ value = Buffer.from( JSON.stringify( value ) );
117
135
  }
118
136
 
119
137
  this.redis.set( key, value, function(err) {
@@ -123,6 +141,11 @@ module.exports = Class.create({
123
141
  }
124
142
  else self.logDebug(9, "Store complete: " + key);
125
143
 
144
+ // possibly cache in LRU
145
+ if (!err && self.cache && !is_binary) {
146
+ self.cache.set( key, value, { date: Tools.timeNow(true) } );
147
+ }
148
+
126
149
  if (callback) callback(err);
127
150
  } );
128
151
  },
@@ -152,7 +175,30 @@ module.exports = Class.create({
152
175
  // The Redis API has no way to head / ping an object.
153
176
  // So, we have to do this the RAM-hard way...
154
177
 
155
- this.redis.get( key, function(err, data) {
178
+ // check cache first
179
+ if (this.cache && this.cache.has(key)) {
180
+ var item = this.cache.getMeta(key);
181
+
182
+ if (item.value.length == 0) {
183
+ process.nextTick( function() {
184
+ var err = new Error("Failed to head key: " + key + ": Not found");
185
+ err.code = "NoSuchKey";
186
+ callback( err );
187
+ } );
188
+ }
189
+ else {
190
+ process.nextTick( function() {
191
+ self.logDebug(9, "Cached head complete: " + key);
192
+ callback( null, {
193
+ mod: item.date,
194
+ len: item.value.length
195
+ } );
196
+ } );
197
+ }
198
+ return;
199
+ } // cache
200
+
201
+ this.redis.getBuffer( key, function(err, data) {
156
202
  if (err) {
157
203
  // an actual error
158
204
  err.message = "Failed to head key: " + key + ": " + err;
@@ -164,7 +210,12 @@ module.exports = Class.create({
164
210
  // always use "NoSuchKey" in error code
165
211
  var err = new Error("Failed to head key: " + key + ": Not found");
166
212
  err.code = "NoSuchKey";
167
-
213
+
214
+ if (self.cache && !self.storage.isBinaryKey(key)) {
215
+ // store 'empty' stub in cache
216
+ self.cache.set( key, Buffer.alloc(0), { date: 0 } );
217
+ }
218
+
168
219
  callback( err, null );
169
220
  }
170
221
  else {
@@ -177,44 +228,79 @@ module.exports = Class.create({
177
228
  // fetch Redis value given key
178
229
  var self = this;
179
230
  key = this.prepKey(key);
231
+ var is_binary = this.storage.isBinaryKey(key);
180
232
 
181
233
  this.logDebug(9, "Fetching Redis Object: " + key);
182
234
 
183
- var func = this.storage.isBinaryKey(key) ? 'getBuffer' : 'get';
184
- this.redis[func]( key, function(err, result) {
185
- if (!result) {
186
- if (err) {
187
- // an actual error
188
- err.message = "Failed to fetch key: " + key + ": " + err;
189
- self.logError('redis', ''+err);
190
- callback( err, null );
191
- }
192
- else {
193
- // record not found
194
- // always use "NoSuchKey" in error code
235
+ // check cache first
236
+ if (this.cache && !is_binary && this.cache.has(key)) {
237
+ var item = this.cache.getMeta(key);
238
+
239
+ if (item.value.length == 0) {
240
+ process.nextTick( function() {
195
241
  var err = new Error("Failed to fetch key: " + key + ": Not found");
196
242
  err.code = "NoSuchKey";
197
-
198
- callback( err, null );
199
- }
243
+ callback( err );
244
+ } );
200
245
  }
201
246
  else {
202
- if (self.storage.isBinaryKey(key)) {
203
- self.logDebug(9, "Binary fetch complete: " + key, '' + result.length + ' bytes');
204
- }
205
- else {
206
- try { result = JSON.parse( result.toString() ); }
247
+ process.nextTick( function() {
248
+ var data = null;
249
+ try { data = JSON.parse( item.value.toString() ); }
207
250
  catch (err) {
208
251
  self.logError('redis', "Failed to parse JSON record: " + key + ": " + err);
209
252
  callback( err, null );
210
253
  return;
211
254
  }
212
- self.logDebug(9, "JSON fetch complete: " + key, self.debugLevel(10) ? result : null);
255
+ self.logDebug(9, "Cached JSON fetch complete: " + key, self.debugLevel(10) ? data : null);
256
+
257
+ callback( null, data );
258
+ } );
259
+ }
260
+ return;
261
+ } // cache
262
+
263
+ this.redis.getBuffer( key, function(err, result) {
264
+ if (err) {
265
+ // an actual error
266
+ err.message = "Failed to fetch key: " + key + ": " + err;
267
+ self.logError('redis', ''+err);
268
+ return callback( err, null );
269
+ }
270
+ else if (!result || !result.length) {
271
+ // record not found
272
+ // always use "NoSuchKey" in error code
273
+ var err = new Error("Failed to fetch key: " + key + ": Not found");
274
+ err.code = "NoSuchKey";
275
+
276
+ if (self.cache && !is_binary) {
277
+ // store 'empty' stub in cache
278
+ self.cache.set( key, Buffer.alloc(0), { date: 0 } );
213
279
  }
214
280
 
215
- callback( null, result );
281
+ return callback( err, null );
216
282
  }
217
- } );
283
+
284
+ if (is_binary) {
285
+ self.logDebug(9, "Binary fetch complete: " + key, '' + result.length + ' bytes');
286
+ }
287
+ else {
288
+ // possibly cache in LRU
289
+ if (self.cache) {
290
+ self.cache.set( key, result, { date: Tools.timeNow(true) } );
291
+ }
292
+
293
+ try { result = JSON.parse( result.toString() ); }
294
+ catch (err) {
295
+ self.logError('redis', "Failed to parse JSON record: " + key + ": " + err);
296
+ callback( err, null );
297
+ return;
298
+ }
299
+ self.logDebug(9, "JSON fetch complete: " + key, self.debugLevel(10) ? result : null);
300
+ }
301
+
302
+ callback( null, result );
303
+ } ); // redis.getBuffer
218
304
  },
219
305
 
220
306
  getBuffer: function(key, callback) {
@@ -303,7 +389,6 @@ module.exports = Class.create({
303
389
  end = buf.length ? buf.length - 1 : 0;
304
390
  }
305
391
  if (isNaN(start) || isNaN(end) || (start < 0) || (start >= buf.length) || (end < start) || (end >= buf.length)) {
306
- download.destroy();
307
392
  callback( new Error("Invalid byte range (" + start + '-' + end + ") for key: " + key + " (len: " + buf.length + ")"), null );
308
393
  return;
309
394
  }
@@ -318,6 +403,7 @@ module.exports = Class.create({
318
403
  // delete Redis key given key
319
404
  var self = this;
320
405
  key = this.prepKey(key);
406
+ var is_binary = this.storage.isBinaryKey(key);
321
407
 
322
408
  this.logDebug(9, "Deleting Redis Object: " + key);
323
409
 
@@ -325,11 +411,24 @@ module.exports = Class.create({
325
411
  if (!err && !deleted) {
326
412
  err = new Error("Failed to fetch key: " + key + ": Not found");
327
413
  err.code = "NoSuchKey";
414
+
415
+ if (self.cache && !is_binary) {
416
+ // store 'empty' stub in cache
417
+ self.cache.set( key, Buffer.alloc(0), { date: 0 } );
418
+ }
328
419
  }
329
420
  if (err) {
330
421
  self.logError('redis', "Failed to delete object: " + key + ": " + err);
331
422
  }
332
- else self.logDebug(9, "Delete complete: " + key);
423
+ else {
424
+ self.logDebug(9, "Delete complete: " + key);
425
+
426
+ // possibly "delete" from LRU cache as well
427
+ if (self.cache && !is_binary) {
428
+ // store 'empty' stub in cache
429
+ self.cache.set( key, Buffer.alloc(0), { date: 0 } );
430
+ }
431
+ }
333
432
 
334
433
  callback(err);
335
434
  } );
package/engines/S3.js CHANGED
@@ -149,7 +149,7 @@ module.exports = Class.create({
149
149
  }
150
150
  else {
151
151
  this.logDebug(9, "Storing S3 JSON Object: " + key, this.debugLevel(10) ? params.Body : null);
152
- params.Body = this.pretty ? JSON.stringify( params.Body, null, "\t" ) : JSON.stringify( params.Body );
152
+ params.Body = Buffer.from( this.pretty ? JSON.stringify( params.Body, null, "\t" ) : JSON.stringify( params.Body ) );
153
153
  params.ContentType = 'application/json';
154
154
  }
155
155
 
@@ -257,13 +257,22 @@ module.exports = Class.create({
257
257
  if (this.cache && this.cache.has(orig_key)) {
258
258
  var item = this.cache.getMeta(orig_key);
259
259
 
260
- process.nextTick( function() {
261
- self.logDebug(9, "Cached head complete: " + orig_key);
262
- callback( null, {
263
- mod: item.date,
264
- len: item.value.length
260
+ if (item.value.length == 0) {
261
+ process.nextTick( function() {
262
+ var err = new Error("Failed to head key: " + key + ": Not found");
263
+ err.code = "NoSuchKey";
264
+ callback( err );
265
265
  } );
266
- } );
266
+ }
267
+ else {
268
+ process.nextTick( function() {
269
+ self.logDebug(9, "Cached head complete: " + orig_key);
270
+ callback( null, {
271
+ mod: item.date,
272
+ len: item.value.length
273
+ } );
274
+ } );
275
+ }
267
276
  return;
268
277
  } // cache
269
278
 
@@ -288,6 +297,11 @@ module.exports = Class.create({
288
297
  // always include "Not found" in error message
289
298
  err = new Error("Failed to head key: " + key + ": Not found");
290
299
  err.code = "NoSuchKey";
300
+
301
+ if (self.cache && !self.storage.isBinaryKey(orig_key)) {
302
+ // store 'empty' stub in cache
303
+ self.cache.set( orig_key, Buffer.alloc(0), { date: 0 } );
304
+ }
291
305
  }
292
306
  else if (err.name == 'SlowDown') {
293
307
  // special behavior for SlowDown errors
@@ -315,19 +329,29 @@ module.exports = Class.create({
315
329
 
316
330
  // check cache first
317
331
  if (this.cache && !is_binary && this.cache.has(orig_key)) {
318
- var data = this.cache.get(orig_key);
332
+ var item = this.cache.getMeta(orig_key);
319
333
 
320
- process.nextTick( function() {
321
- try { data = JSON.parse( data ); }
322
- catch (e) {
323
- self.logError('file', "Failed to parse JSON record: " + orig_key + ": " + e);
324
- callback( e, null );
325
- return;
326
- }
327
- self.logDebug(9, "Cached JSON fetch complete: " + orig_key, self.debugLevel(10) ? data : null);
328
-
329
- callback( null, data );
330
- } );
334
+ if (item.value.length == 0) {
335
+ process.nextTick( function() {
336
+ var err = new Error("Failed to fetch key: " + key + ": Not found");
337
+ err.code = "NoSuchKey";
338
+ callback( err );
339
+ } );
340
+ }
341
+ else {
342
+ process.nextTick( function() {
343
+ var data = null;
344
+ try { data = JSON.parse( item.value.toString() ); }
345
+ catch (e) {
346
+ self.logError('file', "Failed to parse JSON record: " + orig_key + ": " + e);
347
+ callback( e, null );
348
+ return;
349
+ }
350
+ self.logDebug(9, "Cached JSON fetch complete: " + orig_key, self.debugLevel(10) ? data : null);
351
+
352
+ callback( null, data );
353
+ } );
354
+ }
331
355
  return;
332
356
  } // cache
333
357
 
@@ -350,14 +374,12 @@ module.exports = Class.create({
350
374
  self.logDebug(9, "Binary fetch complete: " + key, '' + body.length + ' bytes');
351
375
  }
352
376
  else {
353
- body = body.toString();
354
-
355
377
  // possibly cache in LRU
356
378
  if (self.cache) {
357
379
  self.cache.set( orig_key, body, { date: Tools.timeNow(true) } );
358
380
  }
359
381
 
360
- try { body = JSON.parse( body ); }
382
+ try { body = JSON.parse( body.toString() ); }
361
383
  catch (e) {
362
384
  self.logError('s3', "Failed to parse JSON record: " + key + ": " + e);
363
385
  callback( e, null );
@@ -378,6 +400,11 @@ module.exports = Class.create({
378
400
  // always include "Not found" in error message
379
401
  err = new Error("Failed to fetch key: " + key + ": Not found");
380
402
  err.code = "NoSuchKey";
403
+
404
+ if (self.cache && !is_binary) {
405
+ // store 'empty' stub in cache
406
+ self.cache.set( orig_key, Buffer.alloc(0), { date: 0 } );
407
+ }
381
408
  }
382
409
  else if (err.name == 'SlowDown') {
383
410
  // special behavior for SlowDown errors
@@ -577,9 +604,10 @@ module.exports = Class.create({
577
604
  self.logDebug(9, "Delete complete: " + key);
578
605
  self.storage.emit('billing', 's3_delete', 1);
579
606
 
580
- // possibly delete from LRU cache as well
581
- if (self.cache && self.cache.has(orig_key)) {
582
- self.cache.delete(orig_key);
607
+ // possibly "delete" from LRU cache as well
608
+ if (self.cache && !self.storage.isBinaryKey(orig_key)) {
609
+ // store 'empty' stub in cache
610
+ self.cache.set( orig_key, Buffer.alloc(0), { date: 0 } );
583
611
  }
584
612
 
585
613
  if (callback) process.nextTick( function() { callback(null, data); } );