Game backend met event sourcing

Pagina: 1
Acties:

Acties:
  • 0 Henk 'm!

  • Cartman!
  • Registratie: April 2000
  • Niet online
Ik ben een beetje op zoek naar input voor wat een mooie manier zou zijn om een event sourced backend te maken. Vorig jaar heb ik de backend opgeleverd voor een game waar ik uit kon gaan van deze dingen:

- Single player
- Client push only (client stuurt request, krijgt response, nooit push van server naar client)
- Event sourced

Wat we gemaakt hebben was een PHP app gebouwd op Symfony Framework die gebruik maakt van Redis shards (tijdens registratie kom je op een shard uit, daar blijft de data) om het profiel van de speler op te slaan. De events schrijven we weg naar de event store (in ons geval Amazon Kinesis Firehose). Dit heeft voor die app enorm goed gewerkt en een ruim jaar later hebben we rond de 6 miljoen accounts. Groot nadeel qua architectuur hierin is dat we aan t eind van de request de events wegschrijven naar de event store en als dat gelukt is updaten we de speler z'n profiel dmv 1 (pipelined) call naar Redis. Hiermee is een 2 Generals Problem ontstaan.

Voor een nieuwe backend heb ik echter ook de requirement dat er ondersteuning voor multiplayer moet komen en dan kom ik niet meer weg met de shards in Redis zoals dit nu geimplementeerd is, dan maak ik het probleem namelijk nog groter door te moeten schrijven naar meerdere shards. Ook wil ik in het geheel af van het 2 Generals Problem. Er is ook al bekend dat bepaalde events bijvoorbeeld push messages of mailtjes moeten gaan versturen.

De oplossing zoals ik het zie is om gebruik te maken van een event store (ik kijk nu naar Apache Kafka, Redis Streams klinkt ook ideaal maar is nog niet beschikbaar) met daar aan gekoppeld meerdere consumers die de events verwerken. Daarbij ben ik echter bang dat het (vaak) zal gebeuren dat de consumers de events nog niet verwerkt hebben en de client dus acties probeert uit te voeren die de backend (nog) niet kan valideren. Deze backend zal met veel meer players te maken krijgen dan de app die we hebben opgeleverd dus schaalbaarheid is echt enorm belangrijk maar daarbij moet betrouwbaarheid niet worden vergeten.

Ik heb natuurlijk al best wel wat gelezen over event sourcing en ook CQRS maar hou een beetje deze vragen:

- Hoe voorkom ik het 2 generals problem waarbij ik in 1 request naar meerdere DB's moet schrijven?
- Hoe zit het in praktijk met latency als je enkel werkt met events/consumers?

Is er iemand die antwoord kan geven op bovenstaande vragen of ervaring heeft met dergelijke architectuur en me in een andere/betere richting kan duwen?

Acties:
  • 0 Henk 'm!

  • Infinitive
  • Registratie: Maart 2001
  • Laatst online: 25-09-2023
Geen antwoord op je vraag, maar mogelijk een insteekpunt:

wil je niet een database gebruiken dat deze problemen voor je kan oplossen. Zoals key-value stores met CRDTs en transacties met casual consistency. Voorbeeld met wat leesvoer: http://syncfree.github.io/antidote/

putStr $ map (x -> chr $ round $ 21/2 * x^3 - 92 * x^2 + 503/2 * x - 105) [1..4]


Acties:
  • 0 Henk 'm!

  • Cartman!
  • Registratie: April 2000
  • Niet online
Dank voor je inbreng maar het gaat me niet om welke database ik ga gebruiken maar met name hoe ik voorkom dat ik naar meerdere databases/externe services in 1 request moet schrijven.

[ Voor 4% gewijzigd door Cartman! op 03-01-2018 10:49 ]


Acties:
  • 0 Henk 'm!

  • Kobus Post
  • Registratie: September 2010
  • Laatst online: 06-10 17:46
Is Service Fabric geen oplossing voor je? Een Actor service die je de events laat verwerken en Service Fabric regelt zelf het bijhouden van State. Schaalbaarheid is ook top.

https://azure.microsoft.com/en-us/services/service-fabric/

No trees were harmed in the creation of this message, but several thousand electrons were mildly inconvenienced.


Acties:
  • 0 Henk 'm!

  • drdextro
  • Registratie: Oktober 2010
  • Laatst online: 07-10 19:20
Een tijd geleden heb ik een voorbeeld gemaakt van CQRS met ES in Laravel. Misschien kan je hier nog iets mee:

https://github.com/rapideinternet/demo.cqrs

Acties:
  • 0 Henk 'm!

  • Cartman!
  • Registratie: April 2000
  • Niet online
Kobus Post schreef op woensdag 3 januari 2018 @ 11:15:
Is Service Fabric geen oplossing voor je? Een Actor service die je de events laat verwerken en Service Fabric regelt zelf het bijhouden van State. Schaalbaarheid is ook top.

https://azure.microsoft.com/en-us/services/service-fabric/
Interessant, met name de Actor Service lijkt iets te zijn waarin je de state kunt schaalbaar kunt bijhouden. Los van dat we het in principe in PHP gaan doen zitten we ook "vast"aan AWS en daarmee valt Azure een beetje af. Een setup waar ik bij AWS ook aan zat te denken is om gebruik te maken van DynamoDB en Lambda maar gezien vrijwel alle services bij AWS over https verlopen is de latency altijd vrij hoog (een request naar Kinesis Firehose duurt ~80ms terwijl de request zonder dit in totaal 3ms duurt...).

Kom zelf dit tegen nu wat een goed artikel lijkt te zijn: https://medium.com/retail...and-dynamodb-7d78e992a02d
drdextro schreef op woensdag 3 januari 2018 @ 11:57:
Een tijd geleden heb ik een voorbeeld gemaakt van CQRS met ES in Laravel. Misschien kan je hier nog iets mee:

https://github.com/rapideinternet/demo.cqrs
Zonder alle code in te duiken, hoe ga jij om met het opslaan van je events en het opslaan van de logica (bijv projectors)?

Ik zie bijv een mailer service, die mag (zoals ik het zie) pas gaan mailen als alle events succesvol zijn opgeslagen. Echter kan het dan voorkomen dat de mailserver down is waardoor iemand geen mail krijgt en je dit niet opnieuw kunt proberen omdat je request al voorbij is. Daarom klinkt voor mij een oplossing als Kafka erg goed en voor het sturen van mailtjes maakt t niet uit of dat 10ms, 1 seconde of 10 seconden duurt maar het is wel belangrijk dat iemands profiel wel zeer snel geupdated wordt.

