Hoogfrequente gebeurtenissen opslaan in een database met beperkte verbindingen voor verbindingen

We hebben een situatie waarin ik te maken heb met een enorme toevloed van gebeurtenissen die binnenkomen op onze server, met gemiddeld ongeveer 1000 gebeurtenissen per seconde (piek zou ~ 2000 kunnen zijn).

Het probleem

Ons systeem wordt gehost op Heroku en maakt gebruik van een relatief dure Heroku Postgres DB , die een maximum van 500 DB-verbindingen toestaat. We gebruiken verbindingspooling om verbinding te maken vanaf de server met de DB.

Gebeurtenissen komen sneller dan de DB-verbindingspool aankan

Het probleem we have is that events come faster than the connection pool can handle. By the time one connection has finished the network roundtrip from the server to the DB, so it can get released back to the pool, more than n additional events come in.

Uiteindelijk stapelen de gebeurtenissen op, wachtend om te worden opgeslagen en omdat er geen beschikbare verbindingen in de pool zijn, nemen ze een time-out en wordt het hele systeem niet-operationeel gemaakt.

We hebben de noodsituatie opgelost door de beledigende hoogfrequente gebeurtenissen in een langzamer tempo uit te zenden vanaf de clients, maar we willen nog steeds weten hoe we met deze scenario's moeten omgaan als we die hoogfrequente gebeurtenissen moeten afhandelen.

constraints

Andere klanten willen misschien tegelijkertijd evenementen lezen

Andere clients vragen continu om alle gebeurtenissen met een bepaalde sleutel te lezen, zelfs als ze nog niet in de database zijn opgeslagen.

Een client kan GET api/v1/events? ClientId = 1 opvragen en alle events ontvangen die door client 1 zijn verzonden, zelfs als die events nog niet zijn voltooid in de database.

Zijn er voorbeelden van 'klaslokalen' over hoe hiermee om te gaan?

Mogelijke oplossingen

Houd de gebeurtenissen op onze server in de wacht

We zouden de gebeurtenissen op de server in de wachtrij kunnen zetten (waarbij de wachtrij een maximale concurrency van 400 heeft, zodat de verbindingspool niet opraakt).

Dit is slecht idee omdat:

  • Het zal het beschikbare servergeheugen opeten. De opeengestapelde enquared events verbruiken enorme hoeveelheden RAM.
  • Onze servers worden elke 24 uur opnieuw gestart . Dit is een harde limiet opgelegd door Heroku. De server kan opnieuw worden gestart terwijl evenementen in de wachtrij worden geplaatst, waardoor we de wachtrijgebeurtenissen verliezen.
  • Het introduceert status op de server, waardoor de schaalbaarheid wordt geschaad. Als we een instelling voor meerdere servers hebben en een client alle opgeslagen + opgeslagen evenementen wil lezen, weten we niet op welke server de in het rij gestelde evenementen leven.

Gebruik een aparte berichtenwachtrij

Ik neem aan dat we een berichtenwachtrij kunnen gebruiken (zoals RabbitMQ ?), Waar we de berichten erin en op het andere uiteinde is er een andere server die alleen bezig is met het opslaan van de gebeurtenissen in de database.

Ik weet niet zeker of berichtenwachtrijen het in de wacht zetten van wachtrijgebeurtenissen toestaat (die nog niet zijn opgeslagen), dus als een andere client de berichten van een andere client wil lezen, kan ik de opgeslagen berichten van de DB en de in behandeling zijnde berichten gewoon uit de wachtrij halen en voeg ze samen toe zodat ik ze kan terugsturen naar de leesverzoek-client.

Gebruik meerdere databases, waarbij elk een deel van de berichten opslaat met een centrale DB-coördinator-server om deze te beheren

Een andere oplossing die we echter hebben is om meerdere databases te gebruiken, met een centrale "DB-coördinator/taakverdeler". Bij ontvangst van een evenement het deze coördinator zou een van de databases kiezen om het bericht naar te schrijven. Dit zou ons in staat moeten stellen om meerdere Heroku-databases te gebruiken, waardoor de verbindingslimiet wordt verhoogd tot 500 x aantal databases.

