Systeemarchitectuur/Redundantie

Uit Inf20
Naar navigatie springen Naar zoeken springen
De printervriendelijke versie wordt niet langer ondersteund en kan weergavefouten bevatten. Werk uw browserbladwijzers bij en gebruik de gewone afdrukfunctie van de browser.

Redundantie

Er is sprake van een redundante representatie van een betekenis als je meer bits gebruikt dan er nodig zijn om deze betekenis uit te drukken.

Redundantie in de fysieke laag

In de fysieke laag kun je redundantie soms goed gebruiken:

  • je kunt de betrouwbaarheid van een informatiesysteem vergroten;
  • je kunt de snelheid van een informatiesysteem (of proces) vergroten.

Vergroten van de betrouwbaarheid

Veranderingen in delen van een redundante representatie kunnen tot inconsistenties leiden. Door een slimme codering te kiezen kun je deze inconsistenties detecteren, en soms zelfs herstellen. Je spreekt dan over foutdetecterende en foutherstellende codes.

Een eenvoudig voorbeeld van een foutdetecterende code is een pariteitsbit. Voor een grotere waarde kun je gebruik maken van een checksum (Cyclic Redundancy Check), of van een hashcode, bijvoorbeeld MD5 of SHA. Er zijn ook slimme coderingen die foutcorrectie mogelijk maken.

  • De audio-CD maakt gebruik van foutherstellende codes om minder gevoelig te zijn voor krassen op het oppervlak.
  • Foutdetecterende en foutherstellende codes worden gebruikt bij datastransport (draadloos en bedraad), en bij het opslaan en terughalen van gegevens (RAM, harde schijf).
We kennen een vergelijkbaar gebruik in de wereld van materie en energie: je kunt een brug betrouwbaarder maken door het aantal balken te verdubbelen, of door dikkere balken te gebruiken.

Het herhalen (verdubbelen) van een representatie is in het algemeen een slecht idee: als je twee verschillende representaties hebt voor hetzelfde, dan weet je nog niet welke van de twee de juiste is. (Als je twee horloges hebt die niet gelijk lopen, hoe laat is het dan?)

Vergroten van de snelheid

Je kunt op verschillende manieren de snelheid van een informatieproces vergroten:

  • gebruik van parallellisme: verdelen van een berekening over
    • parallellisme op bitniveau: bijvoorbeeld 128-bits opteller;
    • pipelining: verschillende delen van een berekening door verschillende deel-processen; (vergelijk een lopende band)
    • parallellisme op server-niveau: verdelen van de rekenlast over identieke servers;
  • vergroten van lokaliteit
    • gebruik van caches (registers, caches op verschillende niveaus, werkgeheugen als cache van achtergrondgeheugen, caching in het web)
    • computers dichtbij de "bron" - bijvoorbeeld rekencentra zo dicht mogelijk bij Wall Street.

Het vergroten van lokaliteit is belangrijk voor het vergroten van de snelheid, omdat het fysieke transport van gegevens zoveel tijd kost. De lichtsnelheid is beperkt ten opzichte van de snelheid van computers: een moderne computer heeft een kloksnelheid van 1 GHz of meer; in 1 kloktik van 1ns legt het licht "slechts" een afstand van ca. 30 cm af ("1 foot per nanosecond", vaak gebruikt door Grace Hopper). Transport van elektrische signalen door koperdraden of op een chip verloopt nog langzamer.

Het verkleinen van de ontwerp-dimensies van een IC (chip), om het aantal transistoren op eenzelfde oppervlak te vergroten, heeft verschillende gevolgen voor de rekensnelheid (snelheid van transistoren), en voor de snelheid van het datatransport. De snelheid van een transistor neemt kwadratisch toe (het oppervlak, en daarmee de capaciteit, neemt kwadratisch af), de snelheid van datatransport neemt lineair af.

In de praktijk gebruik je vaak een combinatie van parallellisme en grotere lokaliteit.

Het gebruik van parallellisme en het gebruik van caches impliceert het maken van kopieën van programmacode en van de data. In het geval van veranderende data levert dit problemen, die je op de een of anderen manier moet oplossen.

Voorbeeld: als je met meerdere personen aan een document wilt werken (parallellisme), om dit eerder af te krijgen, dan kun je daarvan een kopie maken waarmee ieder voor zich aan de slag gaat. Maar dan heb je een probleem dat deze verschillende kopieën op de een of andere manier samengevoegd en consistent gemaakt moeten worden. Als je dit niet goed aanpakt, gaan delen van het document verloren (en ben je toch nog meer tijd kwijt).

Redundantie in de logische laag

Een redundante representatie "verzet zich tegen verandering": als je een verandering aanbrengt, moet je dat op meerdere plaatsen doen, anders krijg je een inconsistente representatie.

In de fysieke laag kun je dit gebruiken om ongewenste veranderingen, ten gevolge van onbetrouwbare hardware, te detecteren, en eventueel te corrigeren.

Dit ligt anders in de logische laag, als het gaat om veranderingen in de betekenis. Je moet dan de representatie (vorm) op meerdere plaatsen veranderen; als je dat niet zorgvuldig doet, krijg je een inconsistente representatie, die je niet op een consistente manier kunt interpreteren.

Immutable data: kopiëren staat vrij

Als de betekenis niet verandert, hoef je de vorm niet aan te passen ("immutable data"). Het gebruik van dergelijke immutable data heeft grote voordelen. Deze kun je zonder problemen kopiëren: er is geen risico dat het origineel en de kopieën op een of andere manier uit elkaar gaan lopen, en onderling inconsistent worden.

  • we gebruiken bijvoorbeeld liever een geboortedatum, dan een leeftijd: een geboortedatum is (in principe) immutable.