[ Voor 40% gewijzigd door Cartman! op 03-01-2018 12:09 ]


Acties:
  • +1 Henk 'm!

  • Sandor_Clegane
  • Registratie: Januari 2012
  • Niet online

Sandor_Clegane

Fancy plans and pants to match

Doe je CQRS en DDD en Event sourcing? The whole shebang zeg maar? Of CQRS met Event Sourcing maar geen Aggregates?

Ik zou niet sharden, synchronisatie is een moeilijk probleem. Heb je tests gedaan? 6 miljoen records in een Aggregate is niet zoveel lijkt me, een beetje een redis instance schrijft dit zo in memory weg. Door een aggregate verantwoordelijk te houden voor maar 1 entiteit ( alle spelers bijvoorbeeld ) en deze zijn eigen lokale storage te geven moet dat wel goed schalen.

Verder voor elke speler een Actor is een mooi idee, maar als je dat wilt schalen moet je wel redelijk zeker zijn van je software stack dat dit gaat werken.
Hoeveel spelers zijn er gelijktijdig als ik vragen mag?

Met Erlang zou ik het aandurven met Amnesia op de backend. Doet whatsapp ook, elke client is een Actor met een mailbox en deze Actor encapsuleert zijn eigen state een stuurt alleen maar berichtjes naar een andere Actor ( speler ). Akka is ook nog een oplossing.

Wel een toffe uitdaging moet ik zeggen :)

Less alienation, more cooperation.


Acties:
  • 0 Henk 'm!

  • Cartman!
  • Registratie: April 2000
  • Niet online
Sandor_Clegane schreef op woensdag 3 januari 2018 @ 12:14:
Doe je CQRS en DDD en Event sourcing? The whole shebang zeg maar? Of CQRS met Event Sourcing maar geen Aggregates?
Die keuze is niet perse nog gemaakt zo gezien t stadium van t project waar we in zitten maar ik neig wel naar the whole shebang ja.
Ik zou niet sharden, synchronisatie is een moeilijk probleem. Heb je tests gedaan? 6 miljoen records in een Aggregate is niet zoveel lijkt me, een beetje een redis instance schrijft dit zo in memory weg. Door een aggregate verantwoordelijk te houden voor maar 1 entiteit ( alle spelers bijvoorbeeld ) en deze zijn eigen lokale storage te geven moet dat wel goed schalen.
Als we een dienst gebruiken als DynamoDB zou dit overigens niet nodig zijn maar we zijn enorm gek op Redis en dan is het lastig gokken op dat de grooste instance size ook groot genoeg zal zijn. Het gaat niet om 6 miljoen records alleen, per speler heb je aan 1 key niet genoeg maar heb je misschien wel honderden of duizenden keys nodig. Dat Redis snel genoeg is, is bekend maar het gaat me vooral om de limitatie in het werken met 1 instance.
Verder voor elke speler een Actor is een mooi idee, maar als je dat wilt schalen moet je wel redelijk zeker zijn van je software stack dat dit gaat werken.
Hoeveel spelers zijn er gelijktijdig als ik vragen mag?
Dat is nog niet bekend maar ik reken op tienduizenden wereldwijd. Overigens denk ik niet aan een Actor per speler maar een consumer per type van acties. Bijv een consumer die de profielen bijwerkt, een consumer die push messages stuurt, een consumer die mailtjes stuurt, etc. Van elke consumer draaien er zoveel als nodig natuurlijk.
Met Erlang zou ik het aandurven met Amnesia op de backend. Doet whatsapp ook, elke client is een Actor met een mailbox en deze Actor encapsuleert zijn eigen state een stuurt alleen maar berichtjes naar een andere Actor ( speler ). Akka is ook nog een oplossing.
Interessant, ga ik meer over lezen.
Wel een toffe uitdaging moet ik zeggen :)
Ja, zeker tof maar ook lastig ;)

[ Voor 12% gewijzigd door Cartman! op 03-01-2018 12:28 ]


Acties:
  • 0 Henk 'm!

  • Sandor_Clegane
  • Registratie: Januari 2012
  • Niet online

Sandor_Clegane

Fancy plans and pants to match

Hoe zit dat in elkaar? Delen ze een spelwereld? Een instance zeg maar? Of is het hele spel voor iedereen altijd toegankelijk en iedereen is actief als het ware?

Werk je met instances dan zou je elke "sessie" een Aggregate kunnen maken en iedereen stuurt zijn updates naar deze instance en deze is verantwoordelijk om deze sequentieel te verwerken ( een queue zeg maar waarin berichtjes komen "speler1 raakt speler2" etc. ) en deze houdt de state van de spelwereld bij. Deze instances zijn dan ook "vluchtig" zeg maar. Nadat iemand gewonnen heeft werk je de spelers bij ( leaderboards oid ) en dan kan deze Aggregate weer verdwijnen. Zo hoef je deze events ook niet op te slaan voor een sessie.

Ik weet niet in welke taal je werkt, maar 10000 tegelijk is niet heel veel voor zoiets als Erlang, dat kan Akka of Akka.net ook prima aan. In .Net en F# kun je op een redelijke machine zo 60000 mailboxprocessors aanmaken (actor met een mailbox en een queue ). Probleem is wel als er zoveel berichten binnenkomen dat hij ze niet meer kan verwerken ( backpush ) maar op een beetje hardware en de latency van het internet zou ik dat zeker eens proberen of dit uberhaupt wel een probleem is.

[ Voor 26% gewijzigd door Sandor_Clegane op 03-01-2018 12:31 ]

Less alienation, more cooperation.


Acties:
  • 0 Henk 'm!

  • Cartman!
  • Registratie: April 2000
  • Niet online
Sandor_Clegane schreef op woensdag 3 januari 2018 @ 12:26:
Hoe zit dat in elkaar? Delen ze een spelwereld? Een instance zeg maar? Of is het hele spel voor iedereen altijd toegankelijk en iedereen is actief als het ware?