Bij een leesvraag kan deze coördinator query's voor SELECT aan elke database uitgeven, alle resultaten samenvoegen en deze terugsturen naar de client die om het lezen heeft gevraagd.

Dit is slecht idee omdat:

  • Dit idee klinkt als ... ahem .. over-engineering? Zou ook een nachtmerrie zijn om te beheren (back-ups enz.). Het is ingewikkeld om te bouwen en te onderhouden en als het absoluut noodzakelijk is, klinkt het als een schending van KISS .
  • Het offert Consistentie . Het doen van transacties over meerdere DB's is een 'no-go' als we dit idee volgen.
12
sommige antwoorden houden hier rekening mee, maar ik vraag liever: is het absoluut noodzakelijk dat 100% van uw evenement correct in de database wordt ingevoegd, zo ja, hoe gaat u momenteel om met het probleem wanneer uw server opnieuw wordt opgestart?
toegevoegd de auteur Walfrat, de bron
U wilt dus een 100% beschikbaarheid, maar niet synchroon. Dan zou mijn weddenschap zijn om eerst de gebeurtenissen lokaal (ex: bestanden) voort te zetten en de bestanden op regelmatige basis te exporteren (dit zou het rollen van tmp-bestanden kunnen zijn, om elke 30 seconden sloten te vermijden). De basis van zo'n systeem is dat je alles in dezelfde tijd kunt hebben (geen verlies, direct proces, prestaties houden). Je moet weten wat je kunt laten vallen (bijvoorbeeld synchroon of echt 0% verlies) om te krijgen wat je nodig hebt. Dit hangt echter af van de vereisten van uw systeem, waardoor u misschien niet degene bent die ze heeft gerepareerd.
toegevoegd de auteur Walfrat, de bron
Je moet echt duidelijk maken of dit percentage piek of gemiddeld is. Als het piek is, wat is het aantal afspraken per dag?
toegevoegd de auteur JimmyJames, de bron
"We hebben de noodsituatie opgelost door de aanstootgevende hoogfrequente gebeurtenissen in een langzamer tempo uit te zenden vanaf de clients, maar we willen nog steeds weten hoe we met deze scenario's moeten omgaan als we die hoogfrequente events moeten afhandelen." Ik weet niet zeker hoe dit het probleem oplost. Als u meer krijgt dan u gemiddeld aankan, zal een klant niet vertragen, met als gevolg dat zij voortdurend een diepere achterstand aan het opbouwen zijn van gebeurtenissen die moeten worden afgehandeld?
toegevoegd de auteur JimmyJames, de bron
Waar zit je knelpunt? U noemt uw verbindingspool, maar dat beïnvloedt alleen het parallellisme, niet de snelheid per insert. Als u 500 verbindingen hebt en bijvoorbeeld 2000QPS, zou dit goed moeten werken als elke vraag binnen 250ms voltooit die een looong tijd is. Waarom is dat meer dan 15ms? Merk ook op dat u met behulp van een PaaS aanzienlijke optimalisatiemogelijkheden opgeeft, zoals het schalen van de databasehardware of het gebruik van leesreplica's om de belasting van de primaire database te verminderen. Heroku is het niet waard, tenzij inzet je grootste probleem is.
toegevoegd de auteur amon, de bron
@NicholasKyriakides Juiste hardware is geen micro-optimalisatie. Het is de belangrijkste manier om databases te schalen. Netwerklatentie binnen één datacenter is hier te verwaarlozen, <1ms. Het schrijven naar een enterprise-grade SSD is ook <1ms. Voor 1000 transacties heeft u ten minste 1k IOPS nodig, die bijvoorbeeld harde schijven kunnen niet bieden, hoewel RAID-0 kan helpen. Een competent sysadmin moet dit allemaal goed kunnen configureren. Toch zie je problemen. Of je hebt een enorm prestatieprobleem in een softwarecomponent (je hebt dit uitgesloten voor de DB) of je PaaS is gewoon heel erg slecht. Cloud is slecht voor de prestaties.
toegevoegd de auteur amon, de bron
Is het inpakken van een paar gebeurtenissen op een enkel verzoek voordat ze over het netwerk worden verzonden geen optie? Ik heb een soortgelijk probleem opgelost door elke klant alle gebeurtenissen in een bepaald tijdsbestek op één verzoek te laten "inpakken" en ze elke 10-15s of zo te verzenden. Als dat een optie is, geef me een ping en ik zal het uitbreiden op een volledig antwoord.
toegevoegd de auteur T. Sar, de bron
Hoe heb je precies gecontroleerd of de verbindingspool het probleem is? @amon is correct in zijn berekeningen. Probeer de uitgifte van select null bij 500 verbindingen. Ik wed dat je zult merken dat de verbindingspool daar niet het probleem is.
toegevoegd de auteur user26009, de bron
Als select null problematisch is, hebt u waarschijnlijk gelijk. Hoewel het interessant zou zijn wanneer al die tijd wordt besteed. Geen enkel netwerk is zo traag.
toegevoegd de auteur user26009, de bron
@amon Het knelpunt is inderdaad de verbindingspool. Ik heb ANALYSE op de query's zelf uitgevoerd en dit is geen probleem. Ik heb ook een prototype gebouwd om de hypothese van de verbindingspool te testen en geverifieerd dat dit inderdaad het probleem is. De database en de server zelf leven op verschillende machines, vandaar de latentie. Ook willen we Heroku niet opgeven, tenzij het absoluut noodzakelijk is, en ons geen zorgen maken over de inzet is een groot pluspunt voor ons.
toegevoegd de auteur Nicholas Kyriakides, de bron
... Dit scenario heeft ons doen denken dat, hoewel we deze keer "weg via gasklep" onze weg naar buiten kunnen vinden, dat vrij snel niet zal gebeuren.
toegevoegd de auteur Nicholas Kyriakides, de bron
@JimmyJames zal een client niet vertragen, wat betekent dat ze voortdurend een diepere achterstand aan het opbouwen zijn van gebeurtenissen die moeten worden afgehandeld? . Niet in dit geval. We hebben de clients genas, zodat ze dat evenement in een lager tempo verzenden. Voor dat evenement hadden we geen gegevens nodig die in dat tempo werden verzonden, maar het zou leuk zijn om dat te hebben. Er zijn evenementen die we altijd moeten hebben. Op dit moment hebben we niet zoveel gebruikers, dus het vereiste evenement zal hetzelfde probleem veroorzaken, maar we zullen snel genoeg van hoe het eruit ziet. Ik ben niet precies aan het oplossen voor mijn huidige probleem ...
toegevoegd de auteur Nicholas Kyriakides, de bron
@Walfrat We hebben het niet aangepakt. We hebben alleen het tempo vertraagd dat gebeurtenissen worden uitgezonden als tijdelijke oplossing. Ook: is het absoluut noodzakelijk dat 100% van uw evenement correct in de database wordt ingevoegd . Ja en nee; Als een klant een evenement naar de server stuurt, wil ik garanderen dat het onmiddellijk na 2.3 jaar beschikbaar zal zijn voor het lezen door andere klanten. Het hoeft niet onmiddellijk in de database te worden ingevoegd, maar elke voorgestelde oplossing zou bij voorkeur fouttolerant zijn.
toegevoegd de auteur Nicholas Kyriakides, de bron
@JimmyJames Bewerkte de vraag, het is gemiddeld.
toegevoegd de auteur Nicholas Kyriakides, de bron
@usr Mijn testharnas is uitgevoerd op 50 verbindingen, niet op 500. Ik heb SELECT NULL uitgevoerd en het is nog steeds problematisch. Ook heb ik ANALYSE op de vragen uitgevoerd en hun tijden lijken goed. Hoewel het concept van mijn vraag nog steeds bestaat, zal ik het bijwerken met nauwkeurigere gegevens. Ik ben ook vergeten de grootte van de query toe te voegen die over de draad wordt verzonden, wat vrij groot is (~ 5KB gemiddeld)
toegevoegd de auteur Nicholas Kyriakides, de bron
Dat gezegd hebbende, begrijp ik dat er micro-optimalisaties zijn die ik zou kunnen doen om het huidige probleem op te lossen. Ik vraag me af of er een schaalbare architectuur -oplossing is voor mijn probleem.
toegevoegd de auteur Nicholas Kyriakides, de bron
Als algemene richtlijn zou ik zeggen: wanneer u de grenzen bereikt van de technologie die u gebruikt, moet u overschakelen naar andere technologie.
toegevoegd de auteur Dominique, de bron

