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