Werk je met instances dan zou je elke "sessie" een Aggregate kunnen maken en iedereen stuurt zijn updates naar deze instance en deze is verantwoordelijk om deze sequentieel te verwerken ( een queue zeg maar waarin berichtjes komen "speler1 raakt speler2" etc. ) en deze houdt de state van de spelwereld bij. Deze instances zijn dan ook "vluchtig" zeg maar. Nadat iemand gewonnen heeft werk je de spelers bij ( leaderboards oid ) en dan kan deze Aggregate weer verdwijnen. Zo hoef je deze events ook niet op te slaan voor een sessie.
De spelers delen in principe geen spelwereld maar moeten wel vrienden van elkaar kunnen worden en bij elkaar iets uit de inventory kunnen "lenen" om in hun eigen spel verder te komen. Of ik het echt mutliplayer moet noemen weet ik niet maar ik moet er dus rekening mee houden dat een speler niet in isolement leeft zoals bij de app die al draait wel het geval is (waardoor sharding dus simpel opgelost kon worden).

Acties:
  • 0 Henk 'm!

  • Sandor_Clegane
  • Registratie: Januari 2012
  • Niet online

Sandor_Clegane

Fancy plans and pants to match

Je zou iedereen die actief is een Actor kunnen maken ( komt ook redelijk dicht bij de werkelijkheid ;) ) en die heeft zijn eigen procesje op de server en deze verstuurt voor hem alle berichtjes ( stuur item naar andere speler bijvoorbeeld ) of voeg toe aan vriendenlijst.

Een andere manier is om een aggregate met alle spelers te maken en een aggregate voor de inventory oid, wil je dan een update aan de inventory doen dan stuur je een berichtje naar de aggegrate voor de inventory met de opdracht haal uit inventory met GUID item met GUID en zet deze in inventory met GUID. Dan is het ook redelijk atomisch.

Deze events zet je dan in je eventstore.

[ Voor 3% gewijzigd door Sandor_Clegane op 03-01-2018 12:39 ]

Less alienation, more cooperation.


Acties:
  • 0 Henk 'm!

  • drdextro
  • Registratie: Oktober 2010
  • Laatst online: 07-10 19:20
[b][message=53765079,noline]
Zonder alle code in te duiken, hoe ga jij om met het opslaan van je events en het opslaan van de logica (bijv projectors)?

Ik zie bijv een mailer service, die mag (zoals ik het zie) pas gaan mailen als alle events succesvol zijn opgeslagen. Echter kan het dan voorkomen dat de mailserver down is waardoor iemand geen mail krijgt en je dit niet opnieuw kunt proberen omdat je request al voorbij is. Daarom klinkt voor mij een oplossing als Kafka erg goed en voor het sturen van mailtjes maakt t niet uit of dat 10ms, 1 seconde of 10 seconden duurt maar het is wel belangrijk dat iemands profiel wel zeer snel geupdated wordt.
Bij het opslaan van events worden de events gefired en op de queue geplaatst. Dit is in dit geval in de database maar Kafka is hier een hele goeie oplossing voor. (Zie ook https://github.com/rapideinternet/laravel-queue-kafka). Kafka bied namelijk storage voor event streams. Hierdoor kun je altijd een replay doen van een bepaald model en zo je models terug brengen naar een bepaalde state.

Het opslaan van de ReadModels gebeurd in dit geval in Elasticsearch. Dit bied snelle zoekfunctionaliteit en voor dit voorbeeld was dit zeer geschikt.

Kafka werkt d.m.v. consumers met pointers. Dit betekend dat verschillende consumers op verschillende punten in een evenstream kunnen zijn. En pas zodra er een consumer een ack heeft gegeven word de pointer opgeschoven. Mocht dus een mailserver down zijn zal hij nooit de ack geven aan de queue en gaat hij pas verder met het afhandelen van de queue zodra hij weer kan versturen.

Acties:
  • 0 Henk 'm!

  • Cartman!
  • Registratie: April 2000
  • Niet online
drdextro schreef op woensdag 3 januari 2018 @ 12:53:
[...]


Bij het opslaan van events worden de events gefired en op de queue geplaatst. Dit is in dit geval in de database maar Kafka is hier een hele goeie oplossing voor. (Zie ook https://github.com/rapideinternet/laravel-queue-kafka). Kafka bied namelijk storage voor event streams. Hierdoor kun je altijd een replay doen van een bepaald model en zo je models terug brengen naar een bepaalde state.

Het opslaan van de ReadModels gebeurd in dit geval in Elasticsearch. Dit bied snelle zoekfunctionaliteit en voor dit voorbeeld was dit zeer geschikt.

Kafka werkt d.m.v. consumers met pointers. Dit betekend dat verschillende consumers op verschillende punten in een evenstream kunnen zijn. En pas zodra er een consumer een ack heeft gegeven word de pointer opgeschoven. Mocht dus een mailserver down zijn zal hij nooit de ack geven aan de queue en gaat hij pas verder met het afhandelen van de queue zodra hij weer kan versturen.
Ik ben bekend met Kafka (zoals genoemd is het waar ik enorm naar neig om te gaan gebruiken) maar het zit hem vooral in het updaten van de readmodels. In de meeste voorbeelden/tutorials kom ik tegen dat de app die direct update terwijl je dan het 2 Generals Problem creeert en daar wil ik dus vanaf. Het enige nadeel wat ik daar in kan ontdekken is dat er misschien teveel latency ontstaat waardoor iemand in de app een actie wil uitvoeren die in het readmodel nog niet is doorgekomen waardoor de app niet verder kan. Misschien maak ik me daar ook teveel zorgen over, vandaar dit topic :)

Acties:
  • 0 Henk 'm!

  • Sandor_Clegane
  • Registratie: Januari 2012
  • Niet online

Sandor_Clegane

Fancy plans and pants to match

Cartman! schreef op woensdag 3 januari 2018 @ 13:31:
[...]

Ik ben bekend met Kafka (zoals genoemd is het waar ik enorm naar neig om te gaan gebruiken) maar het zit hem vooral in het updaten van de readmodels. In de meeste voorbeelden/tutorials kom ik tegen dat de app die direct update terwijl je dan het 2 Generals Problem creeert en daar wil ik dus vanaf. Het enige nadeel wat ik daar in kan ontdekken is dat er misschien teveel latency ontstaat waardoor iemand in de app een actie wil uitvoeren die in het readmodel nog niet is doorgekomen waardoor de app niet verder kan. Misschien maak ik me daar ook teveel zorgen over, vandaar dit topic :)
Hoe snel moet iemand op de hoogte zijn van de wijziging? Hij ziet de update toch vanzelf?
Ik snap je 2 Generals probleem niet, je hebt toch 1 waarheid? De aggregate of een andere centrale registratie.

