1use std::{
37 collections::VecDeque,
38 fmt::{Debug, Write as _},
39 ops::ControlFlow,
40 pin::Pin,
41 sync::mpsc::{self, SyncSender},
42 time::Duration,
43};
44
45use ahash::AHashMap;
46use anyhow::Context;
47use bytes::Bytes;
48use nautilus_common::{
49 cache::{
50 CacheConfig,
51 database::{CacheDatabaseAdapter, CacheDatabaseFactory, CacheMap},
52 },
53 enums::SerializationEncoding,
54 live::get_runtime,
55 logging::{log_task_awaiting, log_task_started, log_task_stopped},
56 signal::Signal,
57};
58use nautilus_core::{UUID4, UnixNanos, correctness::check_slice_not_empty};
59use nautilus_cryptography::providers::install_cryptographic_provider;
60use nautilus_model::{
61 accounts::AccountAny,
62 data::{
63 Bar, CustomData, DataType, FundingRateUpdate, HasTsInit, InstrumentClose, QuoteTick,
64 TradeTick,
65 },
66 events::{
67 AccountState, OrderEventAny, OrderFilled, OrderSnapshot,
68 position::snapshot::PositionSnapshot,
69 },
70 identifiers::{
71 AccountId, ActorId, ClientId, ClientOrderId, InstrumentId, PositionId, StrategyId,
72 TraderId, VenueOrderId,
73 },
74 instruments::{Instrument, InstrumentAny, SyntheticInstrument},
75 orderbook::OrderBook,
76 orders::{Order, OrderAny},
77 position::Position,
78 types::{Currency, Money},
79};
80use redis::{AsyncCommands, Pipeline, aio::ConnectionManager};
81use serde::{Deserialize, Serialize};
82use ustr::Ustr;
83
84use super::{REDIS_DELIMITER, REDIS_FLUSHDB, get_index_key};
85use crate::redis::{RedisConnectionConfig, create_redis_connection, queries::DatabaseQueries};
86
87const CACHE_READ: &str = "cache-read";
89const CACHE_WRITE: &str = "cache-write";
90const CACHE_PROCESS: &str = "cache-process";
91
92const FAILED_TX_CHANNEL: &str = "Failed to send to channel";
94
95const INDEX: &str = "index";
97const GENERAL: &str = "general";
98const CURRENCIES: &str = "currencies";
99const INSTRUMENTS: &str = "instruments";
100const INSTRUMENT_CLOSES: &str = "instrument_closes";
101const SYNTHETICS: &str = "synthetics";
102const ACCOUNTS: &str = "accounts";
103const ORDERS: &str = "orders";
104const POSITIONS: &str = "positions";
105const ACTORS: &str = "actors";
106const STRATEGIES: &str = "strategies";
107const SNAPSHOTS: &str = "snapshots";
108const HEALTH: &str = "health";
109const CUSTOM: &str = "custom";
110
111const INDEX_ORDER_IDS: &str = "index:order_ids";
113const INDEX_ORDER_POSITION: &str = "index:order_position";
114const INDEX_ORDER_CLIENT: &str = "index:order_client";
115const INDEX_ORDERS: &str = "index:orders";
116const INDEX_ORDERS_OPEN: &str = "index:orders_open";
117const INDEX_ORDERS_CLOSED: &str = "index:orders_closed";
118const INDEX_ORDERS_EMULATED: &str = "index:orders_emulated";
119const INDEX_ORDERS_INFLIGHT: &str = "index:orders_inflight";
120const INDEX_POSITIONS: &str = "index:positions";
121const INDEX_POSITIONS_OPEN: &str = "index:positions_open";
122const INDEX_POSITIONS_CLOSED: &str = "index:positions_closed";
123
124#[cfg_attr(
128 feature = "python",
129 expect(
130 clippy::unsafe_derive_deserialize,
131 reason = "config deserializes plain fields; unsafe methods come from generated PyO3 integration"
132 )
133)]
134#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
135#[serde(default, deny_unknown_fields)]
136#[cfg_attr(
137 feature = "python",
138 pyo3::pyclass(module = "nautilus_trader.infrastructure", from_py_object)
139)]
140#[cfg_attr(
141 feature = "python",
142 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.infrastructure")
143)]
144pub struct RedisCacheConfig {
145 pub host: Option<String>,
147 pub port: Option<u16>,
149 pub username: Option<String>,
151 pub password: Option<String>,
153 pub ssl: bool,
155 pub connection_timeout: u16,
157 pub response_timeout: u16,
159 pub number_of_retries: usize,
161 pub exponent_base: u64,
163 pub max_delay: u64,
165 pub factor: u64,
167}
168
169impl Debug for RedisCacheConfig {
170 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
171 let redacted = self.password.as_ref().map(|_| "***");
172 f.debug_struct(stringify!(RedisCacheConfig))
173 .field("host", &self.host)
174 .field("port", &self.port)
175 .field("username", &self.username)
176 .field("password", &redacted)
177 .field("ssl", &self.ssl)
178 .field("connection_timeout", &self.connection_timeout)
179 .field("response_timeout", &self.response_timeout)
180 .field("number_of_retries", &self.number_of_retries)
181 .field("exponent_base", &self.exponent_base)
182 .field("max_delay", &self.max_delay)
183 .field("factor", &self.factor)
184 .finish()
185 }
186}
187
188impl Default for RedisCacheConfig {
189 fn default() -> Self {
190 Self {
191 host: None,
192 port: None,
193 username: None,
194 password: None,
195 ssl: false,
196 connection_timeout: 20,
197 response_timeout: 20,
198 number_of_retries: 100,
199 exponent_base: 2,
200 max_delay: 1000,
201 factor: 2,
202 }
203 }
204}
205
206impl RedisConnectionConfig for RedisCacheConfig {
207 fn host(&self) -> Option<&str> {
208 self.host.as_deref()
209 }
210
211 fn port(&self) -> Option<u16> {
212 self.port
213 }
214
215 fn username(&self) -> Option<&str> {
216 self.username.as_deref()
217 }
218
219 fn password(&self) -> Option<&str> {
220 self.password.as_deref()
221 }
222
223 fn ssl(&self) -> bool {
224 self.ssl
225 }
226
227 fn connection_timeout(&self) -> u16 {
228 self.connection_timeout
229 }
230
231 fn response_timeout(&self) -> u16 {
232 self.response_timeout
233 }
234
235 fn number_of_retries(&self) -> usize {
236 self.number_of_retries
237 }
238
239 fn exponent_base(&self) -> u64 {
240 self.exponent_base
241 }
242
243 fn max_delay(&self) -> u64 {
244 self.max_delay
245 }
246
247 fn factor(&self) -> u64 {
248 self.factor
249 }
250}
251
252#[derive(Clone, Debug)]
254pub enum DatabaseOperation {
255 Insert,
256 Update,
257 UpdateOrder,
258 ReplaceList,
259 Delete,
260 Flush(SyncSender<()>),
261 Close,
262}
263
264#[derive(Clone, Debug)]
266pub struct DatabaseCommand {
267 pub op_type: DatabaseOperation,
269 pub key: Option<String>,
271 pub payload: Option<Vec<Bytes>>,
273}
274
275impl DatabaseCommand {
276 #[must_use]
278 pub const fn new(op_type: DatabaseOperation, key: String, payload: Option<Vec<Bytes>>) -> Self {
279 Self {
280 op_type,
281 key: Some(key),
282 payload,
283 }
284 }
285
286 #[must_use]
288 pub const fn close() -> Self {
289 Self {
290 op_type: DatabaseOperation::Close,
291 key: None,
292 payload: None,
293 }
294 }
295}
296
297#[cfg_attr(
298 feature = "python",
299 pyo3::pyclass(module = "nautilus_trader.infrastructure")
300)]
301pub struct RedisCacheDatabase {
302 pub con: ConnectionManager,
303 pub trader_id: TraderId,
304 pub trader_key: String,
305 pub encoding: SerializationEncoding,
306 pub bulk_read_batch_size: Option<usize>,
307 tx: tokio::sync::mpsc::UnboundedSender<DatabaseCommand>,
308 handle: Option<tokio::task::JoinHandle<()>>,
309}
310
311impl Debug for RedisCacheDatabase {
312 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
313 f.debug_struct(stringify!(RedisCacheDatabase))
314 .field("trader_id", &self.trader_id)
315 .field("encoding", &self.encoding)
316 .finish_non_exhaustive()
317 }
318}
319
320impl RedisCacheDatabase {
321 pub async fn new(
330 trader_id: TraderId,
331 instance_id: UUID4,
332 config: CacheConfig,
333 database: RedisCacheConfig,
334 ) -> anyhow::Result<Self> {
335 install_cryptographic_provider();
336
337 let con = create_redis_connection(CACHE_READ, &database).await?;
338
339 let (tx, rx) = tokio::sync::mpsc::unbounded_channel::<DatabaseCommand>();
340 let trader_key = get_trader_key(trader_id, instance_id, &config);
341 let trader_key_clone = trader_key.clone();
342 let encoding = config.encoding;
343 let bulk_read_batch_size = config.bulk_read_batch_size;
344
345 let handle = get_runtime().spawn(async move {
346 if let Err(e) =
347 process_commands(rx, trader_key_clone, config.clone(), database.clone()).await
348 {
349 log::error!("Error in task '{CACHE_PROCESS}': {e}");
350 }
351 });
352
353 Ok(Self {
354 con,
355 trader_id,
356 trader_key,
357 encoding,
358 bulk_read_batch_size,
359 tx,
360 handle: Some(handle),
361 })
362 }
363
364 #[must_use]
365 pub const fn get_encoding(&self) -> SerializationEncoding {
366 self.encoding
367 }
368
369 #[must_use]
370 pub fn get_trader_key(&self) -> &str {
371 &self.trader_key
372 }
373
374 pub fn close(&mut self) {
375 log::debug!("Closing");
376
377 let Some(handle) = self.handle.take() else {
378 log::debug!("Already closed");
379 return;
380 };
381
382 if let Err(e) = self.tx.send(DatabaseCommand::close()) {
383 log::debug!("Error sending close command: {e:?}");
384 }
385
386 log_task_awaiting(CACHE_PROCESS);
387
388 let (tx, rx) = mpsc::sync_channel(1);
389
390 get_runtime().spawn(async move {
391 if let Err(e) = handle.await {
392 log::error!("Error awaiting task '{CACHE_PROCESS}': {e:?}");
393 }
394 let _ = tx.send(());
395 });
396 let _ = blocking_recv(&rx);
397
398 log::debug!("Closed");
399 }
400
401 pub async fn flushdb(&mut self) {
402 if let Err(e) = redis::cmd(REDIS_FLUSHDB)
403 .query_async::<()>(&mut self.con)
404 .await
405 {
406 log::error!("Failed to flush database: {e:?}");
407 }
408 }
409
410 pub fn flushdb_sync(&self) -> anyhow::Result<()> {
417 let (reply_tx, reply_rx) = mpsc::sync_channel(1);
418 let cmd = DatabaseCommand {
419 op_type: DatabaseOperation::Flush(reply_tx),
420 key: None,
421 payload: None,
422 };
423 self.tx
424 .send(cmd)
425 .map_err(|e| anyhow::anyhow!("{FAILED_TX_CHANNEL}: {e}"))?;
426 blocking_recv(&reply_rx).map_err(|e| anyhow::anyhow!("Failed to flush database: {e}"))?;
427 Ok(())
428 }
429
430 pub async fn keys(&mut self, pattern: &str) -> anyhow::Result<Vec<String>> {
436 let pattern = format!("{}{REDIS_DELIMITER}{pattern}", self.trader_key);
437 DatabaseQueries::scan_keys(&mut self.con, pattern).await
438 }
439
440 pub async fn read(&mut self, key: &str) -> anyhow::Result<Vec<Bytes>> {
446 DatabaseQueries::read(&self.con, &self.trader_key, key).await
447 }
448
449 pub async fn read_bulk(&mut self, keys: &[String]) -> anyhow::Result<Vec<Option<Bytes>>> {
455 match self.bulk_read_batch_size {
456 Some(batch_size) => {
457 DatabaseQueries::read_bulk_batched(&self.con, keys, batch_size).await
458 }
459 None => DatabaseQueries::read_bulk(&self.con, keys).await,
460 }
461 }
462
463 pub fn load_custom_data(&self, data_type: &DataType) -> anyhow::Result<Vec<CustomData>> {
473 let con = self.con.clone();
474 let trader_key = self.trader_key.clone();
475 let data_type = data_type.clone();
476 let (tx, rx) = mpsc::channel();
477
478 get_runtime().spawn(async move {
479 let result = DatabaseQueries::load_custom_data(&con, &trader_key, &data_type).await;
480 if let Err(e) = tx.send(result) {
481 log::error!("Failed to send custom data result for '{data_type}': {e:?}");
482 }
483 });
484
485 blocking_recv(&rx).map_err(|e| anyhow::anyhow!("load_custom_data channel closed: {e}"))?
486 }
487
488 pub fn insert(&self, key: String, payload: Option<Vec<Bytes>>) -> anyhow::Result<()> {
494 let op = DatabaseCommand::new(DatabaseOperation::Insert, key, payload);
495 match self.tx.send(op) {
496 Ok(()) => Ok(()),
497 Err(e) => anyhow::bail!("{FAILED_TX_CHANNEL}: {e}"),
498 }
499 }
500
501 pub fn add_custom_data(&self, data: &CustomData) -> anyhow::Result<()> {
507 let json_bytes = serde_json::to_vec(data)
508 .map_err(|e| anyhow::anyhow!("CustomData serialization failed: {e}"))?;
509 let ts_init = data.ts_init().as_u64();
510 let key = format!(
511 "{CUSTOM}{REDIS_DELIMITER}{:020}{REDIS_DELIMITER}{}",
512 ts_init,
513 UUID4::new()
514 );
515 self.insert(key, Some(vec![Bytes::from(json_bytes)]))
516 }
517
518 pub fn update(&mut self, key: String, payload: Option<Vec<Bytes>>) -> anyhow::Result<()> {
524 let op = DatabaseCommand::new(DatabaseOperation::Update, key, payload);
525 match self.tx.send(op) {
526 Ok(()) => Ok(()),
527 Err(e) => anyhow::bail!("{FAILED_TX_CHANNEL}: {e}"),
528 }
529 }
530
531 pub fn delete(&mut self, key: String, payload: Option<Vec<Bytes>>) -> anyhow::Result<()> {
537 let op = DatabaseCommand::new(DatabaseOperation::Delete, key, payload);
538 match self.tx.send(op) {
539 Ok(()) => Ok(()),
540 Err(e) => anyhow::bail!("{FAILED_TX_CHANNEL}: {e}"),
541 }
542 }
543
544 pub fn delete_order(&self, client_order_id: &ClientOrderId) -> anyhow::Result<()> {
550 let order_id_bytes = Bytes::from(client_order_id.to_string());
551
552 let key = format!("{ORDERS}{REDIS_DELIMITER}{client_order_id}");
554 let op = DatabaseCommand::new(DatabaseOperation::Delete, key, None);
555 self.tx
556 .send(op)
557 .map_err(|e| anyhow::anyhow!("Failed to send delete order command: {e}"))?;
558
559 let index_keys = [
561 INDEX_ORDER_IDS,
562 INDEX_ORDERS,
563 INDEX_ORDERS_OPEN,
564 INDEX_ORDERS_CLOSED,
565 INDEX_ORDERS_EMULATED,
566 INDEX_ORDERS_INFLIGHT,
567 ];
568
569 for index_key in &index_keys {
570 let key = (*index_key).to_string();
571 let payload = vec![order_id_bytes.clone()];
572 let op = DatabaseCommand::new(DatabaseOperation::Delete, key, Some(payload));
573 self.tx
574 .send(op)
575 .map_err(|e| anyhow::anyhow!("Failed to send delete order index command: {e}"))?;
576 }
577
578 let hash_indexes = [INDEX_ORDER_POSITION, INDEX_ORDER_CLIENT];
580 for index_key in &hash_indexes {
581 let key = (*index_key).to_string();
582 let payload = vec![order_id_bytes.clone()];
583 let op = DatabaseCommand::new(DatabaseOperation::Delete, key, Some(payload));
584 self.tx.send(op).map_err(|e| {
585 anyhow::anyhow!("Failed to send delete order hash index command: {e}")
586 })?;
587 }
588
589 Ok(())
590 }
591
592 pub fn delete_position(&self, position_id: &PositionId) -> anyhow::Result<()> {
598 let position_id_bytes = Bytes::from(position_id.to_string());
599
600 let key = format!("{POSITIONS}{REDIS_DELIMITER}{position_id}");
602 let op = DatabaseCommand::new(DatabaseOperation::Delete, key, None);
603 self.tx
604 .send(op)
605 .map_err(|e| anyhow::anyhow!("Failed to send delete position command: {e}"))?;
606
607 let index_keys = [
609 INDEX_POSITIONS,
610 INDEX_POSITIONS_OPEN,
611 INDEX_POSITIONS_CLOSED,
612 ];
613
614 for index_key in &index_keys {
615 let key = (*index_key).to_string();
616 let payload = vec![position_id_bytes.clone()];
617 let op = DatabaseCommand::new(DatabaseOperation::Delete, key, Some(payload));
618 self.tx.send(op).map_err(|e| {
619 anyhow::anyhow!("Failed to send delete position index command: {e}")
620 })?;
621 }
622
623 Ok(())
624 }
625
626 pub fn delete_account_event(
632 &self,
633 account_id: &AccountId,
634 event_id: &str,
635 ) -> anyhow::Result<()> {
636 log::warn!(
637 "Deleting account events currently a no-op (pending redesign), {account_id}: {event_id}"
638 );
639 Ok(())
640 }
641}
642
643fn blocking_recv<T>(rx: &mpsc::Receiver<T>) -> Result<T, mpsc::RecvError> {
649 let on_nautilus_runtime =
650 tokio::runtime::Handle::try_current().is_ok_and(|h| h.id() == get_runtime().handle().id());
651
652 if on_nautilus_runtime {
653 tokio::task::block_in_place(|| rx.recv())
654 } else {
655 rx.recv()
656 }
657}
658
659async fn process_commands(
660 mut rx: tokio::sync::mpsc::UnboundedReceiver<DatabaseCommand>,
661 trader_key: String,
662 config: CacheConfig,
663 database: RedisCacheConfig,
664) -> anyhow::Result<()> {
665 log_task_started(CACHE_PROCESS);
666
667 let mut con = create_redis_connection(CACHE_WRITE, &database).await?;
668
669 let mut buffer: VecDeque<DatabaseCommand> = VecDeque::new();
671 let buffer_interval = Duration::from_millis(config.buffer_interval_ms.unwrap_or(0) as u64);
672
673 let flush_timer = tokio::time::sleep(buffer_interval);
677 tokio::pin!(flush_timer);
678
679 loop {
681 tokio::select! {
682 maybe_cmd = rx.recv() => {
683 let result = handle_command(
684 maybe_cmd,
685 &mut buffer,
686 buffer_interval,
687 &mut con,
688 &trader_key,
689 config.encoding,
690 ).await;
691
692 if result.is_break() {
693 break;
694 }
695 }
696 () = &mut flush_timer, if !buffer_interval.is_zero() => {
697 flush_buffer(
698 &mut buffer,
699 &mut con,
700 &trader_key,
701 config.encoding,
702 &mut flush_timer,
703 buffer_interval,
704 ).await;
705 }
706 }
707 }
708
709 if !buffer.is_empty() {
711 drain_buffer(&mut con, &trader_key, config.encoding, &mut buffer).await;
712 }
713
714 log_task_stopped(CACHE_PROCESS);
715 Ok(())
716}
717
718async fn handle_command(
719 maybe_cmd: Option<DatabaseCommand>,
720 buffer: &mut VecDeque<DatabaseCommand>,
721 buffer_interval: Duration,
722 con: &mut ConnectionManager,
723 trader_key: &str,
724 encoding: SerializationEncoding,
725) -> ControlFlow<()> {
726 let Some(cmd) = maybe_cmd else {
727 log::debug!("Command channel closed");
728 return ControlFlow::Break(());
729 };
730
731 log::trace!("Received {cmd:?}");
732
733 match cmd.op_type {
734 DatabaseOperation::Close => {
735 if !buffer.is_empty() {
736 drain_buffer(con, trader_key, encoding, buffer).await;
737 }
738 return ControlFlow::Break(());
739 }
740 DatabaseOperation::Flush(reply_tx) => {
741 if !buffer.is_empty() {
742 drain_buffer(con, trader_key, encoding, buffer).await;
743 }
744
745 if let Err(e) = redis::cmd(REDIS_FLUSHDB).query_async::<()>(con).await {
746 log::error!("Failed to flush database: {e:?}");
747 }
748 let _ = reply_tx.send(());
749 return ControlFlow::Continue(());
750 }
751 _ => {}
752 }
753
754 buffer.push_back(cmd);
755
756 if buffer_interval.is_zero() {
757 drain_buffer(con, trader_key, encoding, buffer).await;
758 }
759
760 ControlFlow::Continue(())
761}
762
763async fn flush_buffer(
764 buffer: &mut VecDeque<DatabaseCommand>,
765 con: &mut ConnectionManager,
766 trader_key: &str,
767 encoding: SerializationEncoding,
768 flush_timer: &mut Pin<&mut tokio::time::Sleep>,
769 buffer_interval: Duration,
770) {
771 if !buffer.is_empty() {
772 drain_buffer(con, trader_key, encoding, buffer).await;
773 }
774 flush_timer
775 .as_mut()
776 .reset(tokio::time::Instant::now() + buffer_interval);
777}
778
779async fn drain_buffer(
780 conn: &mut ConnectionManager,
781 trader_key: &str,
782 encoding: SerializationEncoding,
783 buffer: &mut VecDeque<DatabaseCommand>,
784) {
785 let mut pipe = redis::pipe();
786 pipe.atomic();
787 let mut has_pending_ops = false;
788
789 for msg in buffer.drain(..) {
790 let Some(key) = msg.key else {
791 log::error!("Null key found for message: {msg:?}");
792 continue;
793 };
794 let collection = match get_collection_key(&key) {
795 Ok(collection) => collection,
796 Err(e) => {
797 log::error!("{e}");
798 continue; }
800 };
801
802 let key = format!("{trader_key}{REDIS_DELIMITER}{key}");
803
804 match msg.op_type {
805 DatabaseOperation::Insert => {
806 if let Some(payload) = msg.payload {
807 log::debug!("Processing INSERT for collection: {collection}, key: {key}");
808 if let Err(e) = insert(&mut pipe, collection, &key, &payload) {
809 log::error!("{e}");
810 } else {
811 has_pending_ops = true;
812 }
813 } else {
814 log::error!("Null `payload` for `insert`");
815 }
816 }
817 DatabaseOperation::Update => {
818 if let Some(payload) = msg.payload {
819 log::debug!("Processing UPDATE for collection: {collection}, key: {key}");
820 if let Err(e) = update(&mut pipe, collection, &key, &payload) {
821 log::error!("{e}");
822 } else {
823 has_pending_ops = true;
824 }
825 } else {
826 log::error!("Null `payload` for `update`");
827 }
828 }
829 DatabaseOperation::UpdateOrder => {
830 flush_pending_pipeline(conn, &mut pipe, &mut has_pending_ops).await;
831
832 if let Some(payload) = msg.payload {
833 log::debug!("Processing UPDATE_ORDER for key: {key}");
834 if let Err(e) =
835 update_order_event_log(conn, trader_key, encoding, &key, &payload).await
836 {
837 log::error!("{e}");
838 }
839 } else {
840 log::error!("Null `payload` for `update_order`");
841 }
842 }
843 DatabaseOperation::ReplaceList => {
844 if let Some(payload) = msg.payload {
845 log::debug!("Processing REPLACE_LIST for key: {key}");
846 if let Err(e) = replace_list_operation(&mut pipe, collection, &key, &payload) {
847 log::error!("{e}");
848 } else {
849 has_pending_ops = true;
850 }
851 } else {
852 log::error!("Null `payload` for `replace_list`");
853 }
854 }
855 DatabaseOperation::Delete => {
856 log::debug!(
857 "Processing DELETE for collection: {}, key: {}, payload: {:?}",
858 collection,
859 key,
860 msg.payload.as_ref().map(std::vec::Vec::len)
861 );
862 if let Err(e) = delete(&mut pipe, collection, &key, msg.payload) {
864 log::error!("{e}");
865 } else {
866 has_pending_ops = true;
867 }
868 }
869 DatabaseOperation::Close => panic!("Close command should not be drained"),
870 DatabaseOperation::Flush(_) => panic!("Flush command should not be drained"),
871 }
872 }
873
874 flush_pending_pipeline(conn, &mut pipe, &mut has_pending_ops).await;
875}
876
877async fn flush_pending_pipeline(
878 conn: &mut ConnectionManager,
879 pipe: &mut Pipeline,
880 has_pending_ops: &mut bool,
881) {
882 if !*has_pending_ops {
883 return;
884 }
885
886 if let Err(e) = pipe.query_async::<()>(conn).await {
887 log::error!("{e}");
888 }
889
890 *pipe = redis::pipe();
891 pipe.atomic();
892 *has_pending_ops = false;
893}
894
895async fn update_order_event_log(
896 conn: &mut ConnectionManager,
897 trader_key: &str,
898 encoding: SerializationEncoding,
899 key: &str,
900 value: &[Bytes],
901) -> anyhow::Result<()> {
902 check_slice_not_empty(value, stringify!(value))?;
903
904 let result: Vec<Bytes> = conn.lrange(key, 0, -1).await?;
905 if result.is_empty() {
906 log::warn!("Cannot update order in Redis, no existing state at {key}");
907 return Ok(());
908 }
909
910 let mut append_pipe = redis::pipe();
911 append_pipe.atomic();
912 update_list(&mut append_pipe, key, value[0].as_ref());
913 append_pipe.query_async::<()>(conn).await?;
914
915 let mut events: Vec<OrderEventAny> = result
916 .iter()
917 .map(|payload| DatabaseQueries::deserialize_payload(encoding, payload))
918 .collect::<anyhow::Result<_>>()
919 .with_context(|| {
920 format!(
921 "Order event append succeeded for {key}, but index replay failed decoding history"
922 )
923 })?;
924 let event: OrderEventAny = DatabaseQueries::deserialize_payload(encoding, value[0].as_ref())
925 .with_context(|| {
926 format!(
927 "Order event append succeeded for {key}, but index replay failed decoding appended event"
928 )
929 })?;
930 events.push(event);
931 let order = OrderAny::from_events(events).with_context(|| {
932 format!("Order event append succeeded for {key}, but index replay failed rebuilding order")
933 })?;
934
935 let mut pipe = redis::pipe();
936 pipe.atomic();
937 update_order_indexes(&mut pipe, trader_key, &order);
938 pipe.query_async::<()>(conn).await?;
939
940 Ok(())
941}
942
943fn insert(pipe: &mut Pipeline, collection: &str, key: &str, value: &[Bytes]) -> anyhow::Result<()> {
944 check_slice_not_empty(value, stringify!(value))?;
945
946 match collection {
947 INDEX => insert_index(pipe, key, value),
948 GENERAL | CURRENCIES | INSTRUMENTS | INSTRUMENT_CLOSES | SYNTHETICS | ACTORS
949 | STRATEGIES | HEALTH | CUSTOM => {
950 insert_string(pipe, key, value[0].as_ref());
951 Ok(())
952 }
953 ACCOUNTS | ORDERS | POSITIONS | SNAPSHOTS => {
954 insert_list(pipe, key, value[0].as_ref());
955 Ok(())
956 }
957 _ => anyhow::bail!("Unsupported operation: `insert` for collection '{collection}'"),
958 }
959}
960
961fn insert_index(pipe: &mut Pipeline, key: &str, value: &[Bytes]) -> anyhow::Result<()> {
962 let index_key = get_index_key(key)?;
963 match index_key {
964 INDEX_ORDER_IDS
965 | INDEX_ORDERS
966 | INDEX_ORDERS_OPEN
967 | INDEX_ORDERS_CLOSED
968 | INDEX_ORDERS_EMULATED
969 | INDEX_ORDERS_INFLIGHT
970 | INDEX_POSITIONS
971 | INDEX_POSITIONS_OPEN
972 | INDEX_POSITIONS_CLOSED => {
973 insert_set(pipe, key, value[0].as_ref());
974 Ok(())
975 }
976 INDEX_ORDER_POSITION => {
977 insert_hset(pipe, key, value[0].as_ref(), value[1].as_ref());
978 Ok(())
979 }
980 INDEX_ORDER_CLIENT => {
981 if !value.len().is_multiple_of(2) {
982 anyhow::bail!(
983 "Invalid hash index payload for '{index_key}': expected field-value pairs"
984 );
985 }
986
987 let entries = value
988 .as_chunks::<2>()
989 .0
990 .iter()
991 .map(|entry| (entry[0].as_ref(), entry[1].as_ref()))
992 .collect::<Vec<(&[u8], &[u8])>>();
993 pipe.hset_multiple(key, &entries);
994 Ok(())
995 }
996 _ => anyhow::bail!("Index unknown '{index_key}' on insert"),
997 }
998}
999
1000fn insert_string(pipe: &mut Pipeline, key: &str, value: &[u8]) {
1001 pipe.set(key, value);
1002}
1003
1004fn insert_set(pipe: &mut Pipeline, key: &str, value: &[u8]) {
1005 pipe.sadd(key, value);
1006}
1007
1008fn insert_hset(pipe: &mut Pipeline, key: &str, name: &[u8], value: &[u8]) {
1009 pipe.hset(key, name, value);
1010}
1011
1012fn insert_list(pipe: &mut Pipeline, key: &str, value: &[u8]) {
1013 pipe.rpush(key, value);
1014}
1015
1016fn replace_list(pipe: &mut Pipeline, key: &str, value: &[u8]) {
1017 pipe.del(key);
1018 pipe.rpush(key, value);
1019}
1020
1021fn replace_list_operation(
1022 pipe: &mut Pipeline,
1023 collection: &str,
1024 key: &str,
1025 value: &[Bytes],
1026) -> anyhow::Result<()> {
1027 check_slice_not_empty(value, stringify!(value))?;
1028
1029 match collection {
1030 ACCOUNTS | ORDERS | POSITIONS => {
1031 replace_list(pipe, key, value[0].as_ref());
1032 Ok(())
1033 }
1034 _ => anyhow::bail!("Unsupported operation: `replace_list` for collection '{collection}'"),
1035 }
1036}
1037
1038fn update(pipe: &mut Pipeline, collection: &str, key: &str, value: &[Bytes]) -> anyhow::Result<()> {
1039 check_slice_not_empty(value, stringify!(value))?;
1040
1041 match collection {
1042 ACCOUNTS | ORDERS | POSITIONS => {
1043 update_list(pipe, key, value[0].as_ref());
1044 Ok(())
1045 }
1046 _ => anyhow::bail!("Unsupported operation: `update` for collection '{collection}'"),
1047 }
1048}
1049
1050fn update_list(pipe: &mut Pipeline, key: &str, value: &[u8]) {
1051 pipe.rpush_exists(key, value);
1052}
1053
1054fn delete(
1055 pipe: &mut Pipeline,
1056 collection: &str,
1057 key: &str,
1058 value: Option<Vec<Bytes>>,
1059) -> anyhow::Result<()> {
1060 log::debug!(
1061 "delete: collection={}, key={}, has_payload={}",
1062 collection,
1063 key,
1064 value.is_some()
1065 );
1066
1067 match collection {
1068 INDEX => delete_from_index(pipe, key, value),
1069 ORDERS | POSITIONS | ACCOUNTS | ACTORS | STRATEGIES => {
1070 delete_string(pipe, key);
1071 Ok(())
1072 }
1073 _ => anyhow::bail!("Unsupported operation: `delete` for collection '{collection}'"),
1074 }
1075}
1076
1077fn delete_from_index(
1078 pipe: &mut Pipeline,
1079 key: &str,
1080 value: Option<Vec<Bytes>>,
1081) -> anyhow::Result<()> {
1082 let value = value.ok_or_else(|| anyhow::anyhow!("Empty `payload` for `delete` '{key}'"))?;
1083 let index_key = get_index_key(key)?;
1084
1085 match index_key {
1086 INDEX_ORDER_IDS
1087 | INDEX_ORDERS
1088 | INDEX_ORDERS_OPEN
1089 | INDEX_ORDERS_CLOSED
1090 | INDEX_ORDERS_EMULATED
1091 | INDEX_ORDERS_INFLIGHT
1092 | INDEX_POSITIONS
1093 | INDEX_POSITIONS_OPEN
1094 | INDEX_POSITIONS_CLOSED => {
1095 remove_from_set(pipe, key, value[0].as_ref());
1096 Ok(())
1097 }
1098 INDEX_ORDER_POSITION | INDEX_ORDER_CLIENT => {
1099 remove_from_hash(pipe, key, value[0].as_ref());
1100 Ok(())
1101 }
1102 _ => anyhow::bail!("Unsupported index operation: remove from '{index_key}'"),
1103 }
1104}
1105
1106fn remove_from_set(pipe: &mut Pipeline, key: &str, member: &[u8]) {
1107 pipe.srem(key, member);
1108}
1109
1110fn remove_from_hash(pipe: &mut Pipeline, key: &str, field: &[u8]) {
1111 pipe.hdel(key, field);
1112}
1113
1114fn delete_string(pipe: &mut Pipeline, key: &str) {
1115 pipe.del(key);
1116}
1117
1118fn full_redis_key(trader_key: &str, key: &str) -> String {
1119 format!("{trader_key}{REDIS_DELIMITER}{key}")
1120}
1121
1122fn update_order_indexes(pipe: &mut Pipeline, trader_key: &str, order: &OrderAny) {
1123 let client_order_id = order.client_order_id();
1124 let order_id_bytes = client_order_id.to_string();
1125
1126 insert_set(
1127 pipe,
1128 &full_redis_key(trader_key, INDEX_ORDERS),
1129 order_id_bytes.as_bytes(),
1130 );
1131
1132 if order.venue_order_id().is_some() {
1133 insert_set(
1134 pipe,
1135 &full_redis_key(trader_key, INDEX_ORDER_IDS),
1136 order_id_bytes.as_bytes(),
1137 );
1138 }
1139
1140 if order.is_inflight() {
1141 insert_set(
1142 pipe,
1143 &full_redis_key(trader_key, INDEX_ORDERS_INFLIGHT),
1144 order_id_bytes.as_bytes(),
1145 );
1146 } else {
1147 remove_from_set(
1148 pipe,
1149 &full_redis_key(trader_key, INDEX_ORDERS_INFLIGHT),
1150 order_id_bytes.as_bytes(),
1151 );
1152 }
1153
1154 if order.is_open() {
1155 remove_from_set(
1156 pipe,
1157 &full_redis_key(trader_key, INDEX_ORDERS_CLOSED),
1158 order_id_bytes.as_bytes(),
1159 );
1160 insert_set(
1161 pipe,
1162 &full_redis_key(trader_key, INDEX_ORDERS_OPEN),
1163 order_id_bytes.as_bytes(),
1164 );
1165 } else if order.is_closed() {
1166 remove_from_set(
1167 pipe,
1168 &full_redis_key(trader_key, INDEX_ORDERS_OPEN),
1169 order_id_bytes.as_bytes(),
1170 );
1171 insert_set(
1172 pipe,
1173 &full_redis_key(trader_key, INDEX_ORDERS_CLOSED),
1174 order_id_bytes.as_bytes(),
1175 );
1176 }
1177
1178 if order.emulation_trigger().is_some() && !order.is_closed() {
1179 insert_set(
1180 pipe,
1181 &full_redis_key(trader_key, INDEX_ORDERS_EMULATED),
1182 order_id_bytes.as_bytes(),
1183 );
1184 } else {
1185 remove_from_set(
1186 pipe,
1187 &full_redis_key(trader_key, INDEX_ORDERS_EMULATED),
1188 order_id_bytes.as_bytes(),
1189 );
1190 }
1191}
1192
1193fn format_timestamp(timestamp: UnixNanos) -> String {
1194 format!("{:.9}", timestamp.to_datetime_utc())
1195}
1196
1197fn get_trader_key(trader_id: TraderId, instance_id: UUID4, config: &CacheConfig) -> String {
1198 let mut key = String::new();
1199
1200 if config.use_trader_prefix {
1201 key.push_str("trader-");
1202 }
1203
1204 key.push_str(trader_id.as_str());
1205
1206 if config.use_instance_id {
1207 key.push(REDIS_DELIMITER);
1208 write!(key, "{instance_id}").expect("writing to String cannot fail");
1209 }
1210
1211 key
1212}
1213
1214fn get_collection_key(key: &str) -> anyhow::Result<&str> {
1215 key.split_once(REDIS_DELIMITER)
1216 .map(|(collection, _)| collection)
1217 .ok_or_else(|| {
1218 anyhow::anyhow!("Invalid `key`, missing a '{REDIS_DELIMITER}' delimiter, was {key}")
1219 })
1220}
1221
1222#[derive(Debug)]
1223pub struct RedisCacheDatabaseAdapter {
1224 pub database: RedisCacheDatabase,
1225}
1226
1227impl RedisCacheDatabaseAdapter {
1228 fn encoding(&self) -> SerializationEncoding {
1229 self.database.get_encoding()
1230 }
1231
1232 fn send_command(
1233 &self,
1234 op_type: DatabaseOperation,
1235 key: String,
1236 payload: Option<Vec<Bytes>>,
1237 ) -> anyhow::Result<()> {
1238 let op = DatabaseCommand::new(op_type, key, payload);
1239 self.database
1240 .tx
1241 .send(op)
1242 .map_err(|e| anyhow::anyhow!("{FAILED_TX_CHANNEL}: {e}"))
1243 }
1244
1245 fn append_list(&self, key: String, payload: Bytes) -> anyhow::Result<()> {
1246 self.send_command(DatabaseOperation::Update, key, Some(vec![payload]))
1247 }
1248
1249 fn serialize_account_event(&self, account: &AccountAny) -> anyhow::Result<Bytes> {
1250 let event: AccountState = account.last_event().ok_or_else(|| {
1251 anyhow::anyhow!("Cannot persist account with no events: {}", account.id())
1252 })?;
1253 let payload = DatabaseQueries::serialize_payload(self.encoding(), &event)?;
1254 Ok(Bytes::from(payload))
1255 }
1256
1257 fn serialize_order_event(&self, order_event: &OrderEventAny) -> anyhow::Result<Bytes> {
1258 let payload = DatabaseQueries::serialize_payload(self.encoding(), order_event)?;
1259 Ok(Bytes::from(payload))
1260 }
1261
1262 fn serialize_position_event(&self, position: &Position) -> anyhow::Result<Bytes> {
1263 let event: OrderFilled = position.last_event().ok_or_else(|| {
1264 anyhow::anyhow!("Cannot persist position with no events: {}", position.id)
1265 })?;
1266 let payload = DatabaseQueries::serialize_payload(self.encoding(), &event)?;
1267 Ok(Bytes::from(payload))
1268 }
1269
1270 fn load_state(&self, key: String) -> anyhow::Result<AHashMap<String, Bytes>> {
1271 let mut con = self.database.con.clone();
1272 let trader_key = self.database.trader_key.clone();
1273 let encoding = self.encoding();
1274 let (tx, rx) = mpsc::channel();
1275
1276 get_runtime().spawn(async move {
1277 let result = async {
1278 let full_key = format!("{trader_key}{REDIS_DELIMITER}{key}");
1279 let value: Option<Bytes> = con.get(&full_key).await?;
1280 let Some(value) = value else {
1281 return Ok(AHashMap::new());
1282 };
1283
1284 DatabaseQueries::deserialize_payload(encoding, &value)
1285 }
1286 .await;
1287
1288 if let Err(e) = tx.send(result) {
1289 log::error!("Failed to send state load result for '{key}': {e:?}");
1290 }
1291 });
1292
1293 blocking_recv(&rx).map_err(|e| anyhow::anyhow!("load_state channel closed: {e}"))?
1294 }
1295
1296 fn update_state(&self, key: String, state: &AHashMap<String, Bytes>) -> anyhow::Result<()> {
1297 let payload = DatabaseQueries::serialize_payload(self.encoding(), state)?;
1298 self.database.insert(key, Some(vec![Bytes::from(payload)]))
1299 }
1300
1301 fn replace_list(&self, key: String, payload: Bytes) -> anyhow::Result<()> {
1302 self.send_command(DatabaseOperation::ReplaceList, key, Some(vec![payload]))
1303 }
1304}
1305
1306#[async_trait::async_trait]
1307impl CacheDatabaseFactory for RedisCacheConfig {
1308 async fn create(
1309 &self,
1310 trader_id: TraderId,
1311 instance_id: UUID4,
1312 config: CacheConfig,
1313 ) -> anyhow::Result<Box<dyn CacheDatabaseAdapter>> {
1314 let database =
1315 RedisCacheDatabase::new(trader_id, instance_id, config, self.clone()).await?;
1316 Ok(Box::new(RedisCacheDatabaseAdapter { database }))
1317 }
1318}
1319
1320#[async_trait::async_trait]
1321impl CacheDatabaseAdapter for RedisCacheDatabaseAdapter {
1322 fn close(&mut self) -> anyhow::Result<()> {
1323 self.database.close();
1324 Ok(())
1325 }
1326
1327 fn flush(&mut self) -> anyhow::Result<()> {
1328 self.database.flushdb_sync()
1329 }
1330
1331 async fn load_all(&self) -> anyhow::Result<CacheMap> {
1332 log::debug!("Loading all data");
1333
1334 let (
1335 currencies,
1336 instruments,
1337 instrument_closes,
1338 synthetics,
1339 accounts,
1340 orders,
1341 positions,
1342 greeks,
1343 yield_curves,
1344 ) = tokio::try_join!(
1345 self.load_currencies(),
1346 self.load_instruments(),
1347 self.load_instrument_closes(),
1348 self.load_synthetics(),
1349 self.load_accounts(),
1350 self.load_orders(),
1351 self.load_positions(),
1352 self.load_greeks(),
1353 self.load_yield_curves()
1354 )
1355 .map_err(|e| anyhow::anyhow!("Error loading cache data: {e}"))?;
1356
1357 Ok(CacheMap {
1358 currencies,
1359 instruments,
1360 instrument_closes,
1361 synthetics,
1362 accounts,
1363 orders,
1364 positions,
1365 greeks,
1366 yield_curves,
1367 })
1368 }
1369
1370 fn load(&self) -> anyhow::Result<AHashMap<String, Bytes>> {
1371 let con = self.database.con.clone();
1372 let trader_key = self.database.trader_key.clone();
1373 let (tx, rx) = mpsc::channel();
1374
1375 get_runtime().spawn(async move {
1376 let result = async {
1377 let pattern = format!("{trader_key}{REDIS_DELIMITER}{GENERAL}:*");
1378 let mut con_scan = con.clone();
1379 let keys = DatabaseQueries::scan_keys(&mut con_scan, pattern).await?;
1380 if keys.is_empty() {
1381 return Ok(AHashMap::new());
1382 }
1383
1384 let values = DatabaseQueries::read_bulk(&con, &keys).await?;
1385 let prefix = format!("{trader_key}{REDIS_DELIMITER}{GENERAL}{REDIS_DELIMITER}");
1386 let mut general = AHashMap::new();
1387
1388 for (key, value) in keys.into_iter().zip(values) {
1389 let Some(value) = value else {
1390 continue;
1391 };
1392
1393 if let Some(clean_key) = key.strip_prefix(&prefix) {
1394 general.insert(clean_key.to_string(), value);
1395 }
1396 }
1397
1398 Ok(general)
1399 }
1400 .await;
1401
1402 if let Err(e) = tx.send(result) {
1403 log::error!("Failed to send general load result: {e:?}");
1404 }
1405 });
1406
1407 blocking_recv(&rx).map_err(|e| anyhow::anyhow!("load channel closed: {e}"))?
1408 }
1409
1410 async fn load_currencies(&self) -> anyhow::Result<AHashMap<Ustr, Currency>> {
1411 DatabaseQueries::load_currencies(
1412 &self.database.con,
1413 &self.database.trader_key,
1414 self.encoding(),
1415 )
1416 .await
1417 }
1418
1419 async fn load_instruments(&self) -> anyhow::Result<AHashMap<InstrumentId, InstrumentAny>> {
1420 DatabaseQueries::load_instruments(
1421 &self.database.con,
1422 &self.database.trader_key,
1423 self.encoding(),
1424 )
1425 .await
1426 }
1427
1428 async fn load_instrument_closes(
1429 &self,
1430 ) -> anyhow::Result<AHashMap<InstrumentId, InstrumentClose>> {
1431 DatabaseQueries::load_instrument_closes(
1432 &self.database.con,
1433 &self.database.trader_key,
1434 self.encoding(),
1435 )
1436 .await
1437 }
1438
1439 async fn load_synthetics(&self) -> anyhow::Result<AHashMap<InstrumentId, SyntheticInstrument>> {
1440 DatabaseQueries::load_synthetics(
1441 &self.database.con,
1442 &self.database.trader_key,
1443 self.encoding(),
1444 )
1445 .await
1446 }
1447
1448 async fn load_accounts(&self) -> anyhow::Result<AHashMap<AccountId, AccountAny>> {
1449 DatabaseQueries::load_accounts(
1450 &self.database.con,
1451 &self.database.trader_key,
1452 self.encoding(),
1453 )
1454 .await
1455 }
1456
1457 async fn load_orders(&self) -> anyhow::Result<AHashMap<ClientOrderId, OrderAny>> {
1458 DatabaseQueries::load_orders(
1459 &self.database.con,
1460 &self.database.trader_key,
1461 self.encoding(),
1462 )
1463 .await
1464 }
1465
1466 async fn load_positions(&self) -> anyhow::Result<AHashMap<PositionId, Position>> {
1467 DatabaseQueries::load_positions(
1468 &self.database.con,
1469 &self.database.trader_key,
1470 self.encoding(),
1471 )
1472 .await
1473 }
1474
1475 fn load_index_order_position(&self) -> anyhow::Result<AHashMap<ClientOrderId, PositionId>> {
1476 let con = self.database.con.clone();
1477 let trader_key = self.database.trader_key.clone();
1478 let (tx, rx) = mpsc::channel();
1479
1480 get_runtime().spawn(async move {
1481 let result = DatabaseQueries::load_index_order_position(&con, &trader_key).await;
1482 if let Err(e) = tx.send(result) {
1483 log::error!("Failed to send load_index_order_position result: {e:?}");
1484 }
1485 });
1486
1487 blocking_recv(&rx)
1488 .map_err(|e| anyhow::anyhow!("load_index_order_position channel closed: {e}"))?
1489 }
1490
1491 fn load_index_order_client(&self) -> anyhow::Result<AHashMap<ClientOrderId, ClientId>> {
1492 let con = self.database.con.clone();
1493 let trader_key = self.database.trader_key.clone();
1494 let (tx, rx) = mpsc::channel();
1495
1496 get_runtime().spawn(async move {
1497 let result = DatabaseQueries::load_index_order_client(&con, &trader_key).await;
1498 if let Err(e) = tx.send(result) {
1499 log::error!("Failed to send load_index_order_client result: {e:?}");
1500 }
1501 });
1502
1503 blocking_recv(&rx)
1504 .map_err(|e| anyhow::anyhow!("load_index_order_client channel closed: {e}"))?
1505 }
1506
1507 async fn load_currency(&self, code: &Ustr) -> anyhow::Result<Option<Currency>> {
1508 DatabaseQueries::load_currency(
1509 &self.database.con,
1510 &self.database.trader_key,
1511 code,
1512 self.encoding(),
1513 )
1514 .await
1515 }
1516
1517 async fn load_instrument(
1518 &self,
1519 instrument_id: &InstrumentId,
1520 ) -> anyhow::Result<Option<InstrumentAny>> {
1521 DatabaseQueries::load_instrument(
1522 &self.database.con,
1523 &self.database.trader_key,
1524 instrument_id,
1525 self.encoding(),
1526 )
1527 .await
1528 }
1529
1530 async fn load_synthetic(
1531 &self,
1532 instrument_id: &InstrumentId,
1533 ) -> anyhow::Result<Option<SyntheticInstrument>> {
1534 DatabaseQueries::load_synthetic(
1535 &self.database.con,
1536 &self.database.trader_key,
1537 instrument_id,
1538 self.encoding(),
1539 )
1540 .await
1541 }
1542
1543 async fn load_account(&self, account_id: &AccountId) -> anyhow::Result<Option<AccountAny>> {
1544 DatabaseQueries::load_account(
1545 &self.database.con,
1546 &self.database.trader_key,
1547 account_id,
1548 self.encoding(),
1549 )
1550 .await
1551 }
1552
1553 async fn load_order(
1554 &self,
1555 client_order_id: &ClientOrderId,
1556 ) -> anyhow::Result<Option<OrderAny>> {
1557 DatabaseQueries::load_order(
1558 &self.database.con,
1559 &self.database.trader_key,
1560 client_order_id,
1561 self.encoding(),
1562 )
1563 .await
1564 }
1565
1566 async fn load_position(&self, position_id: &PositionId) -> anyhow::Result<Option<Position>> {
1567 DatabaseQueries::load_position(
1568 &self.database.con,
1569 &self.database.trader_key,
1570 position_id,
1571 self.encoding(),
1572 )
1573 .await
1574 }
1575
1576 fn load_actor(&self, actor_id: &ActorId) -> anyhow::Result<AHashMap<String, Bytes>> {
1577 let key = format!("{ACTORS}{REDIS_DELIMITER}{actor_id}{REDIS_DELIMITER}state");
1578 self.load_state(key)
1579 }
1580
1581 fn load_strategy(&self, strategy_id: &StrategyId) -> anyhow::Result<AHashMap<String, Bytes>> {
1582 let key = format!("{STRATEGIES}{REDIS_DELIMITER}{strategy_id}{REDIS_DELIMITER}state");
1583 self.load_state(key)
1584 }
1585
1586 fn load_signals(&self, _name: &str) -> anyhow::Result<Vec<Signal>> {
1587 anyhow::bail!("Loading signals from Redis cache adapter not supported")
1588 }
1589
1590 fn load_custom_data(&self, data_type: &DataType) -> anyhow::Result<Vec<CustomData>> {
1591 self.database.load_custom_data(data_type)
1592 }
1593
1594 fn load_order_snapshot(
1595 &self,
1596 _client_order_id: &ClientOrderId,
1597 ) -> anyhow::Result<Option<OrderSnapshot>> {
1598 anyhow::bail!("Loading order snapshots from Redis cache adapter not supported")
1599 }
1600
1601 fn load_position_snapshot(
1602 &self,
1603 _position_id: &PositionId,
1604 ) -> anyhow::Result<Option<PositionSnapshot>> {
1605 anyhow::bail!("Loading position snapshots from Redis cache adapter not supported")
1606 }
1607
1608 fn load_quotes(&self, _instrument_id: &InstrumentId) -> anyhow::Result<Vec<QuoteTick>> {
1609 anyhow::bail!("Loading quote data for Redis cache adapter not supported")
1610 }
1611
1612 fn load_trades(&self, _instrument_id: &InstrumentId) -> anyhow::Result<Vec<TradeTick>> {
1613 anyhow::bail!("Loading market data for Redis cache adapter not supported")
1614 }
1615
1616 fn load_funding_rates(
1617 &self,
1618 _instrument_id: &InstrumentId,
1619 ) -> anyhow::Result<Vec<FundingRateUpdate>> {
1620 anyhow::bail!("Loading market data for Redis cache adapter not supported")
1621 }
1622
1623 fn load_bars(&self, _instrument_id: &InstrumentId) -> anyhow::Result<Vec<Bar>> {
1624 anyhow::bail!("Loading market data for Redis cache adapter not supported")
1625 }
1626
1627 fn add(&self, key: String, value: Bytes) -> anyhow::Result<()> {
1628 let key = format!("{GENERAL}{REDIS_DELIMITER}{key}");
1629 self.database.insert(key, Some(vec![value]))
1630 }
1631
1632 fn add_currency(&self, currency: &Currency) -> anyhow::Result<()> {
1633 let key = format!("{CURRENCIES}{REDIS_DELIMITER}{}", currency.code);
1634 let payload = DatabaseQueries::serialize_payload(self.encoding(), currency)?;
1635 self.database.insert(key, Some(vec![Bytes::from(payload)]))
1636 }
1637
1638 fn add_instrument(&self, instrument: &InstrumentAny) -> anyhow::Result<()> {
1639 let key = format!("{INSTRUMENTS}{REDIS_DELIMITER}{}", instrument.id());
1640 let payload = DatabaseQueries::serialize_payload(self.encoding(), instrument)?;
1641 self.database.insert(key, Some(vec![Bytes::from(payload)]))
1642 }
1643
1644 fn add_instrument_close(&self, close: &InstrumentClose) -> anyhow::Result<()> {
1645 let key = format!(
1646 "{INSTRUMENT_CLOSES}{REDIS_DELIMITER}{}",
1647 close.instrument_id
1648 );
1649 let payload = DatabaseQueries::serialize_payload(self.encoding(), close)?;
1650 self.database.insert(key, Some(vec![Bytes::from(payload)]))
1651 }
1652
1653 fn add_synthetic(&self, synthetic: &SyntheticInstrument) -> anyhow::Result<()> {
1654 let key = format!("{SYNTHETICS}{REDIS_DELIMITER}{}", synthetic.id);
1655 let payload = DatabaseQueries::serialize_payload(self.encoding(), synthetic)?;
1656 self.database.insert(key, Some(vec![Bytes::from(payload)]))
1657 }
1658
1659 fn add_account(&self, account: &AccountAny) -> anyhow::Result<()> {
1660 let account_id = account.id();
1661 let key = format!("{ACCOUNTS}{REDIS_DELIMITER}{account_id}");
1662
1663 let payload = self.serialize_account_event(account)?;
1664 self.database.insert(key, Some(vec![payload]))
1665 }
1666
1667 fn add_order(&self, order: &OrderAny, client_id: Option<ClientId>) -> anyhow::Result<()> {
1668 let client_order_id = order.client_order_id();
1669 let key = format!("{ORDERS}{REDIS_DELIMITER}{client_order_id}");
1670
1671 let event = OrderEventAny::Initialized(order.init_event().clone());
1672 let payload = self.serialize_order_event(&event)?;
1673 self.replace_list(key, payload)?;
1674
1675 let order_id_bytes = Bytes::from(client_order_id.to_string());
1676 self.database
1677 .insert(INDEX_ORDERS.to_string(), Some(vec![order_id_bytes.clone()]))?;
1678
1679 if order.emulation_trigger().is_some() {
1680 self.database.insert(
1681 INDEX_ORDERS_EMULATED.to_string(),
1682 Some(vec![order_id_bytes.clone()]),
1683 )?;
1684 }
1685
1686 if let Some(client_id) = client_id {
1687 self.database.insert(
1688 INDEX_ORDER_CLIENT.to_string(),
1689 Some(vec![order_id_bytes, Bytes::from(client_id.to_string())]),
1690 )?;
1691 }
1692
1693 Ok(())
1694 }
1695
1696 fn add_order_snapshot(&self, snapshot: &OrderSnapshot) -> anyhow::Result<()> {
1697 let key = format!(
1698 "{SNAPSHOTS}{REDIS_DELIMITER}{ORDERS}{REDIS_DELIMITER}{}",
1699 snapshot.client_order_id
1700 );
1701 let payload = DatabaseQueries::serialize_payload(self.encoding(), snapshot)?;
1702 self.database.insert(key, Some(vec![Bytes::from(payload)]))
1703 }
1704
1705 fn add_position(&self, position: &Position) -> anyhow::Result<()> {
1706 let position_id = position.id;
1707 let key = format!("{POSITIONS}{REDIS_DELIMITER}{position_id}");
1708
1709 let payload = self.serialize_position_event(position)?;
1710 self.replace_list(key, payload)?;
1711
1712 let position_id_bytes = Bytes::from(position_id.to_string());
1713 self.database.insert(
1714 INDEX_POSITIONS.to_string(),
1715 Some(vec![position_id_bytes.clone()]),
1716 )?;
1717 self.database.insert(
1718 INDEX_POSITIONS_OPEN.to_string(),
1719 Some(vec![position_id_bytes.clone()]),
1720 )?;
1721 self.send_command(
1722 DatabaseOperation::Delete,
1723 INDEX_POSITIONS_CLOSED.to_string(),
1724 Some(vec![position_id_bytes]),
1725 )?;
1726
1727 Ok(())
1728 }
1729
1730 fn add_position_snapshot(&self, snapshot: &PositionSnapshot) -> anyhow::Result<()> {
1731 let key = format!(
1732 "{SNAPSHOTS}{REDIS_DELIMITER}{POSITIONS}{REDIS_DELIMITER}{}",
1733 snapshot.position_id
1734 );
1735 let payload = DatabaseQueries::serialize_payload(self.encoding(), snapshot)?;
1736 self.database.insert(key, Some(vec![Bytes::from(payload)]))
1737 }
1738
1739 fn add_order_book(&self, _order_book: &OrderBook) -> anyhow::Result<()> {
1740 anyhow::bail!("Saving market data for Redis cache adapter not supported")
1741 }
1742
1743 fn add_signal(&self, _signal: &Signal) -> anyhow::Result<()> {
1744 anyhow::bail!("Saving signals for Redis cache adapter not supported")
1745 }
1746
1747 fn add_custom_data(&self, data: &CustomData) -> anyhow::Result<()> {
1748 self.database.add_custom_data(data)
1749 }
1750
1751 fn add_quote(&self, _quote: &QuoteTick) -> anyhow::Result<()> {
1752 anyhow::bail!("Saving market data for Redis cache adapter not supported")
1753 }
1754
1755 fn add_trade(&self, _trade: &TradeTick) -> anyhow::Result<()> {
1756 anyhow::bail!("Saving market data for Redis cache adapter not supported")
1757 }
1758
1759 fn add_funding_rate(&self, _funding_rate: &FundingRateUpdate) -> anyhow::Result<()> {
1760 anyhow::bail!("Saving market data for Redis cache adapter not supported")
1761 }
1762
1763 fn add_bar(&self, _bar: &Bar) -> anyhow::Result<()> {
1764 anyhow::bail!("Saving market data for Redis cache adapter not supported")
1765 }
1766
1767 fn delete_actor(&self, actor_id: &ActorId) -> anyhow::Result<()> {
1768 let key = format!("{ACTORS}{REDIS_DELIMITER}{actor_id}{REDIS_DELIMITER}state");
1769 let op = DatabaseCommand::new(DatabaseOperation::Delete, key, None);
1770 self.database
1771 .tx
1772 .send(op)
1773 .map_err(|e| anyhow::anyhow!("{FAILED_TX_CHANNEL}: {e}"))
1774 }
1775
1776 fn delete_strategy(&self, component_id: &StrategyId) -> anyhow::Result<()> {
1777 let key = format!("{STRATEGIES}{REDIS_DELIMITER}{component_id}{REDIS_DELIMITER}state");
1778 let op = DatabaseCommand::new(DatabaseOperation::Delete, key, None);
1779 self.database
1780 .tx
1781 .send(op)
1782 .map_err(|e| anyhow::anyhow!("{FAILED_TX_CHANNEL}: {e}"))
1783 }
1784
1785 fn delete_order(&self, client_order_id: &ClientOrderId) -> anyhow::Result<()> {
1786 self.database.delete_order(client_order_id)
1787 }
1788
1789 fn delete_position(&self, position_id: &PositionId) -> anyhow::Result<()> {
1790 self.database.delete_position(position_id)
1791 }
1792
1793 fn delete_account_event(&self, account_id: &AccountId, event_id: &str) -> anyhow::Result<()> {
1794 self.database.delete_account_event(account_id, event_id)
1795 }
1796
1797 fn index_venue_order_id(
1798 &self,
1799 client_order_id: ClientOrderId,
1800 venue_order_id: VenueOrderId,
1801 ) -> anyhow::Result<()> {
1802 self.database.insert(
1803 INDEX_ORDER_IDS.to_string(),
1804 Some(vec![Bytes::from(client_order_id.to_string())]),
1805 )?;
1806 log::debug!("Indexed {client_order_id:?} -> {venue_order_id:?}");
1807 Ok(())
1808 }
1809
1810 fn index_order_position(
1811 &self,
1812 client_order_id: ClientOrderId,
1813 position_id: PositionId,
1814 ) -> anyhow::Result<()> {
1815 self.database.insert(
1816 INDEX_ORDER_POSITION.to_string(),
1817 Some(vec![
1818 Bytes::from(client_order_id.to_string()),
1819 Bytes::from(position_id.to_string()),
1820 ]),
1821 )
1822 }
1823
1824 fn index_order_clients(&self, claims: &[(ClientOrderId, ClientId)]) -> anyhow::Result<()> {
1825 if claims.is_empty() {
1826 return Ok(());
1827 }
1828
1829 let mut payload = Vec::with_capacity(claims.len() * 2);
1830 for (client_order_id, client_id) in claims {
1831 payload.push(Bytes::from(client_order_id.to_string()));
1832 payload.push(Bytes::from(client_id.to_string()));
1833 }
1834
1835 self.database
1836 .insert(INDEX_ORDER_CLIENT.to_string(), Some(payload))
1837 }
1838
1839 fn update_actor(
1840 &self,
1841 actor_id: &ActorId,
1842 state: &AHashMap<String, Bytes>,
1843 ) -> anyhow::Result<()> {
1844 let key = format!("{ACTORS}{REDIS_DELIMITER}{actor_id}{REDIS_DELIMITER}state");
1845 self.update_state(key, state)
1846 }
1847
1848 fn update_strategy(
1849 &self,
1850 strategy_id: &StrategyId,
1851 state: &AHashMap<String, Bytes>,
1852 ) -> anyhow::Result<()> {
1853 let key = format!("{STRATEGIES}{REDIS_DELIMITER}{strategy_id}{REDIS_DELIMITER}state");
1854 self.update_state(key, state)
1855 }
1856
1857 fn update_account(&self, account: &AccountAny) -> anyhow::Result<()> {
1858 let account_id = account.id();
1859 let key = format!("{ACCOUNTS}{REDIS_DELIMITER}{account_id}");
1860 let payload = self.serialize_account_event(account)?;
1861 self.append_list(key, payload)
1862 }
1863
1864 fn update_order(&self, order_event: &OrderEventAny) -> anyhow::Result<()> {
1865 let client_order_id = order_event.client_order_id();
1866 let key = format!("{ORDERS}{REDIS_DELIMITER}{client_order_id}");
1867 let payload = DatabaseQueries::serialize_payload(self.encoding(), order_event)?;
1868 let op = DatabaseCommand::new(
1869 DatabaseOperation::UpdateOrder,
1870 key,
1871 Some(vec![Bytes::from(payload)]),
1872 );
1873 self.database
1874 .tx
1875 .send(op)
1876 .map_err(|e| anyhow::anyhow!("{FAILED_TX_CHANNEL}: {e}"))
1877 }
1878
1879 fn update_position(&self, position: &Position) -> anyhow::Result<()> {
1880 let position_id = position.id;
1881 if position.fill_voids.is_empty() {
1882 let key = format!("{POSITIONS}{REDIS_DELIMITER}{position_id}");
1883 let payload = self.serialize_position_event(position)?;
1884 self.append_list(key, payload)?;
1885 } else {
1886 self.add_position_snapshot(&PositionSnapshot::from_replay_state(position, None))?;
1887 }
1888
1889 let position_id_bytes = Bytes::from(position_id.to_string());
1890
1891 if position.is_open() {
1892 self.database.insert(
1893 INDEX_POSITIONS_OPEN.to_string(),
1894 Some(vec![position_id_bytes.clone()]),
1895 )?;
1896 self.send_command(
1897 DatabaseOperation::Delete,
1898 INDEX_POSITIONS_CLOSED.to_string(),
1899 Some(vec![position_id_bytes]),
1900 )?;
1901 } else if position.is_closed() {
1902 self.database.insert(
1903 INDEX_POSITIONS_CLOSED.to_string(),
1904 Some(vec![position_id_bytes.clone()]),
1905 )?;
1906 self.send_command(
1907 DatabaseOperation::Delete,
1908 INDEX_POSITIONS_OPEN.to_string(),
1909 Some(vec![position_id_bytes]),
1910 )?;
1911 }
1912
1913 Ok(())
1914 }
1915
1916 fn snapshot_order_state(&self, order: &OrderAny) -> anyhow::Result<()> {
1917 let snapshot = OrderSnapshot::from(order.clone());
1918 self.add_order_snapshot(&snapshot)
1919 }
1920
1921 fn snapshot_position_state(
1922 &self,
1923 position: &Position,
1924 ts_snapshot: UnixNanos,
1925 unrealized_pnl: Option<Money>,
1926 ) -> anyhow::Result<()> {
1927 let mut snapshot = PositionSnapshot::from(position, unrealized_pnl);
1928 snapshot.ts_init = ts_snapshot;
1929 self.add_position_snapshot(&snapshot)
1930 }
1931
1932 fn heartbeat(&self, timestamp: UnixNanos) -> anyhow::Result<()> {
1933 let timestamp = format_timestamp(timestamp);
1934 self.database.insert(
1935 format!("{HEALTH}{REDIS_DELIMITER}heartbeat"),
1936 Some(vec![Bytes::from(timestamp)]),
1937 )
1938 }
1939}
1940
1941#[cfg(test)]
1942mod tests {
1943 use rstest::rstest;
1944
1945 use super::*;
1946
1947 #[rstest]
1948 fn test_get_trader_key_with_prefix_and_instance_id() {
1949 let trader_id = TraderId::from("tester-123");
1950 let instance_id = UUID4::new();
1951 let config = CacheConfig {
1952 use_instance_id: true,
1953 ..Default::default()
1954 };
1955
1956 let key = get_trader_key(trader_id, instance_id, &config);
1957 assert!(key.starts_with("trader-tester-123:"));
1958 assert!(key.ends_with(&instance_id.to_string()));
1959 }
1960
1961 #[rstest]
1962 fn test_get_collection_key_valid() {
1963 let key = "collection:123";
1964 assert_eq!(get_collection_key(key).unwrap(), "collection");
1965 }
1966
1967 #[rstest]
1968 fn test_get_collection_key_invalid() {
1969 let key = "no_delimiter";
1970 assert!(get_collection_key(key).is_err());
1971 }
1972}