Programmacode is in principe ook immutable (afgezien van nieuwe versies). Dit betekent dat we programmacode vrij kunnen kopiëren in een computersysteem, bijvoorbeeld om de snelheid te vergroten (zie hierboven).

Veranderlijke data: zo mogelijk, delen (sharing) van een enkele bron

Als de betekenis verandert, dan moeten we de representatie aanpassen. Als deze representatie op meerdere plaatsen aanwezig is (caches e.d.) is het lastig om deze aan te passen. Bovendien moeten we ervoor zorgen dat we dit op een consistente manier doen. Deze situatie willen we zoveel mogelijk vermijden, door (waar mogelijk) te werken met een enkele representatie die vanuit meerdere processen gebruikt kan worden. We spreken dan over een gemeenschappelijke (shared) representatie (shared data, maar ook shared code).

Dit komen we op de volgende manieren tegen:

  • gebruik van een database door meerdere toepassingen;
  • gebruik van een (data) "model" met meerdere "views", in een Model-View-Controller (MVC) ontwerp van een toepassing;
  • gebruik van een enkel document met meerdere gebruikers: Google Docs
  • gebruik van een enkel document met meerdere gebruikers: wiki
    • WikiPedia
  • ontwikkelen van een programma, met versiebeheer (Git, GitHub, enz.)

Het web biedt de mogelijkheid om eenzelfde document vanuit de hele wereld te benaderen. Je identificeert zo'n document met een URL: dat levert een wereldwijd unieke identificatie op. En dankzij deze URL kun je overal dit document vinden.

P.M.:

  • publish-subscribe

Ontwikkelen van programma's met versiebeheer

Opmerking: voor het ontwikkelen van een programma met meerdere personen heb je te maken met een andere situatie dan een Google Docs document. Je moet je eigen wijzigingen op een manier kunnen aanbrengen zodat de anderen daarvan geen last hebben: je werkt bij voorkeur aan een lokale kopie. Deze moet intern consistent zijn: je wilt ook geen last hebben van veranderingen van anderen. Pas op het moment dat je eigen versie getest is, kun je deze samenvoegen met de gedeelde versie die ook door anderen gebruikt wordt (bijvoorbeeld de gemeenschappelijke versie op GitHub).

  • het is niet voor niets dat GitHub een centrale plek is voor veel ontwikkelaars: je wilt voorkomen dat er "per ongeluk" (door een onhandige manier van werken) allerlei verschillende versies ontstaan.

Redundantie in de toepassingen

Gemeenschappelijke data: gebruik van database

Een manier om de redundantie van veranderlijke data tussen toepassingen te verkleinen is om deze data in een gemeenschappelijke database onder te brengen.

In een dergelijke database moet je voorzieningen hebben die het gebruik door meerdere processen mogelijk maken: deze moeten elkaar niet in de weg kunnen zitten. Bovendien moet je ervoor zorgen dat veranderingen op een consistente manier uitgevoerd worden, in de vorm van transacties.

Binnen een programma: factoriseren van vergelijkbare functionaliteit

Hoewel een programma in principe onveranderlijk is, blijkt dit in de praktijk toch vaak anders te liggen. Dit betekent dat we ook in een programma een bepaald deelprobleem (functionaliteit; ontwerpbeslissing) maar op één plaats willen oplossen. Dit kunnen we bijvoorbeeld bereiken door dit in een gemeenschappelijke programmacomponent (functie, module) onder te brengen. Dit heeft de volgende voordelen:

  • de gemeenschappelijke component wordt op verschillende manieren gebruikt: eventuele fouten worden eerder ontdekt; en er is een grotere druk om deze op te lossen; als deze opgelost wordt, dan hebben alle programma's die hiervan gebruik maken, daar direct voordeel van;
  • als er een verandering nodig is in deze gemeenschappelijke component, omdat er in de omgeving iets verandert, dan hoeft deze maar op één plaats aangebracht te worden;
  • de programmatekst wordt meestal kleiner, en daardoor beter te beheersen.

Bij het werken aan een programma probeer je de vergelijkbare functionaliteit zo goed mogelijk in afzonderlijke delen van het programma onder te brengen, die gedeeld worden door de rest van het programma. Dit is één van de aspecten van DRY: Don't Repeat Yourself. Dit kun je bijvoorbeeld op de volgende manieren realiseren:

  • factoriseren ("buiten haakjes brengen") door het maken van abstracte componenten (functies, objecten);
  • generaliseren van programma-onderdelen - bijvoorbeeld door een constante door een variabele of een parameter te vervangen.

Voorbeelden:

  • je kunt een sorteerfunctie voor een array generaliseren door de ordening (vergelijking) als een functie mee te geven. Dit kun je eventueel ook doen voor de "swap" functie. Als je typering te sterk is, dan kun je in beide gevallen de index als parameter meegeven, in plaats van het element zelf.
  • in veel (moderne) talen zijn functies gedefinieerd voor veel voorkomende herhalingsstructuren op arrays: map, reduce, foreach, filter, search.

Gemeenschappelijke functionaliteit naar de logische laag

Er is een tendens om gemeenschappelijke functionaliteit onder te brengen in de gedeelde infrastructuur - bijvoorbeeld het Operating System, programmeertalen/libraries, database management systemen. Dit betekent dat de functionaliteit hiervan steeds toeneemt, wat het eenvoudiger maakt om nieuwe toepassingen te maken.