[ Voor 6% gewijzigd door Sandor_Clegane op 03-01-2018 13:49 ]

Less alienation, more cooperation.


Acties:
  • 0 Henk 'm!

  • Cartman!
  • Registratie: April 2000
  • Niet online
Sandor_Clegane schreef op woensdag 3 januari 2018 @ 13:47:
[...]


Hoe snel moet iemand op de hoogte zijn van de wijziging? Hij ziet de update toch vanzelf?
Ik kan als backend wel doorgeven wat de wijziging is (zodat de app ermee kan doen wat nodig is) maar als iemand daarna weer verder gaat in de app moet die wijziging wel verwerkt zijn (in bijv readmodel) om verdere wijzigingen succesvol te laten verlopen. Bijv: user X unlockt item Y en klik erop om deze te gebruiken, de backend moet wel direct op de hoogte zijn dat user X dat item heeft om te gebruiken. Als daar 10 seconden latency in zit dan zal de backend een foutmelding tonen dat de user dat item niet heeft.

Acties:
  • 0 Henk 'm!

  • Sandor_Clegane
  • Registratie: Januari 2012
  • Niet online

Sandor_Clegane

Fancy plans and pants to match

Cartman! schreef op woensdag 3 januari 2018 @ 13:51:
[...]

Ik kan als backend wel doorgeven wat de wijziging is (zodat de app ermee kan doen wat nodig is) maar als iemand daarna weer verder gaat in de app moet die wijziging wel verwerkt zijn (in bijv readmodel) om verdere wijzigingen succesvol te laten verlopen. Bijv: user X unlockt item Y en klik erop om deze te gebruiken, de backend moet wel direct op de hoogte zijn dat user X dat item heeft om te gebruiken. Als daar 10 seconden latency in zit dan zal de backend een foutmelding tonen dat de user dat item niet heeft.
Gebruiker krijgt item A en geeft dus aan de backend door dat hij een item heeft gekregen: Update player inventory with item, nu wil hij een actie doen met dit item. Wat heeft dat dan voor gevolgen in jouw optiek?

In de validatie van het commando (doe actie met iets ) kan je natuurlijk prima de Aggregate uitvragen die verantwoordelijk is voor de inventory. ( heeft deze player dit item uberhaupt wel? ). Ervan uitgaande dat je een inventory aggregate hebt natuurlijk en je alles via DDD doet.

Less alienation, more cooperation.


Acties:
  • 0 Henk 'm!

  • Cartman!
  • Registratie: April 2000
  • Niet online
Sandor_Clegane schreef op woensdag 3 januari 2018 @ 14:12:
[...]


Gebruiker krijgt item A en geeft dus aan de backend door dat hij een item heeft gekregen: Update player inventory with item, nu wil hij een actie doen met dit item. Wat heeft dat dan voor gevolgen in jouw optiek?

In de validatie van het commando (doe actie met iets ) kan je natuurlijk prima de Aggregate uitvragen die verantwoordelijk is voor de inventory. ( heeft deze player dit item uberhaupt wel? ). Ervan uitgaande dat je een inventory aggregate hebt natuurlijk en je alles via DDD doet.
Backend krijgt request, controleert deze (met bijv readmodel(s)) en stuurt de events met wat er gebeurt is naar de event store. Een consumer zal dan (asynchroon) oa het readmodel updaten, daar kan dus vertraging ontstaan waardoor bij het uitvragen er misschien terugkomt dat de player dit item niet heeft terwijl dat wel zo zou moeten zijn.

Ik wil geen aggregate hebben die alles gaat replayen op iedere request gezien players uiteindelijk miljoenen events kunnen hebben. Bovendien lost dat denk ik m'n vraag/probleem niet op: Kafka kun je niet queryen bijvoorbeeld dus dan zou ik het ophalen van de events (danwel snapshot) voor de player ook achter kunnen lopen. Het uitlezen van de events zou daarmee namelijk niks anders worden dan een readmodel. Ik gebruik dan denk ik liever Redis (of andere NoSQL) database als efficiente readmodel om snel op te kunnen queryen.

Acties:
  • 0 Henk 'm!

  • Sandor_Clegane
  • Registratie: Januari 2012
  • Niet online

Sandor_Clegane

Fancy plans and pants to match

Cartman! schreef op woensdag 3 januari 2018 @ 14:20:
[...]

Backend krijgt request, controleert deze (met bijv readmodel(s)) en stuurt de events met wat er gebeurt is naar de event store. Een consumer zal dan (asynchroon) oa het readmodel updaten, daar kan dus vertraging ontstaan waardoor bij het uitvragen er misschien terugkomt dat de player dit item niet heeft terwijl dat wel zo zou moeten zijn.

Ik wil geen aggregate hebben die alles gaat replayen op iedere request gezien players uiteindelijk miljoenen events kunnen hebben. Bovendien lost dat denk ik m'n vraag/probleem niet op: Kafka kun je niet queryen bijvoorbeeld dus dan zou ik het ophalen van de events (danwel snapshot) voor de player ook achter kunnen lopen. Het uitlezen van de events zou daarmee namelijk niks anders worden dan een readmodel. Ik gebruik dan denk ik liever Redis (of andere NoSQL) database als efficiente readmodel om snel op te kunnen queryen.
Ik zou mijn aggregate in memory houden en dat replayen zou ik alleen doen als alles opnieuw opstart. Ik snap dat ook niet helemaal van eventsourcing, dat replayen voor elke vraag. Lijkt me onnodig.

Met een paar GB aan RAM kun je heel wat arrays van inventories in memory houden.

Less alienation, more cooperation.


Acties:
  • 0 Henk 'm!

  • Cartman!
  • Registratie: April 2000
  • Niet online
Sandor_Clegane schreef op woensdag 3 januari 2018 @ 14:26:
[...]
Ik zou mijn aggregate in memory houden en dat replayen zou ik alleen doen als alles opnieuw opstart. Ik snap dat ook niet helemaal van eventsourcing, dat replayen voor elke vraag. Lijkt me onnodig.