6 antwoord

Mijn gok is dat je een aanpak die je hebt afgewezen zorgvuldiger moet onderzoeken

  • Geef de evenementen op onze server een wachtrij

Mijn suggestie zou zijn om te beginnen met het lezen van de verschillende artikelen die zijn gepubliceerd over de LMAX-architectuur . Het lukte hen om batches van hoog volume te maken voor hun gebruik, en het is mogelijk dat uw trade offs meer op die van hen lijken.

U wilt misschien ook kijken of u de uitlezingen kunt lezen - idealiter wilt u ze onafhankelijk van de schrijfopdrachten kunnen schalen. Dat kan betekenen: onderzoek naar CQRS (scheiding van opdrachtverantwoordelijkheid).

De server kan opnieuw worden gestart terwijl evenementen in de wachtrij worden geplaatst, waardoor we de wachtrijgebeurtenissen verliezen.

In een gedistribueerd systeem denk ik dat je er vrij zeker van kunt zijn dat berichten verloren gaan. Mogelijk kun je een deel van de impact ervan beperken door goed te oordelen over je volgordebarrières (bijvoorbeeld - ervoor zorgen dat het schrijven naar duurzame opslag plaatsvindt - voordat het evenement buiten het systeem wordt gedeeld).

  • Gebruik meerdere databases, waarbij elk een deel van de berichten opslaat met een centrale DB-coördinator-server om deze te beheren

