{"id":17081487,"url":"https://github.com/benfoster/ditto","last_synced_at":"2026-04-29T22:01:49.995Z","repository":{"id":55666493,"uuid":"133404972","full_name":"benfoster/ditto","owner":"benfoster","description":"Cross-cluster replication tool for Event Store","archived":false,"fork":false,"pushed_at":"2020-12-04T21:06:28.000Z","size":133,"stargazers_count":0,"open_issues_count":4,"forks_count":2,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-12-22T19:59:47.528Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/benfoster.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-05-14T18:29:21.000Z","updated_at":"2021-09-18T02:15:17.000Z","dependencies_parsed_at":"2022-08-15T06:00:40.671Z","dependency_job_id":null,"html_url":"https://github.com/benfoster/ditto","commit_stats":null,"previous_names":[],"tags_count":10,"template":false,"template_full_name":null,"purl":"pkg:github/benfoster/ditto","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benfoster%2Fditto","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benfoster%2Fditto/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benfoster%2Fditto/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benfoster%2Fditto/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/benfoster","download_url":"https://codeload.github.com/benfoster/ditto/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benfoster%2Fditto/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32445555,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-29T20:22:27.477Z","status":"ssl_error","status_checked_at":"2026-04-29T20:22:26.507Z","response_time":110,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-14T12:53:17.286Z","updated_at":"2026-04-29T22:01:49.966Z","avatar_url":"https://github.com/benfoster.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Ditto\n\nDitto is a cluster replication tool for [Event Store](http://eventstore.org). It works by subscribing to specific streams from a _source_ cluster and replicating them to a _destination_ cluster.\n\nIt was designed to be run as a standalone application using Docker and uses [Event Store Subscription Groups](https://eventstore.com/docs/dotnet-api/competing-consumers/index.html) to subscribe to the source server/cluster.\n\nMost of the code is part of our boilerplate Event Consumer template and automatically takes care of Event Store connection management and logging via Serilog. The application is configured to use [Seq](https://datalust.co/seq) locally and Datadog in production.\n\n### Docker Image\n\nDocker images are hosted on https://hub.docker.com/r/benfoster/ditto.\n\n```\ndocker pull benfoster/ditto\n```\n\n\n### Configuration\n\nThe application can be configured via JSON (`appsettings.json`) or using Environment Variables:\n\n| JSON Setting | Environment Variable | Default Value | Description |\n| ------------ | -------------------- | ----------- | ----------- |\n| SourceEventStoreConnectionString | Ditto_Settings__SourceEventStoreConnectionString |   | The source event store connection string |\n| DestinationEventStoreConnectionString | Ditto_Settings__DestinationEventStoreConnectionString |   | The destination event store connection string |\n| PersistentSubscriptionBufferSize | Ditto_Settings__PersistentSubscriptionBufferSize | 10 | The buffer size of the subscription. This should be increased for large, frequently updated streams |\n| ReplicationThrottleInterval | Ditto_Settings__ReplicationThrottleInterval | 0 | The interval in milliseconds to wait between events. This can be useful if you want to reduce the load on your source server |\n| SkipVersionCheck | Ditto_Settings__SkipVersionCheck | false | Whether to skip the version check when replicating streams. This may need to be enabled if you are partially replicating streams rather than reading from the beginning |\n| Subscriptions | Ditto_Settings__Subscriptions |  | Array of the persistent subscriptions that should be used for replication |\n\n#### Idempotency\n\nEvents are replicated without modification. Event Store provides best effort idempotency based on the Stream and Event identifiers. If version checking is enabled (see above) then Event Store guarantees idempotency. [More information](https://eventstore.com/docs/dotnet-api/optimistic-concurrency-and-idempotence/index.html#idempotence).\n\n### Metrics\n\nMetrics are exposed on http://localhost:5000/metrics in Prometheus format.\n\n### Replication Considerations\n\nDitto was originally designed to replicate the `$all` system stream. However, I found that this resulted in the replication of a lot of internal streams/events which we did not want. Since it's [not currently possible](https://github.com/EventStore/EventStore/issues/718) to ignore system streams I opted to explicitly specify the streams to replicate.\n\nWe usually subscribe to category streams e.g. `$ce-emails` and then populate the original streams on the destination cluster, for example:\n\n![Ditto in action](docs/img/ditto.png)\n\n### Running locally\n\nTo run the example, clone the repository and run `docker-compose -f docker-compose.yml -f docker-compose.apps.yml up --build`. This will start:\n\n1. Source Event Store at [http://localhost:2113](http://localhost:2113)\n2. Destination Event Store at [http://localhost:4113](http://localhost:4113)\n3. Localstack\n4. SEQ at [http://localhost:5341](http://localhost:5341)\n5. Ditto (Default)\n6. Ditto (Kinesis)\n\nThe docker setup will automatically seed the source event store with \"customer\" events that you can view at http://localhost:2113/web/index.html#/streams/$ce-customer and setup a persistent subscription for the `$ce-customer` category stream.\n\nDitto will connect to the persistent subscription and start consuming events.\n\nTo test the replication you can use the Event Store HTTP API to create some customer events on the source:\n\n```\ncurl --location --request POST 'http://localhost:2113/streams/customer-6d3d4dca-9889-416b-a266-4bf760261f01' \\\n-u admin:changeit \\\n-H 'Content-Type: application/vnd.eventstore.events+json' \\\n--data-raw '[\n    {\n        \"eventId\": \"ab932527-5b9a-4868-82f3-42194bbfd022\",\n        \"eventType\": \"customer_registered\",\n        \"data\": {\n            \"first_name\": \"John\",\n            \"last_name\": \"Smith\",\n            \"phone_number\": \"0111111111111\"\n        },\n        \"metadata\": {\n            \"source\": \"ditto\"\n        }\n    }\n]'\n```\n\nYou should then be able to browse to the stream at [http://localhost:2113/web/index.html#/streams/customer-6d3d4dca-9889-416b-a266-4bf760261f01](http://localhost:2113/web/index.html#/streams/customer-6d3d4dca-9889-416b-a266-4bf760261f01) and see the event in the `$ce-customer` category stream at [http://localhost:213/web/index.html#/streams/$ce-customer](http://localhost:2113/web/index.html#/streams/$ce-customer).\n\nDitto will then replicate this event to the destination server/cluster. You should see output similar to the following in stdout and in SEQ:\n\n```\nReplicating customer_registered #0 from customer-cbea713f-b396-46f4-8c67-81e04b37d334 (Original Event: #16) completed in 17.7 ms\n```\n\nYou should then be able to browse to the same stream on the destination server at [http://localhost:4113/web/index.html#/streams/customer-6d3d4dca-9889-416b-a266-4bf760261f01](http://localhost:4113/web/index.html#/streams/customer-6d3d4dca-9889-416b-a266-4bf760261f01).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbenfoster%2Fditto","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbenfoster%2Fditto","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbenfoster%2Fditto/lists"}