Met een paar GB aan RAM kun je heel wat arrays van inventories in memory houden.
Ik kan niet op elk systeem in het cluster aan webservers alle player in t geheugen gaan zetten, daar heb je NoSQL databases voor. Opnieuw starten is met PHP sowieso niet echt sprake van, in principe ben je op iedere request alles kwijt wat er is. Mijn hoofdvraag gaat er iig om hoe ik zorg dat die readmodels goed in sync blijven met m'n event store.

Acties:
  • 0 Henk 'm!

  • Sandor_Clegane
  • Registratie: Januari 2012
  • Niet online

Sandor_Clegane

Fancy plans and pants to match

Cartman! schreef op woensdag 3 januari 2018 @ 14:29:
[...]

Ik kan niet op elk systeem in het cluster aan webservers alle player in t geheugen gaan zetten, daar heb je NoSQL databases voor. Opnieuw starten is met PHP sowieso niet echt sprake van, in principe ben je op iedere request alles kwijt wat er is. Mijn hoofdvraag gaat er iig om hoe ik zorg dat die readmodels goed in sync blijven met m'n event store.
Maar dat is de kern van CQRS, de readmodels zijn niet in sync. Ze zijn eventueel consistent en kunnen achterlopen op de "waarheid".

Less alienation, more cooperation.


Acties:
  • 0 Henk 'm!

  • Cartman!
  • Registratie: April 2000
  • Niet online
Sandor_Clegane schreef op woensdag 3 januari 2018 @ 14:31:
[...]


Maar dat is de kern van CQRS, de readmodels zijn niet in sync. Ze zijn eventueel consistent en kunnen achterlopen op de "waarheid".
Precies, ik vraag me dus af hoe ik ervoor zorg dat ik met een game backend ervoor zorg dat die latency zo laag mogelijk is. Of moet ik event sourcing gewoon laten vallen en als "extra" beschouwen en wel alles direct in een database zetten.. :)

Redis Streams wat in Redis 4.0 komt lijkt me ook prachtig...dan kan ik zowel de events wegschrijven als m'n readmodel updaten in 1 request, het is alleen nog onduidelijk wanneer dat stable wordt.

[ Voor 16% gewijzigd door Cartman! op 03-01-2018 14:42 ]


Acties:
  • +1 Henk 'm!

  • Sandor_Clegane
  • Registratie: Januari 2012
  • Niet online

Sandor_Clegane

Fancy plans and pants to match

Ik vind leuk. :)

Zoals ik het zie:

je hebt een aantal webservers met een API, daarachter een of meerdere machines waar je aggregates ( voor het gemak ervan uitgaande dat je ze gebruikt ) op kunnen draaien.

Speler krijgt item, lokaal op de client is dit OK. Nu gaat er een commando naar de webserver: Speler met GUID heeft item met GUID gekregen. UpdateInventoryWithItem dit commando wordt gevalideerd op de server: Heb ik een speler met dit GUID? Zo ja stuur update commando naar Inventory en geef de speler een 200 OK. Inventory werkt zichzelf bij, en schrijft commando weg als event in de verleden tijd. UpdatedInventoryWithItem oid.

Nu gaat de speler dit item gebruiken: Actie met Item GUID op iets. Commando naar de server -> DoItemAction. Tijdens validatie van het commando vraag je inventory uit of de speler het item daadwerkelijk heeft. Zo ja, commando is goedgekeurd stuur naar andere aggregate voor de gamestate oid en geef de speler een 200 OK terug. De commando's worden sequentieel uitgevoerd dus het UpdateInventory commando is al uitgevoerd voordat de actie plaatsvindt.

De readmodels zijn nu niet van belang aangezien de wijzigingen hier niet van toepassing zijn. Als andere spelers direct op de hoogte moeten zijn van wijzigingen dan zou je de readmodels direct na het updaten van de aggregates kunnen wegschrijven/bijwerken.

Na ja, my 2 cents zeg maar.

Less alienation, more cooperation.


Acties:
  • 0 Henk 'm!

  • Cartman!
  • Registratie: April 2000
  • Niet online
Sandor_Clegane schreef op woensdag 3 januari 2018 @ 14:48:Zo ja stuur update commando naar Inventory en geef de speler een 200 OK. Inventory werkt zichzelf bij, en schrijft commando weg als event in de verleden tijd. UpdatedInventoryWithItem oid.
En precies het 2 generals problem dat ik beschrijf... ik doe writes naar 2 verschillende systemen en beide kunnen falen. Als ik de inventory update heb gedaan kan het schrijven van het event mislukken en is m'n inventory out of sync met m'n event store.

Ik waardeer het meedenken ontzettend overigens maar vrees dat de silver bullet er gewoon niet is, de opties zijn dus waarschijnlijk niet anders dan:

- je gebruikt een event store en accepteert eventual consistency en probeert de latency zo laag mogelijk te houden naar andere read models

- je update meerdere databases tegelijkertijd en accepteert dat er dingen out of sync kunnen lopen

Acties:
  • 0 Henk 'm!

  • Sandor_Clegane
  • Registratie: Januari 2012
  • Niet online

Sandor_Clegane

Fancy plans and pants to match

Cartman! schreef op woensdag 3 januari 2018 @ 14:53:
[...]

En precies het 2 generals problem dat ik beschrijf... ik doe writes naar 2 verschillende systemen en beide kunnen falen. Als ik de inventory update heb gedaan kan het schrijven van het event mislukken en is m'n inventory out of sync met m'n event store.
Het bijwerken van de eventstore en uitvoeren van het commando is een nagenoeg atomische transactie binnen de aggregate.

Less alienation, more cooperation.


Acties:
  • 0 Henk 'm!

  • Cartman!
  • Registratie: April 2000
  • Niet online
Sandor_Clegane schreef op woensdag 3 januari 2018 @ 15:01:
[...]


Het bijwerken van de eventstore en uitvoeren van het commando is een nagenoeg atomische transactie binnen de aggregate.
Je kunt niet atomic schrijven naar bijv Kafka en Redis tegelijk, 1 van de 2 kan falen.

Acties:
  • 0 Henk 'm!

  • Sandor_Clegane
  • Registratie: Januari 2012
  • Niet online

Sandor_Clegane

Fancy plans and pants to match

Cartman! schreef op woensdag 3 januari 2018 @ 15:02:
[...]