Misschien - ik zou eerder naar uw bedrijfsgrenzen kijken om te zien of er natuurlijke plaatsen zijn om de gegevens te beschadigen.

Er zijn gevallen waarbij het verliezen van gegevens een acceptabele afweging is?

Welnu, ik veronderstel dat dat mogelijk was, maar dat is niet waar ik naartoe ging. Het punt is dat het ontwerp de robuustheid moet hebben ingebouwd die nodig is om vooruitgang te boeken in het geval van berichtverlies.

Hoe dit vaak eruitziet, is een pull-gebaseerd model met meldingen. Provider schrijft de berichten in een bestelde duurzame winkel. Consument haalt de berichten uit de winkel en volgt zijn eigen hoogwatermerkteken. Pushmeldingen worden gebruikt als een latency-reducerend apparaat - maar als de melding verloren gaat, wordt het bericht (uiteindelijk) nog steeds opgehaald omdat de consument een regelmatig schema aan het ophalen is (het verschil is dat als de melding wordt ontvangen, de aantrekkingskracht eerder optreedt ).

See Reliable Messaging Without Distributed Transactions, by Udi Dahan (already referenced by Andy) and Polyglot Data by Greg Young.

11
toegevoegd
Ik denk dat je in een gedistribueerd systeem er vrij zeker van bent dat berichten verloren gaan . Werkelijk? Er zijn gevallen waarbij het verliezen van gegevens een acceptabele afweging is? Ik had de indruk dat gegevens verliezen = mislukken.
toegevoegd de auteur Nicholas Kyriakides, de bron
@NicholasKyriakides, het is meestal niet acceptabel, daarom stelde OP de mogelijkheid voor om naar een duurzame winkel te schrijven voordat het evenement werd uitgezonden. Controleer dit artikel en deze video door Udi Dahan, waar hij het probleem in meer detail bespreekt.
toegevoegd de auteur Andy, de bron

