1#![doc = include_str!("../README.md")]
2
3pub use prolly::{
4 RemoteBatchOp, RemoteManifestUpdate, RemoteNamedRoot, RemoteProllyStore, RemoteRootCondition,
5 RemoteRootWrite, RemoteStoreBackend, RemoteTransactionConflict, RemoteTransactionUpdate,
6};
7
8pub mod redis {
10 use redis_client::{ErrorKind, RedisError, Script, Value};
11
12 use crate::{
13 RemoteBatchOp, RemoteManifestUpdate, RemoteNamedRoot, RemoteRootCondition, RemoteRootWrite,
14 RemoteStoreBackend, RemoteTransactionConflict, RemoteTransactionUpdate,
15 };
16
17 pub type RedisStore = crate::RemoteProllyStore<RedisBackend>;
22
23 #[derive(Clone)]
25 pub struct RedisBackend {
26 connection: redis_client::aio::ConnectionManager,
27 key_prefix: Vec<u8>,
28 read_parallelism: usize,
29 }
30
31 impl std::fmt::Debug for RedisBackend {
32 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
33 f.debug_struct("RedisBackend")
34 .field("key_prefix", &self.key_prefix)
35 .field("read_parallelism", &self.read_parallelism)
36 .finish_non_exhaustive()
37 }
38 }
39
40 impl RedisBackend {
41 pub fn new(connection: redis_client::aio::ConnectionManager) -> Self {
43 Self {
44 connection,
45 key_prefix: DEFAULT_KEY_PREFIX.to_vec(),
46 read_parallelism: DEFAULT_READ_PARALLELISM,
47 }
48 }
49
50 pub async fn connect(redis_url: &str) -> Result<Self, RedisError> {
52 let client = redis_client::Client::open(redis_url)?;
53 Self::from_client(client).await
54 }
55
56 pub async fn from_client(client: redis_client::Client) -> Result<Self, RedisError> {
58 Ok(Self::new(client.get_connection_manager().await?))
59 }
60
61 pub fn connection(&self) -> &redis_client::aio::ConnectionManager {
63 &self.connection
64 }
65
66 pub fn key_prefix(&self) -> &[u8] {
68 &self.key_prefix
69 }
70
71 pub fn with_key_prefix(mut self, key_prefix: impl Into<Vec<u8>>) -> Self {
75 self.key_prefix = key_prefix.into();
76 self
77 }
78
79 pub fn with_read_parallelism(mut self, read_parallelism: usize) -> Self {
81 self.read_parallelism = read_parallelism.max(1);
82 self
83 }
84
85 pub async fn clear_namespace(&self) -> Result<(), RedisError> {
89 if self.key_prefix.is_empty() {
90 return Err(redis_type_error(
91 "refusing to clear an empty Redis key prefix",
92 ));
93 }
94
95 let mut pattern = self.key_prefix.clone();
96 pattern.push(b'*');
97 let keys = self.scan_keys(&pattern).await?;
98 self.delete_keys(&keys).await
99 }
100
101 fn node_key(&self, key: &[u8]) -> Vec<u8> {
102 self.family_key(NODE_FAMILY, key)
103 }
104
105 fn root_key(&self, name: &[u8]) -> Vec<u8> {
106 self.family_key(ROOT_FAMILY, name)
107 }
108
109 fn hint_key(&self, namespace: &[u8], key: &[u8]) -> Vec<u8> {
110 let mut redis_key = self.family_key(HINT_FAMILY, &[]);
111 redis_key.extend_from_slice(&(namespace.len() as u64).to_be_bytes());
112 redis_key.extend_from_slice(namespace);
113 redis_key.extend_from_slice(key);
114 redis_key
115 }
116
117 fn family_key(&self, family: &[u8], suffix: &[u8]) -> Vec<u8> {
118 let mut key = Vec::with_capacity(self.key_prefix.len() + family.len() + suffix.len());
119 key.extend_from_slice(&self.key_prefix);
120 key.extend_from_slice(family);
121 key.extend_from_slice(suffix);
122 key
123 }
124
125 fn family_prefix(&self, family: &[u8]) -> Vec<u8> {
126 self.family_key(family, &[])
127 }
128
129 fn family_pattern(&self, family: &[u8]) -> Vec<u8> {
130 let mut pattern = self.family_prefix(family);
131 pattern.push(b'*');
132 pattern
133 }
134
135 async fn scan_keys(&self, pattern: &[u8]) -> Result<Vec<Vec<u8>>, RedisError> {
136 let mut connection = self.connection.clone();
137 let mut cursor = 0_u64;
138 let mut keys = Vec::new();
139
140 loop {
141 let (next_cursor, batch): (u64, Vec<Vec<u8>>) = redis_client::cmd("SCAN")
142 .arg(cursor)
143 .arg("MATCH")
144 .arg(pattern)
145 .arg("COUNT")
146 .arg(SCAN_COUNT)
147 .query_async(&mut connection)
148 .await?;
149 keys.extend(batch);
150 if next_cursor == 0 {
151 break;
152 }
153 cursor = next_cursor;
154 }
155
156 Ok(keys)
157 }
158
159 async fn delete_keys(&self, keys: &[Vec<u8>]) -> Result<(), RedisError> {
160 if keys.is_empty() {
161 return Ok(());
162 }
163
164 let mut connection = self.connection.clone();
165 for chunk in keys.chunks(DELETE_CHUNK_SIZE) {
166 let mut command = redis_client::cmd("DEL");
167 for key in chunk {
168 command.arg(key.as_slice());
169 }
170 command.query_async::<()>(&mut connection).await?;
171 }
172 Ok(())
173 }
174 }
175
176 impl RemoteStoreBackend for RedisBackend {
177 type Error = RedisError;
178
179 async fn get_node(&self, key: &[u8]) -> Result<Option<Vec<u8>>, Self::Error> {
180 let mut connection = self.connection.clone();
181 redis_client::cmd("GET")
182 .arg(self.node_key(key))
183 .query_async(&mut connection)
184 .await
185 }
186
187 async fn put_node(&self, key: &[u8], value: &[u8]) -> Result<(), Self::Error> {
188 let mut connection = self.connection.clone();
189 redis_client::cmd("SET")
190 .arg(self.node_key(key))
191 .arg(value)
192 .query_async::<()>(&mut connection)
193 .await
194 }
195
196 async fn delete_node(&self, key: &[u8]) -> Result<(), Self::Error> {
197 let mut connection = self.connection.clone();
198 redis_client::cmd("DEL")
199 .arg(self.node_key(key))
200 .query_async::<()>(&mut connection)
201 .await
202 }
203
204 async fn batch_nodes(&self, ops: &[RemoteBatchOp<'_>]) -> Result<(), Self::Error> {
205 if ops.is_empty() {
206 return Ok(());
207 }
208
209 let mut pipeline = redis_client::pipe();
210 pipeline.atomic();
211 for op in ops {
212 match op {
213 RemoteBatchOp::Upsert { key, value } => {
214 pipeline
215 .cmd("SET")
216 .arg(self.node_key(key))
217 .arg(*value)
218 .ignore();
219 }
220 RemoteBatchOp::Delete { key } => {
221 pipeline.cmd("DEL").arg(self.node_key(key)).ignore();
222 }
223 }
224 }
225
226 let mut connection = self.connection.clone();
227 pipeline.query_async::<()>(&mut connection).await
228 }
229
230 async fn batch_get_nodes_ordered(
231 &self,
232 keys: &[&[u8]],
233 ) -> Result<Vec<Option<Vec<u8>>>, Self::Error> {
234 if keys.is_empty() {
235 return Ok(Vec::new());
236 }
237
238 let mut command = redis_client::cmd("MGET");
239 for key in keys {
240 command.arg(self.node_key(key));
241 }
242
243 let mut connection = self.connection.clone();
244 command.query_async(&mut connection).await
245 }
246
247 async fn batch_put_nodes(&self, entries: &[(&[u8], &[u8])]) -> Result<(), Self::Error> {
248 if entries.is_empty() {
249 return Ok(());
250 }
251
252 let mut pipeline = redis_client::pipe();
253 pipeline.atomic();
254 for (key, value) in entries {
255 pipeline
256 .cmd("SET")
257 .arg(self.node_key(key))
258 .arg(*value)
259 .ignore();
260 }
261
262 let mut connection = self.connection.clone();
263 pipeline.query_async::<()>(&mut connection).await
264 }
265
266 async fn list_node_cids(&self) -> Result<Vec<Vec<u8>>, Self::Error> {
267 let prefix = self.family_prefix(NODE_FAMILY);
268 let pattern = self.family_pattern(NODE_FAMILY);
269 let mut cids = self
270 .scan_keys(&pattern)
271 .await?
272 .into_iter()
273 .filter_map(|key| {
274 key.strip_prefix(prefix.as_slice())
275 .filter(|cid| cid.len() == 32)
276 .map(<[u8]>::to_vec)
277 })
278 .collect::<Vec<_>>();
279 cids.sort();
280 Ok(cids)
281 }
282
283 fn prefers_batch_reads(&self) -> bool {
284 true
285 }
286
287 fn read_parallelism(&self) -> usize {
288 self.read_parallelism
289 }
290
291 fn supports_hints(&self) -> bool {
292 true
293 }
294
295 async fn get_hint(
296 &self,
297 namespace: &[u8],
298 key: &[u8],
299 ) -> Result<Option<Vec<u8>>, Self::Error> {
300 let mut connection = self.connection.clone();
301 redis_client::cmd("GET")
302 .arg(self.hint_key(namespace, key))
303 .query_async(&mut connection)
304 .await
305 }
306
307 async fn put_hint(
308 &self,
309 namespace: &[u8],
310 key: &[u8],
311 value: &[u8],
312 ) -> Result<(), Self::Error> {
313 let mut connection = self.connection.clone();
314 redis_client::cmd("SET")
315 .arg(self.hint_key(namespace, key))
316 .arg(value)
317 .query_async::<()>(&mut connection)
318 .await
319 }
320
321 async fn batch_put_nodes_with_hint(
322 &self,
323 entries: &[(&[u8], &[u8])],
324 namespace: &[u8],
325 key: &[u8],
326 value: &[u8],
327 ) -> Result<(), Self::Error> {
328 let mut pipeline = redis_client::pipe();
329 pipeline.atomic();
330 for (key, value) in entries {
331 pipeline
332 .cmd("SET")
333 .arg(self.node_key(key))
334 .arg(*value)
335 .ignore();
336 }
337 pipeline
338 .cmd("SET")
339 .arg(self.hint_key(namespace, key))
340 .arg(value)
341 .ignore();
342
343 let mut connection = self.connection.clone();
344 pipeline.query_async::<()>(&mut connection).await
345 }
346
347 async fn get_root_manifest(&self, name: &[u8]) -> Result<Option<Vec<u8>>, Self::Error> {
348 let mut connection = self.connection.clone();
349 redis_client::cmd("GET")
350 .arg(self.root_key(name))
351 .query_async(&mut connection)
352 .await
353 }
354
355 async fn put_root_manifest(&self, name: &[u8], manifest: &[u8]) -> Result<(), Self::Error> {
356 let mut connection = self.connection.clone();
357 redis_client::cmd("SET")
358 .arg(self.root_key(name))
359 .arg(manifest)
360 .query_async::<()>(&mut connection)
361 .await
362 }
363
364 async fn delete_root_manifest(&self, name: &[u8]) -> Result<(), Self::Error> {
365 let mut connection = self.connection.clone();
366 redis_client::cmd("DEL")
367 .arg(self.root_key(name))
368 .query_async::<()>(&mut connection)
369 .await
370 }
371
372 async fn compare_and_swap_root_manifest(
373 &self,
374 name: &[u8],
375 expected: Option<&[u8]>,
376 new: Option<&[u8]>,
377 ) -> Result<RemoteManifestUpdate, Self::Error> {
378 let script = Script::new(ROOT_CAS_LUA);
379 let mut invocation = script.prepare_invoke();
380 invocation
381 .key(self.root_key(name))
382 .arg(if expected.is_some() { b"1" } else { b"0" }.as_slice())
383 .arg(expected.unwrap_or_default())
384 .arg(if new.is_some() { b"1" } else { b"0" }.as_slice())
385 .arg(new.unwrap_or_default());
386
387 let mut connection = self.connection.clone();
388 let response: Value = invocation.invoke_async(&mut connection).await?;
389 parse_root_cas_response(response)
390 }
391
392 async fn list_root_manifests(&self) -> Result<Vec<RemoteNamedRoot>, Self::Error> {
393 let prefix = self.family_prefix(ROOT_FAMILY);
394 let pattern = self.family_pattern(ROOT_FAMILY);
395 let mut names = self
396 .scan_keys(&pattern)
397 .await?
398 .into_iter()
399 .filter_map(|key| key.strip_prefix(prefix.as_slice()).map(<[u8]>::to_vec))
400 .collect::<Vec<_>>();
401 names.sort();
402
403 let mut roots = Vec::with_capacity(names.len());
404 for name in names {
405 if let Some(manifest) = self.get_root_manifest(&name).await? {
406 roots.push(RemoteNamedRoot::new(name, manifest));
407 }
408 }
409 Ok(roots)
410 }
411
412 fn supports_transactions(&self) -> bool {
413 true
414 }
415
416 async fn commit_transaction(
417 &self,
418 node_writes: &[RemoteBatchOp<'_>],
419 root_conditions: &[RemoteRootCondition],
420 root_writes: &[RemoteRootWrite],
421 ) -> Result<RemoteTransactionUpdate, Self::Error> {
422 let script = Script::new(TRANSACTION_COMMIT_LUA);
423 let mut invocation = script.prepare_invoke();
424 for condition in root_conditions {
425 invocation.key(self.root_key(&condition.name));
426 }
427 for write in node_writes {
428 match write {
429 RemoteBatchOp::Upsert { key, .. } | RemoteBatchOp::Delete { key } => {
430 invocation.key(self.node_key(key));
431 }
432 }
433 }
434 for write in root_writes {
435 match write {
436 RemoteRootWrite::Put { name, .. } | RemoteRootWrite::Delete { name } => {
437 invocation.key(self.root_key(name));
438 }
439 }
440 }
441
442 invocation
443 .arg(root_conditions.len())
444 .arg(node_writes.len())
445 .arg(root_writes.len());
446 for condition in root_conditions {
447 invocation
448 .arg(
449 if condition.expected.is_some() {
450 b"1"
451 } else {
452 b"0"
453 }
454 .as_slice(),
455 )
456 .arg(condition.expected.as_deref().unwrap_or_default());
457 }
458 for write in node_writes {
459 match write {
460 RemoteBatchOp::Upsert { value, .. } => {
461 invocation.arg("upsert").arg(*value);
462 }
463 RemoteBatchOp::Delete { .. } => {
464 invocation.arg("delete");
465 }
466 }
467 }
468 for write in root_writes {
469 match write {
470 RemoteRootWrite::Put { manifest, .. } => {
471 invocation.arg("put").arg(manifest);
472 }
473 RemoteRootWrite::Delete { .. } => {
474 invocation.arg("delete");
475 }
476 }
477 }
478
479 let mut connection = self.connection.clone();
480 let response: Value = invocation.invoke_async(&mut connection).await?;
481 parse_transaction_response(response, root_conditions)
482 }
483 }
484
485 fn parse_root_cas_response(response: Value) -> Result<RemoteManifestUpdate, RedisError> {
486 let Value::Array(values) = response else {
487 return Err(redis_type_error("root CAS script returned a non-array"));
488 };
489 let [applied, current] = values
490 .try_into()
491 .map_err(|_| redis_type_error("root CAS script returned wrong arity"))?;
492
493 if value_to_bool(applied)? {
494 return Ok(RemoteManifestUpdate::Applied);
495 }
496
497 Ok(RemoteManifestUpdate::Conflict {
498 current: value_to_optional_bytes(current)?,
499 })
500 }
501
502 fn value_to_bool(value: Value) -> Result<bool, RedisError> {
503 match value {
504 Value::Int(0) => Ok(false),
505 Value::Int(1) => Ok(true),
506 Value::Boolean(value) => Ok(value),
507 other => Err(redis_type_error(format!(
508 "root CAS script returned invalid applied flag: {other:?}"
509 ))),
510 }
511 }
512
513 fn value_to_usize(value: Value) -> Result<usize, RedisError> {
514 match value {
515 Value::Int(value) if value >= 0 => Ok(value as usize),
516 other => Err(redis_type_error(format!(
517 "transaction script returned invalid conflict index: {other:?}"
518 ))),
519 }
520 }
521
522 fn value_to_optional_bytes(value: Value) -> Result<Option<Vec<u8>>, RedisError> {
523 match value {
524 Value::Nil => Ok(None),
525 Value::Boolean(false) => Ok(None),
526 Value::BulkString(bytes) => Ok(Some(bytes)),
527 other => Err(redis_type_error(format!(
528 "root CAS script returned invalid current manifest: {other:?}"
529 ))),
530 }
531 }
532
533 fn parse_transaction_response(
534 response: Value,
535 root_conditions: &[RemoteRootCondition],
536 ) -> Result<RemoteTransactionUpdate, RedisError> {
537 let Value::Array(values) = response else {
538 return Err(redis_type_error("transaction script returned a non-array"));
539 };
540 let [applied, conflict_index, current] = values
541 .try_into()
542 .map_err(|_| redis_type_error("transaction script returned wrong arity"))?;
543
544 if value_to_bool(applied)? {
545 return Ok(RemoteTransactionUpdate::Applied);
546 }
547
548 let index = value_to_usize(conflict_index)?;
549 if index == 0 || index > root_conditions.len() {
550 return Err(redis_type_error(format!(
551 "transaction script returned out-of-range conflict index: {index}"
552 )));
553 }
554 let condition = &root_conditions[index - 1];
555 Ok(RemoteTransactionUpdate::Conflict(
556 RemoteTransactionConflict::new(
557 condition.name.clone(),
558 condition.expected.clone(),
559 value_to_optional_bytes(current)?,
560 ),
561 ))
562 }
563
564 fn redis_type_error(detail: impl Into<String>) -> RedisError {
565 (
566 ErrorKind::TypeError,
567 "unexpected Redis adapter response",
568 detail.into(),
569 )
570 .into()
571 }
572
573 const DEFAULT_KEY_PREFIX: &[u8] = b"prolly:";
574 const DEFAULT_READ_PARALLELISM: usize = 16;
575 const SCAN_COUNT: usize = 1024;
576 const DELETE_CHUNK_SIZE: usize = 512;
577
578 const NODE_FAMILY: &[u8] = b"node:";
579 const ROOT_FAMILY: &[u8] = b"root:";
580 const HINT_FAMILY: &[u8] = b"hint:";
581
582 pub const NODE_KEY_PREFIX: &str = "prolly:node:";
584 pub const ROOT_KEY_PREFIX: &str = "prolly:root:";
586 pub const HINT_KEY_PREFIX: &str = "prolly:hint:";
588
589 const ROOT_CAS_LUA: &str = r#"
590local current = redis.call('GET', KEYS[1])
591local has_expected = ARGV[1]
592local expected = ARGV[2]
593local has_new = ARGV[3]
594local new_value = ARGV[4]
595
596if has_expected == '1' then
597 if current == false or current ~= expected then
598 return {0, current}
599 end
600else
601 if current ~= false then
602 return {0, current}
603 end
604end
605
606if has_new == '1' then
607 redis.call('SET', KEYS[1], new_value)
608else
609 redis.call('DEL', KEYS[1])
610end
611
612return {1, false}
613"#;
614
615 const TRANSACTION_COMMIT_LUA: &str = r#"
616local condition_count = tonumber(ARGV[1])
617local node_write_count = tonumber(ARGV[2])
618local root_write_count = tonumber(ARGV[3])
619local arg_index = 4
620
621for i = 1, condition_count do
622 local current = redis.call('GET', KEYS[i])
623 local has_expected = ARGV[arg_index]
624 local expected = ARGV[arg_index + 1]
625 arg_index = arg_index + 2
626
627 if has_expected == '1' then
628 if current == false or current ~= expected then
629 return {0, i, current}
630 end
631 else
632 if current ~= false then
633 return {0, i, current}
634 end
635 end
636end
637
638local node_key_offset = condition_count
639for i = 1, node_write_count do
640 local kind = ARGV[arg_index]
641 arg_index = arg_index + 1
642 local key = KEYS[node_key_offset + i]
643
644 if kind == 'upsert' then
645 redis.call('SET', key, ARGV[arg_index])
646 arg_index = arg_index + 1
647 elseif kind == 'delete' then
648 redis.call('DEL', key)
649 else
650 error('unknown transaction node op: ' .. tostring(kind))
651 end
652end
653
654local root_key_offset = condition_count + node_write_count
655for i = 1, root_write_count do
656 local kind = ARGV[arg_index]
657 arg_index = arg_index + 1
658 local key = KEYS[root_key_offset + i]
659
660 if kind == 'put' then
661 redis.call('SET', key, ARGV[arg_index])
662 arg_index = arg_index + 1
663 elseif kind == 'delete' then
664 redis.call('DEL', key)
665 else
666 error('unknown transaction root op: ' .. tostring(kind))
667 end
668end
669
670return {1, 0, false}
671"#;
672}
673
674pub use redis::*;