Je kunt niet atomic schrijven naar bijv Kafka en Redis tegelijk, 1 van de 2 kan falen.
Nope, dat snap ik, maar je kan de hele transactie wel atomair maken binnen je aggregate in code. Daarom zou ik het in memory houden als dat mogelijk is. En alleen het event wegschrijven in Redis oid in een stack.

Less alienation, more cooperation.


Acties:
  • 0 Henk 'm!

  • Cartman!
  • Registratie: April 2000
  • Niet online
Sandor_Clegane schreef op woensdag 3 januari 2018 @ 15:06:
[...]


Nope, dat snap ik, maar je kan de hele transactie wel atomair maken binnen je aggregate in code. Daarom zou ik het in memory houden als dat mogelijk is. En alleen het event wegschrijven in Redis oid in een stack.
Ja, dat doe ik in de app die draait ook. Ik hou een stack bij van alle changes in Redis en de events die ik opsla in Firehose. Ik beschouw Firehose als leading en als die events goed zijn opgeslagen schrijf ik de changes naar Redis. As dat mis gaat dan heb ik events staan in de event store die niet verwerkt zijn.

Acties:
  • 0 Henk 'm!

  • Xiphalon
  • Registratie: Juni 2001
  • Laatst online: 08-10 17:08
Cartman! schreef op woensdag 3 januari 2018 @ 11:58:
[...]
Interessant, [...] zitten we ook "vast"aan AWS en daarmee valt Azure een beetje af.
[...]
Service Fabric integreert mooi met Azure (half Azure draait er zelf op :+ ) maar het is niet verplicht om Service Fabric in Azure te draaien. Kan ook prima op eigen hardware en/of op een virtual machine ergens anders.

Zelfs onder Linux binnenkort/nu al

Acties:
  • +1 Henk 'm!

  • drm
  • Registratie: Februari 2001
  • Laatst online: 09-06 13:31

drm

f0pc0dert

Is de samenvatting van je probleem niet gewoon dat je wilt weten of de validatie die je doet op basis van een representatieve state is voor dat moment? Dat kun je toch op allerlei manieren garanderen, met de meest voor de hand liggende dat in Redis staat wat het laatst verwerkte event is? En is het dan niet gewoon zo dat als er validatie plaats moet vinden je altijd wilt wachten totdat Redis up to date is? Wat heb je immers aan hele snelle validatie die niet betrouwbaar is?

De vraag die dan overblijft is: in hoeverre is het wachten op validatie een probleem voor de user experience? Als dat inderdaad een issue is, zou je volgens mij je events dusdanig op moeten splitsen dat je in staat bent een "belofte" van een bepaalde state te doen, bijvoorbeeld door een deel van de state wel alvast te verwerken maar weer te geven als "nog niet gevalideerd", of te werken met een validatiemechanisme dat altijd in staat is bij een eventueel falende validatie een nieuw event te genereren dat de boel weer herstelt (of een speler als cheater markeert :P)

Maar dat laatste zou ik echt pas over na gaan denken op het moment dat je vaststelt dat dat ook echt een feitelijke bottleneck is die niet gewoon met schalen en optimalisatie opgelost kan worden.

Music is the pleasure the human mind experiences from counting without being aware that it is counting
~ Gottfried Leibniz


Acties:
  • 0 Henk 'm!

  • Cartman!
  • Registratie: April 2000
  • Niet online
drm schreef op woensdag 3 januari 2018 @ 22:49:
Is de samenvatting van je probleem niet gewoon dat je wilt weten of de validatie die je doet op basis van een representatieve state is voor dat moment?'
Dat is een mooie samenvatting denk ik ja :)
Dat kun je toch op allerlei manieren garanderen, met de meest voor de hand liggende dat in Redis staat wat het laatst verwerkte event is? En is het dan niet gewoon zo dat als er validatie plaats moet vinden je altijd wilt wachten totdat Redis up to date is? Wat heb je immers aan hele snelle validatie die niet betrouwbaar is?
Dat is precies het ding eigenlijk...als ik events wegschrijf naar bijv Kafka weet Redis nergens van totdat de consumer die heeft bijgewerkt.
De vraag die dan overblijft is: in hoeverre is het wachten op validatie een probleem voor de user experience? Als dat inderdaad een issue is, zou je volgens mij je events dusdanig op moeten splitsen dat je in staat bent een "belofte" van een bepaalde state te doen, bijvoorbeeld door een deel van de state wel alvast te verwerken maar weer te geven als "nog niet gevalideerd", of te werken met een validatiemechanisme dat altijd in staat is bij een eventueel falende validatie een nieuw event te genereren dat de boel weer herstelt (of een speler als cheater markeert :P)
In de app die al live staat krijg je bijvoorbeeld in-game credits die je in de shop kunt uitgeven. Als je visueel de credits al hebt gekregen, naar de shop gaat om iets te kopen en t blijkt dat dit nog niet verwerkt is kun je t item niet kopen.
Maar dat laatste zou ik echt pas over na gaan denken op het moment dat je vaststelt dat dat ook echt een feitelijke bottleneck is die niet gewoon met schalen en optimalisatie opgelost kan worden.
Als de basis al fout is dan gaat schalen wellicht niet helpen. Je hebt er wel een punt overigens; misschien gewoon maar eens een proof of concept maken om te kijken of dit onder normale omstandigheden tot problemen zal leiden. In een distributed omgeving zullen altijd dingen misgaan maar als t 99,9% van de tijd goed werkt zijn we er (en misschien is dat al wat hoog gegrepen).

Ik hoopte overigens vooral dat hier iemand praktijkervaring heeft met zulke schaal die wat tips had natuurlijk maar tot nu toe valt dat wat tegen.

[ Voor 3% gewijzigd door Cartman! op 04-01-2018 09:26 ]


Acties:
  • 0 Henk 'm!

  • D-Raven
  • Registratie: November 2001
  • Laatst online: 07-10 10:25
Kun je dit niet gewoon oplossen door een durable queue er tussen te zetten icm het accepteren van eventual consistency ?

Dus de events die uit je aggregate komen, publiseer je op een durable queue (kafka, rmq, whatever). Je behandeld het command als voltooid als de eventstream committed is en alle events gepublished zijn op je queue backend.
Vervolgens worden je events afgehandeld door je readmodel projectors. En het is aan de consumer zijde om te bepalen of er retry mechanismes oid toegepast moeten worden.
In het geval dat je op deze manier met een andere aggregate/bounded context moet communiceren, dan zet je er een processmanager tussen die dat specieke scenario behandeld, en compensating events publiseert indien er dingen terug gedraaid moeten worden (omdat er iets echt iets niet wil lukken oid).