Input stream

Het is niet duidelijk of uw 1000 gebeurtenissen/seconden pieken voorstellen of dat het een continue belasting is:

  • als het een piek is, kunt u een berichtenwachtrij als buffer gebruiken om de belasting op de DB-server over een langere tijd te spreiden;
  • als het constante belasting is, is de berichtenwachtrij alleen niet voldoende, omdat de DB-server het nooit kan inhalen. Dan moet je nadenken over een gedistribueerde database.

Voorgestelde oplossing

Intuïtief, in beide gevallen, zou ik gaan voor een Kafka gebaseerd evenement- stroom:

  • All events are systematically published on a kafka topic
  • A consumer would subscribe to the events and store them to the database.
  • A query processor will handle the requests from the clients and query the DB.

Dit is zeer schaalbaar op alle niveaus:

  • Als de DB-server het knelpunt is, voegt u gewoon meerdere consumenten toe. Iedereen kon zich abonneren op het onderwerp en naar een andere DB-server schrijven. Als de distributie echter willekeurig wordt verspreid over de DB-servers, kan de queryprocessor niet voorspellen welke DB-server moet worden gebruikt en moet hij meerdere DB-servers ondervragen. Dit zou kunnen leiden tot een nieuw knelpunt aan de vraagkant.
  • Het DB-distributieschema kan daarom worden geanticipeerd door de gebeurtenisstroom in verschillende onderwerpen te organiseren (bijvoorbeeld door gebruik te maken van sleutelgroepen of eigenschappen om de database volgens een voorspelbare logica te partitioneren).
  • Als een berichtenserver niet volstaat om een ​​groeiende stroom invoergebeurtenissen te verwerken, kunt u kafka-partities toevoegen om kafka-onderwerpen over meerdere fysieke servers te verspreiden.

Het aanbieden van evenementen die nog niet in de DB zijn geschreven aan klanten

U wilt dat uw klanten toegang kunnen krijgen tot informatie die nog in de pijplijn zit en nog niet is weggeschreven naar de database. Dit is een beetje delicater.

Optie 1: een cache gebruiken om db-query's aan te vullen

Ik heb niet diepgaand geanalyseerd, maar het eerste idee dat in mijn gedachten opkomt, zou zijn om van de queryverwerker (s) een consument (en) van de kafka-onderwerpen te maken, maar in een ander consumentenorganisatie kafka . De verzoekverwerker zou dan alle berichten ontvangen die de DB-schrijver zal ontvangen, maar onafhankelijk. Het kan ze dan in een lokale cache bewaren. De query's worden vervolgens uitgevoerd op DB + cache (+ eliminatie van duplicaten).

Het ontwerp zou er dan als volgt uitzien:

enter image description here

De schaalbaarheid van deze querylaag kan worden bereikt door meer query-processors toe te voegen (elk in zijn eigen consumentengroep).

Optie 2: ontwerp een dubbele API

Een betere benadering IMHO zou een dubbele API aanbieden (gebruik het mechanisme van de afzonderlijke consumentengroep):

  • een query-API voor toegang tot gebeurtenissen in de DB en/of het maken van analyses
  • een streaming-API waarmee berichten rechtstreeks vanuit het onderwerp worden doorgestuurd

Het voordeel is dat u de klant laat beslissen wat interessant is. Dit kan voorkomen dat u DB-gegevens systematisch samenvoegt met vers geïncasseerde gegevens, wanneer de client alleen geïnteresseerd is in nieuwe binnenkomende gebeurtenissen. Als de delicate fusie tussen verse en gearchiveerde evenementen echt nodig is, dan zou de klant het moeten organiseren.

varianten

Ik stelde kafka voor omdat het is ontworpen voor zeer hoge volumes met blijvende berichten, zodat u de servers indien nodig opnieuw kunt opstarten.

