1use crate::consensus::transport::NetworkTransport;
78use crate::error::{Result, TdbError};
79use anyhow::Context;
80use chrono::{DateTime, Duration, Utc};
81use parking_lot::{Mutex, RwLock};
82use serde::{Deserialize, Serialize};
83use std::collections::{HashMap, VecDeque};
84use std::sync::Arc;
85
86#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
88pub enum ReplicationMode {
89 MasterSlave,
91 MasterMaster,
93}
94
95#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
97pub enum SyncMode {
98 Async,
100 Sync,
102 SemiSync,
104}
105
106#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
108pub enum ReplicaRole {
109 Master,
111 Slave,
113}
114
115#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
117pub enum ReplicationStatus {
118 Healthy,
120 Lagging,
122 Unreachable,
124 Error,
126}
127
128#[derive(Debug, Clone, Serialize, Deserialize)]
130pub struct ReplicaNode {
131 pub node_id: String,
133 pub endpoint: String,
135 pub role: ReplicaRole,
137 pub status: ReplicationStatus,
139 pub last_sync: DateTime<Utc>,
141 pub lag_ms: i64,
143 pub last_lsn: u64,
145}
146
147#[derive(Debug, Clone, Serialize, Deserialize)]
149pub struct ReplicationChange {
150 pub lsn: u64,
152 pub txn_id: String,
154 pub timestamp: DateTime<Utc>,
156 pub data: Vec<u8>,
158 pub source_node: String,
160}
161
162#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
164pub enum ConflictResolution {
165 LastWriteWins,
167 FirstWriteWins,
169 Manual,
171 Custom,
173}
174
175#[derive(Debug, Clone, Serialize, Deserialize)]
177pub struct ReplicationConfig {
178 pub mode: ReplicationMode,
180 pub sync_mode: SyncMode,
182 pub conflict_resolution: ConflictResolution,
184 pub max_lag_ms: i64,
186 pub heartbeat_interval: Duration,
188 pub auto_failover: bool,
190 pub quorum_size: usize,
192 pub change_buffer_size: usize,
194}
195
196impl Default for ReplicationConfig {
197 fn default() -> Self {
198 Self {
199 mode: ReplicationMode::MasterSlave,
200 sync_mode: SyncMode::Async,
201 conflict_resolution: ConflictResolution::LastWriteWins,
202 max_lag_ms: 1000,
203 heartbeat_interval: Duration::seconds(5),
204 auto_failover: true,
205 quorum_size: 2,
206 change_buffer_size: 10000,
207 }
208 }
209}
210
211pub struct ReplicationManager {
215 node_id: String,
217 config: ReplicationConfig,
219 role: Arc<RwLock<ReplicaRole>>,
221 replicas: Arc<RwLock<HashMap<String, ReplicaNode>>>,
223 change_buffer: Arc<Mutex<VecDeque<ReplicationChange>>>,
225 current_lsn: Arc<Mutex<u64>>,
227 stats: Arc<Mutex<ReplicationStats>>,
229 transport: Option<Arc<dyn NetworkTransport>>,
235}
236
237#[derive(Debug, Clone, Default, Serialize, Deserialize)]
239pub struct ReplicationStats {
240 pub total_changes: u64,
242 pub total_bytes_replicated: u64,
244 pub successful_replications: u64,
246 pub failed_replications: u64,
248 pub conflicts_detected: u64,
250 pub conflicts_resolved: u64,
252 pub failovers: u64,
254 pub avg_replication_latency_ms: f64,
256 total_latency_ms: f64,
258}
259
260impl ReplicationManager {
261 pub fn new(node_id: String, config: ReplicationConfig) -> Self {
271 let initial_role = match config.mode {
272 ReplicationMode::MasterSlave => ReplicaRole::Master,
273 ReplicationMode::MasterMaster => ReplicaRole::Master,
274 };
275
276 Self {
277 node_id,
278 config,
279 role: Arc::new(RwLock::new(initial_role)),
280 replicas: Arc::new(RwLock::new(HashMap::new())),
281 change_buffer: Arc::new(Mutex::new(VecDeque::new())),
282 current_lsn: Arc::new(Mutex::new(0)),
283 stats: Arc::new(Mutex::new(ReplicationStats::default())),
284 transport: None,
285 }
286 }
287
288 pub fn with_transport(
292 node_id: String,
293 config: ReplicationConfig,
294 transport: Arc<dyn NetworkTransport>,
295 ) -> Self {
296 let mut manager = Self::new(node_id, config);
297 manager.transport = Some(transport);
298 manager
299 }
300
301 pub async fn add_replica(&mut self, node_id: String, endpoint: String) -> Result<()> {
303 let replica = ReplicaNode {
304 node_id: node_id.clone(),
305 endpoint,
306 role: ReplicaRole::Slave,
307 status: ReplicationStatus::Healthy,
308 last_sync: Utc::now(),
309 lag_ms: 0,
310 last_lsn: 0,
311 };
312
313 self.replicas.write().insert(node_id, replica);
314 Ok(())
315 }
316
317 pub async fn remove_replica(&mut self, node_id: &str) -> Result<()> {
319 self.replicas.write().remove(node_id);
320 Ok(())
321 }
322
323 pub async fn promote_to_master(&mut self, node_id: &str) -> Result<()> {
325 let mut replicas = self.replicas.write();
326 if let Some(replica) = replicas.get_mut(node_id) {
327 replica.role = ReplicaRole::Master;
328 replica.status = ReplicationStatus::Healthy;
329
330 let mut stats = self.stats.lock();
331 stats.failovers += 1;
332 }
333
334 Ok(())
335 }
336
337 pub async fn record_change(&mut self, txn_id: String, data: Vec<u8>) -> Result<u64> {
339 let lsn = {
340 let mut current_lsn = self.current_lsn.lock();
341 *current_lsn += 1;
342 *current_lsn
343 };
344
345 let change = ReplicationChange {
346 lsn,
347 txn_id,
348 timestamp: Utc::now(),
349 data: data.clone(),
350 source_node: self.node_id.clone(),
351 };
352
353 let mut buffer = self.change_buffer.lock();
354 buffer.push_back(change);
355
356 while buffer.len() > self.config.change_buffer_size {
358 buffer.pop_front();
359 }
360
361 let mut stats = self.stats.lock();
362 stats.total_changes += 1;
363 stats.total_bytes_replicated += data.len() as u64;
364
365 Ok(lsn)
366 }
367
368 pub async fn replicate_changes(&mut self, changes: Vec<ReplicationChange>) -> Result<()> {
370 if self.transport.is_none() {
371 return Err(TdbError::DistributedTransportNotConfigured {
372 node_id: self.node_id.clone(),
373 protocol: "Replication".to_string(),
374 });
375 }
376
377 let start_time = Utc::now();
378
379 match self.config.sync_mode {
380 SyncMode::Async => self.replicate_async(changes).await?,
381 SyncMode::Sync => self.replicate_sync(changes).await?,
382 SyncMode::SemiSync => self.replicate_semi_sync(changes).await?,
383 }
384
385 let latency = (Utc::now() - start_time).num_milliseconds() as f64;
386 let mut stats = self.stats.lock();
387 stats.total_latency_ms += latency;
388 stats.avg_replication_latency_ms = stats.total_latency_ms / stats.total_changes as f64;
389
390 Ok(())
391 }
392
393 async fn replicate_async(&self, changes: Vec<ReplicationChange>) -> Result<()> {
395 let replicas = self.replicas.read().clone();
396
397 for node_id in replicas.keys() {
398 self.send_changes_to_replica(node_id, &changes).await?;
400 }
401
402 let mut stats = self.stats.lock();
403 stats.successful_replications += replicas.len() as u64;
404
405 Ok(())
406 }
407
408 async fn replicate_sync(&self, changes: Vec<ReplicationChange>) -> Result<()> {
410 let replicas = self.replicas.read().clone();
411 let mut success_count = 0;
412
413 for node_id in replicas.keys() {
414 match self.send_changes_to_replica(node_id, &changes).await {
415 Ok(_) => success_count += 1,
416 Err(_) => {
417 let mut stats = self.stats.lock();
418 stats.failed_replications += 1;
419 }
420 }
421 }
422
423 if success_count != replicas.len() {
425 return Err(TdbError::Other(format!(
426 "Synchronous replication failed: {}/{} replicas acknowledged",
427 success_count,
428 replicas.len()
429 )));
430 }
431
432 let mut stats = self.stats.lock();
433 stats.successful_replications += success_count as u64;
434
435 Ok(())
436 }
437
438 async fn replicate_semi_sync(&self, changes: Vec<ReplicationChange>) -> Result<()> {
440 let replicas = self.replicas.read().clone();
441 let mut success_count = 0;
442
443 for node_id in replicas.keys() {
444 match self.send_changes_to_replica(node_id, &changes).await {
445 Ok(_) => success_count += 1,
446 Err(_) => {
447 let mut stats = self.stats.lock();
448 stats.failed_replications += 1;
449 }
450 }
451 }
452
453 if success_count == 0 {
455 return Err(TdbError::Other(
456 "Semi-sync replication failed: no replicas acknowledged".to_string(),
457 ));
458 }
459
460 let mut stats = self.stats.lock();
461 stats.successful_replications += success_count as u64;
462
463 Ok(())
464 }
465
466 async fn send_changes_to_replica(
469 &self,
470 node_id: &str,
471 changes: &[ReplicationChange],
472 ) -> Result<()> {
473 let transport =
474 self.transport
475 .as_ref()
476 .ok_or_else(|| TdbError::DistributedTransportNotConfigured {
477 node_id: self.node_id.clone(),
478 protocol: "Replication".to_string(),
479 })?;
480 let payload = oxicode::serde::encode_to_vec(&changes.to_vec(), oxicode::config::standard())
481 .map_err(|e| TdbError::Serialization(e.to_string()))?;
482 transport.send_replication_changes(node_id, &payload).await
483 }
484
485 pub async fn check_replica_health(&self) -> Result<()> {
487 let now = Utc::now();
488 let max_lag = self.config.max_lag_ms;
489
490 let mut replicas = self.replicas.write();
491 for replica in replicas.values_mut() {
492 let lag = (now - replica.last_sync).num_milliseconds();
493 replica.lag_ms = lag;
494
495 if lag > max_lag {
496 replica.status = ReplicationStatus::Lagging;
497 } else {
498 replica.status = ReplicationStatus::Healthy;
499 }
500 }
501
502 Ok(())
503 }
504
505 pub async fn resolve_conflict(
507 &mut self,
508 change1: &ReplicationChange,
509 change2: &ReplicationChange,
510 ) -> Result<ReplicationChange> {
511 let mut stats = self.stats.lock();
512 stats.conflicts_detected += 1;
513
514 let resolved = match self.config.conflict_resolution {
515 ConflictResolution::LastWriteWins => {
516 if change1.timestamp > change2.timestamp {
518 change1.clone()
519 } else {
520 change2.clone()
521 }
522 }
523 ConflictResolution::FirstWriteWins => {
524 if change1.timestamp < change2.timestamp {
526 change1.clone()
527 } else {
528 change2.clone()
529 }
530 }
531 ConflictResolution::Manual | ConflictResolution::Custom => {
532 if change1.timestamp > change2.timestamp {
536 change1.clone()
537 } else {
538 change2.clone()
539 }
540 }
541 };
542
543 stats.conflicts_resolved += 1;
544 Ok(resolved)
545 }
546
547 pub async fn check_and_failover(&mut self) -> Result<bool> {
549 if !self.config.auto_failover {
550 return Ok(false);
551 }
552
553 let replicas = self.replicas.read().clone();
555 let unhealthy_masters: Vec<_> = replicas
556 .values()
557 .filter(|r| r.role == ReplicaRole::Master && r.status != ReplicationStatus::Healthy)
558 .collect();
559
560 if unhealthy_masters.is_empty() {
561 return Ok(false);
562 }
563
564 let best_slave = replicas
566 .values()
567 .filter(|r| r.role == ReplicaRole::Slave && r.status == ReplicationStatus::Healthy)
568 .max_by_key(|r| r.last_lsn);
569
570 if let Some(slave) = best_slave {
571 self.promote_to_master(&slave.node_id).await?;
572 return Ok(true);
573 }
574
575 Ok(false)
576 }
577
578 pub fn node_id(&self) -> &str {
580 &self.node_id
581 }
582
583 pub fn role(&self) -> ReplicaRole {
585 *self.role.read()
586 }
587
588 pub fn stats(&self) -> ReplicationStats {
590 self.stats.lock().clone()
591 }
592
593 pub fn replica_count(&self) -> usize {
595 self.replicas.read().len()
596 }
597
598 pub fn healthy_replica_count(&self) -> usize {
600 self.replicas
601 .read()
602 .values()
603 .filter(|r| r.status == ReplicationStatus::Healthy)
604 .count()
605 }
606
607 pub fn current_lsn(&self) -> u64 {
609 *self.current_lsn.lock()
610 }
611
612 pub fn change_buffer_size(&self) -> usize {
614 self.change_buffer.lock().len()
615 }
616}
617
618#[cfg(test)]
619mod tests {
620 use super::*;
621
622 #[tokio::test]
623 async fn test_replication_manager_creation() {
624 let config = ReplicationConfig::default();
625 let manager = ReplicationManager::with_transport(
626 "node1".to_string(),
627 config,
628 crate::consensus::transport::LoopbackSimulationTransport::arc(),
629 );
630
631 assert_eq!(manager.node_id(), "node1");
632 assert_eq!(manager.role(), ReplicaRole::Master);
633 assert_eq!(manager.replica_count(), 0);
634 }
635
636 #[tokio::test]
637 async fn test_replicate_changes_without_transport_fails_loudly() {
638 let config = ReplicationConfig::default();
642 let mut manager = ReplicationManager::new("node1".to_string(), config);
643 manager
644 .add_replica("replica1".to_string(), "http://replica1:8080".to_string())
645 .await
646 .unwrap();
647
648 let changes = vec![ReplicationChange {
649 lsn: 1,
650 txn_id: "txn-1".to_string(),
651 timestamp: Utc::now(),
652 data: vec![1, 2, 3],
653 source_node: "node1".to_string(),
654 }];
655
656 let err = manager.replicate_changes(changes).await.unwrap_err();
657 assert!(matches!(
658 err,
659 TdbError::DistributedTransportNotConfigured { .. }
660 ));
661 assert_eq!(manager.stats().successful_replications, 0);
662 }
663
664 #[tokio::test]
665 async fn test_add_replicas() {
666 let config = ReplicationConfig::default();
667 let mut manager = ReplicationManager::with_transport(
668 "node1".to_string(),
669 config,
670 crate::consensus::transport::LoopbackSimulationTransport::arc(),
671 );
672
673 manager
674 .add_replica("replica1".to_string(), "http://replica1:8080".to_string())
675 .await
676 .unwrap();
677
678 manager
679 .add_replica("replica2".to_string(), "http://replica2:8080".to_string())
680 .await
681 .unwrap();
682
683 assert_eq!(manager.replica_count(), 2);
684 }
685
686 #[tokio::test]
687 async fn test_record_change() {
688 let config = ReplicationConfig::default();
689 let mut manager = ReplicationManager::with_transport(
690 "node1".to_string(),
691 config,
692 crate::consensus::transport::LoopbackSimulationTransport::arc(),
693 );
694
695 let lsn = manager
696 .record_change("txn-001".to_string(), vec![1, 2, 3, 4])
697 .await
698 .unwrap();
699
700 assert_eq!(lsn, 1);
701 assert_eq!(manager.current_lsn(), 1);
702
703 let stats = manager.stats();
704 assert_eq!(stats.total_changes, 1);
705 assert_eq!(stats.total_bytes_replicated, 4);
706 }
707
708 #[tokio::test]
709 async fn test_async_replication() {
710 let config = ReplicationConfig {
711 sync_mode: SyncMode::Async,
712 ..Default::default()
713 };
714
715 let mut manager = ReplicationManager::with_transport(
716 "node1".to_string(),
717 config,
718 crate::consensus::transport::LoopbackSimulationTransport::arc(),
719 );
720
721 manager
722 .add_replica("replica1".to_string(), "http://replica1:8080".to_string())
723 .await
724 .unwrap();
725
726 let changes = vec![ReplicationChange {
727 lsn: 1,
728 txn_id: "txn-001".to_string(),
729 timestamp: Utc::now(),
730 data: vec![1, 2, 3],
731 source_node: "node1".to_string(),
732 }];
733
734 manager.replicate_changes(changes).await.unwrap();
735
736 let stats = manager.stats();
737 assert_eq!(stats.successful_replications, 1);
738 }
739
740 #[tokio::test]
741 async fn test_sync_replication() {
742 let config = ReplicationConfig {
743 sync_mode: SyncMode::Sync,
744 ..Default::default()
745 };
746
747 let mut manager = ReplicationManager::with_transport(
748 "node1".to_string(),
749 config,
750 crate::consensus::transport::LoopbackSimulationTransport::arc(),
751 );
752
753 manager
754 .add_replica("replica1".to_string(), "http://replica1:8080".to_string())
755 .await
756 .unwrap();
757
758 manager
759 .add_replica("replica2".to_string(), "http://replica2:8080".to_string())
760 .await
761 .unwrap();
762
763 let changes = vec![ReplicationChange {
764 lsn: 1,
765 txn_id: "txn-001".to_string(),
766 timestamp: Utc::now(),
767 data: vec![1, 2, 3],
768 source_node: "node1".to_string(),
769 }];
770
771 manager.replicate_changes(changes).await.unwrap();
772
773 let stats = manager.stats();
774 assert_eq!(stats.successful_replications, 2);
775 }
776
777 #[tokio::test]
778 async fn test_semi_sync_replication() {
779 let config = ReplicationConfig {
780 sync_mode: SyncMode::SemiSync,
781 ..Default::default()
782 };
783
784 let mut manager = ReplicationManager::with_transport(
785 "node1".to_string(),
786 config,
787 crate::consensus::transport::LoopbackSimulationTransport::arc(),
788 );
789
790 manager
791 .add_replica("replica1".to_string(), "http://replica1:8080".to_string())
792 .await
793 .unwrap();
794
795 let changes = vec![ReplicationChange {
796 lsn: 1,
797 txn_id: "txn-001".to_string(),
798 timestamp: Utc::now(),
799 data: vec![1, 2, 3],
800 source_node: "node1".to_string(),
801 }];
802
803 manager.replicate_changes(changes).await.unwrap();
804
805 let stats = manager.stats();
806 assert!(stats.successful_replications >= 1);
807 }
808
809 #[tokio::test]
810 async fn test_promote_to_master() {
811 let config = ReplicationConfig::default();
812 let mut manager = ReplicationManager::with_transport(
813 "node1".to_string(),
814 config,
815 crate::consensus::transport::LoopbackSimulationTransport::arc(),
816 );
817
818 manager
819 .add_replica("replica1".to_string(), "http://replica1:8080".to_string())
820 .await
821 .unwrap();
822
823 manager.promote_to_master("replica1").await.unwrap();
824
825 let stats = manager.stats();
826 assert_eq!(stats.failovers, 1);
827 }
828
829 #[tokio::test]
830 async fn test_conflict_resolution_last_write_wins() {
831 let config = ReplicationConfig {
832 conflict_resolution: ConflictResolution::LastWriteWins,
833 mode: ReplicationMode::MasterMaster,
834 ..Default::default()
835 };
836
837 let mut manager = ReplicationManager::with_transport(
838 "node1".to_string(),
839 config,
840 crate::consensus::transport::LoopbackSimulationTransport::arc(),
841 );
842
843 let change1 = ReplicationChange {
844 lsn: 1,
845 txn_id: "txn-001".to_string(),
846 timestamp: Utc::now(),
847 data: vec![1, 2, 3],
848 source_node: "node1".to_string(),
849 };
850
851 tokio::time::sleep(std::time::Duration::from_millis(10)).await;
852
853 let change2 = ReplicationChange {
854 lsn: 2,
855 txn_id: "txn-001".to_string(),
856 timestamp: Utc::now(),
857 data: vec![4, 5, 6],
858 source_node: "node2".to_string(),
859 };
860
861 let resolved = manager.resolve_conflict(&change1, &change2).await.unwrap();
862
863 assert_eq!(resolved.data, vec![4, 5, 6]);
865
866 let stats = manager.stats();
867 assert_eq!(stats.conflicts_detected, 1);
868 assert_eq!(stats.conflicts_resolved, 1);
869 }
870
871 #[tokio::test]
872 async fn test_conflict_resolution_first_write_wins() {
873 let config = ReplicationConfig {
874 conflict_resolution: ConflictResolution::FirstWriteWins,
875 mode: ReplicationMode::MasterMaster,
876 ..Default::default()
877 };
878
879 let mut manager = ReplicationManager::with_transport(
880 "node1".to_string(),
881 config,
882 crate::consensus::transport::LoopbackSimulationTransport::arc(),
883 );
884
885 let change1 = ReplicationChange {
886 lsn: 1,
887 txn_id: "txn-001".to_string(),
888 timestamp: Utc::now(),
889 data: vec![1, 2, 3],
890 source_node: "node1".to_string(),
891 };
892
893 tokio::time::sleep(std::time::Duration::from_millis(10)).await;
894
895 let change2 = ReplicationChange {
896 lsn: 2,
897 txn_id: "txn-001".to_string(),
898 timestamp: Utc::now(),
899 data: vec![4, 5, 6],
900 source_node: "node2".to_string(),
901 };
902
903 let resolved = manager.resolve_conflict(&change1, &change2).await.unwrap();
904
905 assert_eq!(resolved.data, vec![1, 2, 3]);
907 }
908
909 #[tokio::test]
910 async fn test_replica_health_check() {
911 let config = ReplicationConfig {
912 max_lag_ms: 100,
913 ..Default::default()
914 };
915
916 let manager = ReplicationManager::with_transport(
917 "node1".to_string(),
918 config,
919 crate::consensus::transport::LoopbackSimulationTransport::arc(),
920 );
921
922 manager.replicas.write().insert(
923 "replica1".to_string(),
924 ReplicaNode {
925 node_id: "replica1".to_string(),
926 endpoint: "http://replica1:8080".to_string(),
927 role: ReplicaRole::Slave,
928 status: ReplicationStatus::Healthy,
929 last_sync: Utc::now() - Duration::seconds(1),
930 lag_ms: 0,
931 last_lsn: 10,
932 },
933 );
934
935 manager.check_replica_health().await.unwrap();
936
937 let replicas = manager.replicas.read();
938 let replica = replicas.get("replica1").unwrap();
939 assert!(replica.lag_ms > 100);
940 assert_eq!(replica.status, ReplicationStatus::Lagging);
941 }
942
943 #[tokio::test]
944 async fn test_change_buffer() {
945 let config = ReplicationConfig {
946 change_buffer_size: 3,
947 ..Default::default()
948 };
949
950 let mut manager = ReplicationManager::with_transport(
951 "node1".to_string(),
952 config,
953 crate::consensus::transport::LoopbackSimulationTransport::arc(),
954 );
955
956 for i in 0..5 {
958 manager
959 .record_change(format!("txn-{:03}", i), vec![i as u8])
960 .await
961 .unwrap();
962 }
963
964 assert_eq!(manager.change_buffer_size(), 3);
966 }
967
968 #[tokio::test]
969 async fn test_replication_stats() {
970 let config = ReplicationConfig::default();
971 let mut manager = ReplicationManager::with_transport(
972 "node1".to_string(),
973 config,
974 crate::consensus::transport::LoopbackSimulationTransport::arc(),
975 );
976
977 manager
978 .add_replica("replica1".to_string(), "http://replica1:8080".to_string())
979 .await
980 .unwrap();
981
982 for i in 0..5 {
984 manager
985 .record_change(format!("txn-{:03}", i), vec![1, 2, 3])
986 .await
987 .unwrap();
988 }
989
990 let stats = manager.stats();
991 assert_eq!(stats.total_changes, 5);
992 assert_eq!(stats.total_bytes_replicated, 15);
993 }
994
995 #[tokio::test]
996 async fn test_remove_replica() {
997 let config = ReplicationConfig::default();
998 let mut manager = ReplicationManager::with_transport(
999 "node1".to_string(),
1000 config,
1001 crate::consensus::transport::LoopbackSimulationTransport::arc(),
1002 );
1003
1004 manager
1005 .add_replica("replica1".to_string(), "http://replica1:8080".to_string())
1006 .await
1007 .unwrap();
1008
1009 assert_eq!(manager.replica_count(), 1);
1010
1011 manager.remove_replica("replica1").await.unwrap();
1012 assert_eq!(manager.replica_count(), 0);
1013 }
1014}