Het 2 generals probleem bestaat hier niet in. Omdat je source of truth alleen je event-stream is. De rest zijn read-models.

Ik heb zelf al diverse ES + CQRS systemen gebouwd. Eventual consistency can be a bitch.

Er is niet echt een eenduidig antwoord te geven op dit soort vragen. Omdat er veel afhangt van je eigen probleem domein. Maar wat ik je in ieder geval mee wil geven is. Wees pragmatisch. Staar je niet te blind op om alles meteen perfect schaalbaar te willen maken. Naarmate de load op je architectuur toeneemt ga je een hoop leren over je eigen domein, en zul je tegen die tijd hele andere beslissingen nemen als wat je nu denkt.

De meeste grote schaalbare applicaties worden echt niet in een keer neergezet, dit zijn dingen waarvan de architectuur evolueert met de tijd.
Goeie kans dat je bv met 1 redis instantie meer dan genoeg performance hebt. Mijn tip: Probeer complexiteit in je architectuur alleen naar binnen te trekken als je zeker weet dat je het ook echt nodig hebt. Want vaak is YAGNI van toepassing. De simpelste oplossing is vaak beter.

[ Voor 16% gewijzigd door D-Raven op 04-01-2018 10:53 ]


Acties:
  • 0 Henk 'm!

  • Cartman!
  • Registratie: April 2000
  • Niet online
D-Raven schreef op donderdag 4 januari 2018 @ 10:52:
Kun je dit niet gewoon oplossen door een durable queue er tussen te zetten icm het accepteren van eventual consistency ?
Dat is wat ik in de TS ook voorstel ja.
Dus de events die uit je aggregate komen, publiseer je op een durable queue (kafka, rmq, whatever). Je behandeld het command als voltooid als de eventstream committed is en alle events gepublished zijn op je queue backend.
Vervolgens worden je events afgehandeld door je readmodel projectors. En het is aan de consumer zijde om te bepalen of er retry mechanismes oid toegepast moeten worden.
In het geval dat je op deze manier met een andere aggregate/bounded context moet communiceren, dan zet je er een processmanager tussen die dat specieke scenario behandeld, en compensating events publiseert indien er dingen terug gedraaid moeten worden (omdat er iets echt iets niet wil lukken oid).

Het 2 generals probleem bestaat hier niet in. Omdat je source of truth alleen je event-stream is. De rest zijn read-models.
Precies, dit topic heb ik ook gemaakt om erachter te komen wat hier de nadelen van kunnen zijn. Corrigeren dmv events ben ik tegengekomen en zal zeker wel eens voor gaan komen.
Ik heb zelf al diverse ES + CQRS systemen gebouwd. Eventual consistency can be a bitch.
Die ervaring zoek ik! Heb je specifieke voorbeelden?
Er is niet echt een eenduidig antwoord te geven op dit soort vragen. Omdat er veel afhangt van je eigen probleem domein. Maar wat ik je in ieder geval mee wil geven is. Wees pragmatisch. Staar je niet te blind op om alles meteen perfect schaalbaar te willen maken. Naarmate de load op je architectuur toeneemt ga je een hoop leren over je eigen domein, en zul je tegen die tijd hele andere beslissingen nemen als wat je nu denkt.
Ik ben verzekerd van hoge load direct na lancering, de vorige app had 100k registraties op de eerste dag en hierbij zal dat nog meer zijn. Simpelweg afwachten is geen optie.
De meeste grote schaalbare applicaties worden echt niet in een keer neergezet, dit zijn dingen waarvan de architectuur evolueert met de tijd.
Goeie kans dat je bv met 1 redis instantie meer dan genoeg performance hebt. Mijn tip: Probeer complexiteit in je architectuur alleen naar binnen te trekken als je zeker weet dat je het ook echt nodig hebt. Want vaak is YAGNI van toepassing. De simpelste oplossing is vaak beter.
YAGNI is n mooie maar zoals ik zei kijk ik naar de requirements en t verwachte aantal users en dan kan ik niet aankomen met "we kijken wel hoe t loopt", de klant betaalt ons er juist voor om dat voor te zijn. Dank voor je input :)

Acties:
  • 0 Henk 'm!

  • D-Raven
  • Registratie: November 2001
  • Laatst online: 07-10 10:25
Cartman! schreef op donderdag 4 januari 2018 @ 11:30:

YAGNI is n mooie maar zoals ik zei kijk ik naar de requirements en t verwachte aantal users en dan kan ik niet aankomen met "we kijken wel hoe t loopt", de klant betaalt ons er juist voor om dat voor te zijn. Dank voor je input :)
Overigens. Onderschat niet hoeveel werk je kan verzetten met 1 redis server, of 1 kafka/RMQ server, of 1 database server. Je hoeft lang niet altijd meteen naar een geclusterde opzet toe.
Clustering maakt alles meteen een stuk moeilijker, met name potentieel het recoveren van problemen.

Meten is weten. Doe load tests. Ga er vanuit dat je zelf load test tooltjes moet schrijven die load uit je functioneel domain simuleert.


edit: Ik had nog een andere reactie gepost. Maar die is weg ? Heb ik die toevallig rechtstreeks ge-DM'ed oid ?

[ Voor 6% gewijzigd door D-Raven op 04-01-2018 12:39 ]


Acties:
  • 0 Henk 'm!

  • Cartman!
  • Registratie: April 2000
  • Niet online
D-Raven schreef op donderdag 4 januari 2018 @ 12:35:
[...]
Overigens. Onderschat niet hoeveel werk je kan verzetten met 1 redis server, of 1 kafka/RMQ server, of 1 database server. Je hoeft lang niet altijd meteen naar een geclusterde opzet toe.
Clustering maakt alles meteen een stuk moeilijker, met name potentieel het recoveren van problemen.

Meten is weten. Doe load tests. Ga er vanuit dat je zelf load test tooltjes moet schrijven die load uit je functioneel domain simuleert.
Allemaal bekend ja, dergelijke projecten krijgen altijd een load test om bottlenecks te vinden voor livegang. 1 server is overigens nooit een optie, zorg altijd voor replicatie en failover maar ik neem aan dat je in t geval van Redis een enkele master bedoelt. We hebben best wat ervaring met sharding waarbij je naar 1 shard kunt schrijven voor meer throughput maar schrijven naar meerdere shards is nieuw en ook lastig, voorkomen door een enkele master te gebruiken wordt zeker meegenomen maar t risico is dat je uiteindelijk tegen de limiet oploopt van wat AWS kan bieden op een enkele instance.