Je zou een vergelijkbare architectuur met RabbitMQ kunnen bouwen. Als u echter persistente wachtrijen nodig heeft, kan dit de prestaties verminderen . Verder is, voor zover ik weet, de enige manier om het parallel gebruik van dezelfde berichten door verschillende lezers (bijvoorbeeld schrijver + cache) met RabbitMQ te bereiken, kloon de wachtrijen . Dus een hogere schaalbaarheid zou tegen een hogere prijs kunnen komen.

8
toegevoegd
@NicholasKyriakides interpreteerde ik " Andere clients vragen continu om alle gebeurtenissen met een bepaalde sleutel te lezen, zelfs als ze nog niet in de database zijn opgeslagen . "als een behoefte om DB-query (" all ") te maken en deze samen te voegen met inkomende gebeurtenissen (hier behandeld met een" cache "direct gevoed vanuit de invoer), waardoor dubbels geëlimineerd worden. Als u met 'alles' alleen 'helemaal nieuw' bedoelt, kunnen we het vereenvoudigen: geen cache, geen samenvoeging en gelezen vanuit DB of nieuwe gebeurtenissen doorsturen
toegevoegd de auteur Christophe, de bron
Ja. Mijn eerste gedachte zou zijn om niet voor willekeurige distributie te gaan, omdat dit de verwerkingsbelasting voor de query's zou kunnen vergroten (dit wil zeggen dat er meestal een query is van beide meerdere DB's). U kunt ook denken aan gedistribueerde DB-engines (bijvoorbeeld Ignite?). Maar om een ​​weloverwogen keuze te maken, is een goed begrip van de DB-gebruikspatronen vereist (wat staat er nog meer in de db, hoe vaak wordt het opgevraagd, wat voor soort zoekopdrachten, zijn er transactionele beperkingen buiten individuele evenementen, enz ...).
toegevoegd de auteur Christophe, de bron
@ NicholasKyriakides Bedankt! 1) Ik dacht simpelweg aan verschillende onafhankelijke databaseservers, maar met een duidelijk partitioneringsschema (sleutel, geografie, enz.) Dat kan worden gebruikt om de commando's effectief te verzenden. 2) Intuïtief , misschien omdat Kafka is ontworpen voor zeer high throughput met aanhoudende berichten moet u uw servers opnieuw opstarten?). Ik weet niet zeker of RabbitMQ even flexibel is voor de gedistribueerde scenario's en persistente wachtrijen prestaties verminderen
toegevoegd de auteur Christophe, de bron
Stellar; Wat bedoelt u met een gedistribueerde database (bijvoorbeeld met behulp van een specialisatie van de server per groep toetsen) ? Waarom Kafka in plaats van RabbitMQ? Is er een specifieke reden om de ene boven de andere te kiezen?
toegevoegd de auteur Nicholas Kyriakides, de bron
Voor 1) Dus dit is vrij gelijkaardig aan mijn Gebruik meerdere databases -idee, maar je zegt dat ik niet zomaar willekeurig (of rond-robin) de berichten naar elk van de databases moet verdelen. Rechts?
toegevoegd de auteur Nicholas Kyriakides, de bron
Ik vraag me af waarom de lokale cache überhaupt nodig is? Het hele idee van het gebruik van meerdere database/writers is dat de gebeurtenissen onmiddellijk worden opgeslagen en er bijna nooit een backlog is. Waarom niet gewoon rechtstreeks uit de database lezen?
toegevoegd de auteur Nicholas Kyriakides, de bron
zelfs als ze nog niet in de database zijn opgeslagen. . Wat ik hier bedoelde, is dat als er een oplossing wordt gekozen die accepteert dat er altijd een backlog zal zijn van gebeurtenissen die nog niet zijn geschreven, dan zouden de lees-clients ook de achterstandsevenementen willen ontvangen. Het multi-DB-idee betekent vrijwel geen achterstand (in theorie) = nooit niet opgeslagen DB-events = geen cache nodig.
toegevoegd de auteur Nicholas Kyriakides, de bron
Ik wil alleen maar zeggen dat hoewel Kafka een zeer hoge doorvoer kan geven, dit waarschijnlijk boven de meeste mensenbehoeften ligt. Ik ontdekte dat het omgaan met kafka en zijn API voor ons een grote fout was. RabbitMQ is geen flauwekul en heeft een interface die je van een MQ zou verwachten
toegevoegd de auteur Ankit, de bron

