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