Hoewel ik zie dat er nieuwe instances bij gekomen zijn die tot 407GB kunnen opslaan (was 100), dat biedt perspectief om voorlopig daar niet tegenaan te lopen.
edit: Ik had nog een andere reactie gepost. Maar die is weg ? Heb ik die toevallig rechtstreeks ge-DM'ed oid ?
Ik heb per DM niks ontvangen ieder geval...

Acties:
  • +1 Henk 'm!

  • drm
  • Registratie: Februari 2001
  • Laatst online: 09-06 13:31

drm

f0pc0dert

Cartman!:
Ik ben verzekerd van hoge load direct na lancering, de vorige app had 100k registraties op de eerste dag en hierbij zal dat nog meer zijn. Simpelweg afwachten is geen optie.
Mee eens, maar je kunt wel gewoon wat rekenwerk doen. Bijvoorbeeld, gessteld dat je 1 miljoen gelijktijdige users hebt, hoeveel events per seconde moet je dan kunnen verwerken? En als het aantal vertienvoudigt in een dag tijd, wat is dan het scenario?

Als je dat soort hoeveelheden kunt simuleren (zonder de applicatie al gebouwd te hebben) kom je al een heel eind met gevoel krijgen wat een bepaalde setup aan kan. Desnoods schrijf je een paar verschillende loadscenarios (veel verschillende events die steeds meer toenemen versus veel van hetzelfde type, enz) en pak je een dienst als loader.io en ga je het stuk proberen te krijgen. Want als je een systeem op zijn knieën krijgt weet je pas echt wat.

Meten is inderdaad weten. Als je namelijk een heel helder doel hebt van wat je precies wilt meten en waar je precies bottlenecks verwacht, dan is het helemaal niet zo veel werk om dat te simuleren en metrics uit te halen waar je op kan gaan tunen. Althans, dat hoeft het niet te zijn. Zeker niet als je ook kunt zorgen dat loadtests (of op zijn minst performancetests) een onderdeel zijn van je CI-straat, en je dergelijke requirements van begin af aan in de gaten houdt, dan kom je echt best wel goed beslagen ten ijs.

Het zou wel leuk zijn als je je ervaringen hier blijft delen trouwens :)

A propos: ik durf het bijna niet te vragen, maar heb je ook overwogen om (deels) van PHP af te zien? Of ten minste ook een kijkje bij de buren te nemen? De statelessness van PHP heeft namelijk best wel wat nadelen: alles wat je wilt kunnen cachen (of niet wilt recomputen) moet namelijk buiten PHP leven, en dat kan sommige scenarios best wel onnodig compliceren.

edit:
Ik kan overigens de combinatie van loader.io en newrelic van harte aanbevelen voor loadtests.

Music is the pleasure the human mind experiences from counting without being aware that it is counting
~ Gottfried Leibniz


Acties:
  • 0 Henk 'm!

  • Cartman!
  • Registratie: April 2000
  • Niet online
drm schreef op vrijdag 5 januari 2018 @ 00:11:
[...]
Mee eens, maar je kunt wel gewoon wat rekenwerk doen. Bijvoorbeeld, gessteld dat je 1 miljoen gelijktijdige users hebt, hoeveel events per seconde moet je dan kunnen verwerken? En als het aantal vertienvoudigt in een dag tijd, wat is dan het scenario?
Gezien t stadium van t project heb ik die informatie nog niet maar zeker een goed idee om op die manier te gaan berekenen :)
Als je dat soort hoeveelheden kunt simuleren (zonder de applicatie al gebouwd te hebben) kom je al een heel eind met gevoel krijgen wat een bepaalde setup aan kan. Desnoods schrijf je een paar verschillende loadscenarios (veel verschillende events die steeds meer toenemen versus veel van hetzelfde type, enz) en pak je een dienst als loader.io en ga je het stuk proberen te krijgen. Want als je een systeem op zijn knieën krijgt weet je pas echt wat.
Eens, wederom goed plan.
Meten is inderdaad weten. Als je namelijk een heel helder doel hebt van wat je precies wilt meten en waar je precies bottlenecks verwacht, dan is het helemaal niet zo veel werk om dat te simuleren en metrics uit te halen waar je op kan gaan tunen. Althans, dat hoeft het niet te zijn. Zeker niet als je ook kunt zorgen dat loadtests (of op zijn minst performancetests) een onderdeel zijn van je CI-straat, en je dergelijke requirements van begin af aan in de gaten houdt, dan kom je echt best wel goed beslagen ten ijs.
Grote load tests zijn wel heel kostbaar dus denk dat dit een eenmalig ding zal zijn (en wellicht bij major updates).
Het zou wel leuk zijn als je je ervaringen hier blijft delen trouwens :)
Dat zou zeker leuk zijn maar weet niet hoe haalbaar dat is gezien ik de naam van de klant/project niet mag noemen etc. Wellicht een keer een tweakblog over de bevindingen of de uiteindelijk setup, dat moet mogelijk zijn.
A propos: ik durf het bijna niet te vragen, maar heb je ook overwogen om (deels) van PHP af te zien? Of ten minste ook een kijkje bij de buren te nemen? De statelessness van PHP heeft namelijk best wel wat nadelen: alles wat je wilt kunnen cachen (of niet wilt recomputen) moet namelijk buiten PHP leven, en dat kan sommige scenarios best wel onnodig compliceren.
Dat wordt zeker overwogen ja. Ik denk dat er ook delen gedaan zullen worden in NodeJS en/of Python (bijv in AWS Lambda die geen PHP ondersteunt) en wellicht trekken we dat door. Qua developers hebben we simpelweg meer ervaring en capaciteit met PHP.
edit:
Ik kan overigens de combinatie van loader.io en newrelic van harte aanbevelen voor loadtests.
Neem ik mee, wij besteden load tests eigenlijk altijd uit want we willen geen slager zijn die hun eigen vlees keurt. Mochten we t zelf doen omdat klant er weinig budget voor heeft dan gebruiken we Tsung. Ga tzt zeker even loader.io en newrelic bekijken :)
Pagina: 1