Als ik het goed begrijp, is de huidige stroom:

  1. Ontvangen en evenement (ik neem aan via HTTP?)
  2. Vraag een verbinding vanuit de pool aan.
  3. Voeg de gebeurtenis toe aan de DB
  4. Maak de verbinding met de pool vrij.

Als dat zo is, denk ik dat de eerste wijziging in het ontwerp zou zijn om te voorkomen dat uw even-afhandelingscode bij elk evenement verbindingen met de pool retourneert. Maak in plaats daarvan een pool met invoegthreads/-processen die 1-op-1 is met het aantal DB-verbindingen. Deze bevatten elk een speciale DB-verbinding.

Door een soort van gelijktijdige wachtrij te gebruiken, kunt u deze threads berichten uit de gelijktijdige wachtrij laten ophalen en invoegen. In theorie hoeven ze nooit de verbinding naar de pool te retourneren of een nieuwe aan te vragen, maar het kan zijn dat je de behandeling moet inbouwen voor het geval de verbinding slecht gaat. Het is misschien het gemakkelijkst om de thread/het proces te doden en een nieuwe te starten.

Dit zou de overhead van de verbindingspool effectief moeten elimineren. Uiteraard moet u per verbinding minimaal 1000/verbindingen per seconde kunnen pushen. U kunt verschillende aantallen verbindingen proberen proberen, omdat 500 verbindingen die aan dezelfde tabellen werken, conflicten op de DB kunnen creëren, maar dat is een hele andere vraag. Een ander ding om te overwegen is het gebruik van batchinserties, d.w.z. elke thread trekt een aantal berichten en drukt ze allemaal tegelijk in. Vermijd ook dat meerdere verbindingen proberen dezelfde rijen bij te werken.

6
toegevoegd

Veronderstellingen

Ik ga ervan uit dat de lading die je beschrijft constant is, omdat dit het moeilijkere scenario is om op te lossen.

Ik ga er ook van uit dat je een manier hebt om getriggerde, langlopende workloads uit te voeren buiten je webapplicatieproces.

Oplossing

Assuming that you have correctly identified your bottleneck - latency between your process and the Postgres database - that is the primary problem to solve for. The Oplossing needs to account for your consistency Beperking with other clients wanting to read the events as soon as practicable after they are received.

Om het latentieprobleem op te lossen, moet u op een manier werken die de hoeveelheid latentie die per gebeurtenis wordt opgeslagen, minimaliseert. Dit is het belangrijkste dat u moet bereiken als u niet bereid bent of in staat bent om hardware te veranderen . Omdat je op PaaS-services bent en geen controle hebt over de hardware of het netwerk, is de enige manier om de latentie per gebeurtenis te verminderen een soort van gebundelde schrijf van gebeurtenissen.

U moet een reeks gebeurtenissen lokaal opslaan die periodiek worden doorgespoeld en geschreven naar uw db, hetzij wanneer deze een bepaalde grootte heeft bereikt, of na een verstreken hoeveelheid tijd. Een proces zal deze wachtrij moeten volgen om de flush naar de winkel te activeren. Er moeten voldoende voorbeelden zijn over hoe u een gelijktijdige wachtrij kunt beheren die periodiek wordt gespoeld in de taal van uw keuze - Hier is een voorbeeld in C# uit de periodieke batching sink van de logboekregistratie van Serilog.

This SO answer describes the fastest way to flush data in Postgres - although it would require your batching store the queue on disk, and there is likely a problem to be solved there when your disk disappears upon reboot in Heroku.

Beperking

Another answer has already mentioned CQRS, and that is the correct approach to solve for the Beperking. You want to hydrate read models as each event is processed - a Mediator pattern can help encapsulate an event and distribute it to multiple handlers in-process. So one handler may add the event to your read model that is in-memory that clients can query, and another handler can be responsible for queuing the event for its eventual batched write.

Het belangrijkste voordeel van CQRS is dat je je conceptuele lees- en schrijfmodellen loskoppelt - wat een mooie manier is om te zeggen dat je in één model schrijft, en je leest van een ander totaal ander model. Om de schaalvoordelen van CQRS te benutten, wilt u er in het algemeen voor zorgen dat elk model afzonderlijk wordt opgeslagen op een manier die optimaal is voor zijn gebruikspatronen. In dit geval kunnen we een samengesteld leesmodel gebruiken - bijvoorbeeld een Redis-cache of gewoon in het geheugen - om ervoor te zorgen dat onze waarden snel en consistent zijn, terwijl we onze transactiedatabase nog steeds gebruiken om onze gegevens naar te schrijven.

5
toegevoegd

Gebeurtenissen komen sneller dan de DB-verbindingspool aankan

Dit is een probleem als voor elk proces één databaseverbinding nodig is. Het systeem moet zo zijn ontworpen dat u een pool van werknemers heeft waarbij elke werknemer slechts één databaseverbinding nodig heeft en elke werknemer meerdere gebeurtenissen kan verwerken.

De berichtenwachtrij kan met dat ontwerp worden gebruikt, u hebt berichtproducent (en) nodig die gebeurtenissen naar de berichtenwachtrij pushen en de werknemers (consumenten) verwerken de berichten uit de wachtrij.

Andere clients willen misschien gelijktijdige gebeurtenissen lezen

Deze beperking is alleen mogelijk als de gebeurtenissen in de database worden opgeslagen zonder enige verwerking (onbewerkte gebeurtenissen). Als gebeurtenissen worden verwerkt voordat ze in de database worden opgeslagen, zijn de enige manier om de gebeurtenissen te krijgen afkomstig uit de database.

Als de klanten alleen onbewerkte gebeurtenissen willen bevragen, raad ik aan om een ​​zoekmachine zoals Elastic Search te gebruiken. U krijgt zelfs de query/zoek-API gratis.

Gezien het feit dat het zoeken naar gebeurtenissen voordat ze in de database worden opgeslagen belangrijk voor je is, zou een eenvoudige oplossing zoals Elastic Search moeten werken. Je bewaart eigenlijk alle gebeurtenissen erin en dupliceert niet dezelfde gegevens door ze naar de database te kopiëren.

Het schalen van elastisch zoeken is eenvoudig, maar zelfs met de basisconfiguratie is het behoorlijk krachtig.

Wanneer u de verwerking nodig heeft, kan uw proces de gebeurtenissen ophalen bij ES, deze verwerken en opslaan in de database. Ik weet niet wat het prestatieniveau is dat u van deze verwerking nodig hebt, maar het zou volledig los staan ​​van het bevragen van de gebeurtenissen van ES. U zou hoe dan ook geen verbindingsprobleem moeten hebben, aangezien u een vast aantal werknemers en elk met één databaseverbinding kunt hebben.

3
toegevoegd

Ik zou heroku allemaal samen laten vallen, dat wil zeggen dat ik een gecentraliseerde benadering zou laten vallen: meerdere schrijfopdrachten die de maximale poolverbinding overschrijden is een van de belangrijkste redenen waarom db-clusters zijn uitgevonden, voornamelijk omdat je het schrijven niet laadt db (s) met leesverzoeken die kunnen worden uitgevoerd door andere db's in het cluster, ik zou het proberen met een master-slave-topologie, bovendien - zoals iemand al zei, het hebben van uw eigen db-installaties zou het mogelijk maken om het hele systeem om ervoor te zorgen dat de doorlooptijd van de query correct wordt afgehandeld.

Succes

1
toegevoegd