Jag upptäckte ju tidigare att Berkeley DB inte klarar av att det är mer än kanske max 100 trådar som samtidigt anropar funktioner i det, utan att prestanda rasar ner till löjliga nivåer. I mitt fall löste jag det genom att samla ihop anropen till 10 trådar med en connection pool, men det kändes mer som en pinsam workaround än en korrekt lösning. Oracle fick därför en fråga om varför det var på det här sättet.
Efter att ha skickat mina testprogram och skruvat på massor av parametrar för att förbättra prestandan, fick jag till slut ett tydligt svar. De höll med om att min pool var ett bra sätt att lösa det hela på.
The slowdown is not caused by a linear search, but by the low-level latching primitives provided by nearly all existing instruction sets. We use these primitives (e.g., test-and-set or locked compare and exchange) to implement mutexes and shared latches. As the number of threads “fighting” for a mutex increases, there is more overhead, both down in the CPU chip’s cache coherency system as well as in the operating system and BDB software above it.
De använder tydligen posix mutexar för den här synkroniseringen. Min pool är gjord med pthread, som uppenbarligen skalar bättre. Det är lite konstigt att de inte redan stödjer pthread i Berkeley DB, men det kanske kommer i en senare version.
Allt det här gäller i Linux, som jag har för mig har just en ovanligt bra pthread-implementation. Saker kan vara annorlunda i andra unixar.
Läs även andra bloggares åsikter om datorer, programmering, teknik, berkeley, oracle, connection pool, linux, pthread.
February 24th, 2010
Posted by
Daniel Brahneborg |
blogg |
3 comments
För några veckor sedan lyckades jag ju isolera ett problem med Berkeley-databasen. Den blev helt enkelt lite missnöjd med livet när det var för många trådar som accessade den samtidigt. I början så lyckades ju Oracle återskapa problemet, så det såg ljust ut. Tyvärr visade det sig vara en bugg i mitt testprogram, och när det var rättat så försvann krascherna. Attans.
Tyvärr fungerade det fortfarande inte helt bra, men det var inte helt tydligt var felet låg. I testsetupen till den riktiga applikationen så är det två andra program inblandade, och det var inte helt säkert att det inte fanns problem där också. Vissa buggar hittade jag faktiskt, men det var länge väldigt osäkert vart tiden egentligen tog vägen. Det gick bara ofantligt långsamt efter ett tag.
Till slut hade jag i alla fall lyckats mutera det lilla testprogrammet så pass mycket att det började visa upp samma symptom som det riktiga. Efter en massa testande och skruvande på parametrar stod det då klart att Berkeley DB börjar få problem när det är mer än något dussin trådar som samtidigt försöker skriva i databasen. Prestanda rasar, det blir deadlocks som gör att massa operationer måste göras om från början, och emellanåt så kraschar den helt och hållet.
Jag testade att sätta ett stort lås runt alla Berkeley-operationer, så att det alltid bara skedde en operation i taget. Först när allting var klart så fick nästa tråd göra någonting. Prestandan blev ju inte så jättebra eftersom det var så många trådar som inte gjorde något annat än att vänta, men det var i alla fall stabilt. Jag drog slutsatsen att det var antalet aktiva trådar som var det besvärliga. Ändå ville jag inte dra ner det till en enstaka tråd. Berkeley ska trots allt vara trådsäkert, och om trådarna jobbar på olika filer eller i olika delar av en fil, så borde det ju inte vara något problem.
De hundratals trådarna, i vissa fall uppåt två tusen stycken, behövde därför kokas ner till kanske 10 stycken. Alltså behövdes en datastruktur och algoritmer för att låta 10 trådar få passera samtidigt, men den 11:e skulle få vänta till någon av de 10 hade blivit klar. Jag behövde inte leta så länge innan jag hittade den perfekta lösningen: Min connection pool som jag skrev till MySQL-drivern! Den var skriven för att vara helt oberoende av vilken resurs som den hanterade, så det krävdes inte en enda ändring.
Några testkörningar senare så var det tydligt att testprogrammet inte brydde sig det minsta om antalet trådar som använder poolen, det gick lika fort ändå. Antalet deadlocks var inte noll, men ytterst få. I början fick jag deadlocks på varannan operation, och nu var jag nere på ensiffrigt av 100.000 poster. Det såg ljust ut.
På grund av hård refaktorering behövde jag bara ändra i tre små funktioner för att lägga till poolen i riktiga applikationen. Även där var prestandan nu helt stabil, oavsett hur många tusen poster den hade hanterat. Den ska få gå i mer full fart och lite längre tid i morgon, men det verkar som att problemet är löst.
För att använda Berkeley DB i en intensivt multitrådad applikation, behöver man alltså använda någon form av filter så att antalet samtidiga operationer maximeras till runt 10. Har man färre trådar än så behöver man självklart inte göra någonting, men det är vi högre upp på skalan som får problem. Oracle har fått en ny buggrapport.
Läs även andra bloggares åsikter om datorer, programmering, teknik, berkeley, oracle, race condition, connection pool.
February 9th, 2010
Posted by
Daniel Brahneborg |
blogg |
8 comments
Richard Gatarski skrev nyligen ett blogginlägg där han argumenterar för att man ska ta bort “Matematik” som kärnämne, minska antalet lektionstimmar, och ersätta det med något mer “användbart”. I en intervju med Skolvärlden sa han:
… dom flesta behöver bara 3 % av den matte dom fått lära sig (iofs en siffra huggen ur luften). Alltså, om man typ tvingats lära sig 100 mattesaker i skolan, så är det bara tre man har nytta av efteråt. Visst slöseri liksom. Då är det nog bättre att ta bort 96 saker och få tid att kunskapa om något vettigare i stället.
Att 96 + 3 inte blir 100 blir lite extra humor i sammanhanget, men ändå.
Jag blir bara ledsen när jag läser sådant här, för jag tycker det är direkt tragiskt med personer som tror att allting man lär sig i skolan är fakta som ska vara direkt användbara, helst i kassan på Ica. Egentligen behöver man ju då inte lära sig något annat än alfabetet, så man kan söka på wikipedia. Så är det självklart inte. Man går inte i skolan för att lära sig “kunskaper”.
Till att börja med så finns det ingen onödig kunskap. Alla fakta vi lär oss bidrar till att bygga upp en större och mer stabil mental bild av verkligheten. På det sättet blir det dels lättare att lära sig nya fakta, men det blir också lättare att förstå nya saker. Anledningen är att mer av det nya redan finns i skallen. Ta till exempel “koka vatten”. Att förstå vad som händer när vatten kokar är en ganska avancerad historia, som inkluderar både fysik och kemi (och lite annat, beroende på hur noga man vill vara). Om man sedan ska lära sig vad som händer när någon annan vätska kokar, är 99% av kunskapen redan där. Ju mer man kan, desto enklare blir det att lära sig mer.
Har man lärt sig tio huvudstäder så hjälper inte det för att lära sig namnet på den elfte, men man har förmodligen en lite större förståelse av konceptet “huvudstad” än innan.
När jag gick på universitetet i Umeå så var det någon som undrade varför Datavetenskapliga linjen började med så mycket matte (50%). Svaret från en av lärarna var ungefär “för att ni ska lära er att tänka”. Det låter knäppt, men är i grunden ganska enkelt. Matematik rör sig i en abstrakt värld, liksom programmering. För att bli en bra programmerare är det därför nödvändigt att utan problem kunna röra sig i den här abstrakta världen, och ett av de enklaste sätten att få folk att göra det mentala hoppet är att lära dem massor med matte. Inte för att man har användning av matten i sig, utan för att lära sig byta ut allt man vet om den fysiska världen (gravitation, massors tröghet osv), och föra resonemang och röra sig runt i en abstrakt värld, definierad av helt egna regler. Om man lär sig abstrakt tänkande enbart med hjälp av programmering, är risken stor att man fastnar i ett visst programmeringsspråks syntax. Jag har flera vänner som hamnade i den fällan.
Att kunna tänka abstrakt är naturligtvis ingenting som bara programmerare behöver, det har alla nytta av. De som tror att de bara “behöver” 3% (dvs 3 saker av 100, för er som skolkade på mattelektionerna) av den matte de lärde sig i grundskolan, har bara inte förstått hur den ska användas.
Det är inte konstigare än att det är bra för barn att lära sig krypa när de är små, något som bara vissa typer av soldater har någon faktisk nytta av. Det finns inga normala vuxna som har “nytta” av att kunna krypa, men det betyder inte att det är ett slöseri med tid att göra det som liten.
Med tanke på hur konstigt folk hanterar sin ekonomi så skulle å andra sidan många både behövt ha lärt sig mer matte, och dessutom behövt få lära sig hur den ska appliceras i det dagliga livet. Ett av mina favoritexempel är en sak jag har fått från Cornucopia, nämligen folk som skaffar amorteringsfria bostadslån. Visserligen kan det vara lite klurigt att räkna ut exakt hur mycket högre totalkostnaden blir, men det borde vara uppenbart för alla att den blir det.
Så, dels har man nytta av bra mycket mer matematik i vardagen än vad många tror, och dels så gör resten att vår mentala mappning av världen blir bättre.
Däremot behöver matematikundervisningen i Sverige bli mycket bättre, där har Richard helt rätt. Man behöver i början få se en tydligare koppling till verkligheten, så att det blir lättare att göra det här hoppet till den abstrakta världen. Studenter kommer ju till KTH utan att kunna mer än de fyra räknesätten, enligt flera artiklar i Ny Teknik förra året. Personligen tror jag att det beror till stor del just på det här “vad ska jag med just det HÄR till”-tänket. Antingen så visar det sig senare, eller så har du blivit bättre på att tänka abstrakt. Det är en win-win-situation, så länge som man får tillräckligt många exempel på det förra, för att sluta oroa sig.
Att “matte” inte anses coolt och därför ska tas bort, är helt fel lösning på problemet. Matte behövs, och behöver därför få en bättre image. Som den marknadsförare Richard är borde det vara en trevlig utmaning. När Festis ansågs o-coolt, paketerades drycken om och fick lite ball marknadsföring. Drycken i sig är densamma. Det är snarare något sådant som behövs. Matten behövs, men behöver lite ny paketering och reklam. Innehållet behöver inte förändras för det.
Läs även andra bloggares åsikter om datorer, programmering, ekonomi, vetenskap, pedagogik, richard gatarski, festis, reklam, matematik.
January 24th, 2010
Posted by
Daniel Brahneborg |
blogg |
16 comments
På jobbet började vi för ett tag sedan använda den gamla hederliga Berkeley-databasen i en av våra produkter. Den har ju många år på nacken och används i alla möjliga Unixverktyg, så det kändes tryggt.
Den första strategin gick ut på att använda flera olika index för att kunna traversera posterna på olika sätt. Framför allt skulle det göra att ingenting behövde läsas in vid uppstart, och i praktiken göra oss helt oberoende av köstorlekar. Sådant är bra.
Att använda olika index från olika trådar samtidigt var dock inte så populärt. Med intensiv trafik så kraschade nämligen alltid programmet ganska snart någonstans inne i Berkeley-koden. Trots support från flera av Berkeleyutvecklarna lyckades vi aldrig hitta anledningen.
Istället bytte vi strategi, och gick tillbaka till minnesbaserade köer istället. Databasen blev därmed reducerad till operationerna “lagra post X”, “läs in alla poster”, och “ta bort post X”. Det blev lite enklare kod och lite bättre prestanda, så det var ok.
När jag sedan började göra en del tyngre stresstester med massor med trådar, började databasen krascha igen. Det verkar inte som att jag har glömt något fundamentalt, som att skicka med DB_THREAD till db->open, vilket innebär att Berkeley DB i den aktuella versionen 4.8 än så länge inte är helt trådsäker. Den är ganska trådsäker, men med några dussin trådar som inte gör något annat än att skapa och ta bort poster, hittar man uppenbarligen något som är trasigt. Förmodligen är det vad som brukar kallas för ett “race condition”, dvs programmet förutsätter att två operationer sker i en viss ordning, men har glömt att säkerställa det.
Det kan som exempel se ut så här. Först finns tråd 1:
- Gör operation A.
- Vänta på att tråd 2 släpper lås Z.
- Gör någonting som tar lång tid.
- Gör operation B.
Sedan tråd 2:
- Gör operation C.
- Släpp lås Z.
- Gör någonting som tar kort tid.
- Gör operation D.
Av det här så vet vi att A sker före B, C sker före D, och att A sker före D.
Däremot så vet vi inte vad som händer först av A och C.
Rimligtvis borde D ske före B, eftersom båda trådarna passerar punkt 2 samtidigt. Här finns då ett race condition, för plötsligt kanske tråd 2 inte får köra på länge, vilket gör att tråd 1 hinner göra operation B innan tråd 2 hinner fram till operation D.
I verkligheten har man sedan kanske något hundratal trådar, en handfull lås, och några hundra operationer som parvis lite slumpmässigt måste göras i rätt ordning. Om det nu var någon som tyckte att det där exemplet lät alldeles för trivialt.
Nu återstår att se vad Oracle säger om det hela. Kanske har jag trots allt glömt någonting, kanske finns det en bugg.
Uppdatering: Oracle har nu bekräftat att de kan reproducera problemet, och har skickat det vidare till utvecklarna. Kul!
Läs även andra bloggares åsikter om datorer, programmering, teknik, berkeley, oracle, race condition.
January 20th, 2010
Posted by
Daniel Brahneborg |
blogg |
no comments
I ett av mina små Rails-projekt vill jag ha taggar, och hittade efter lite letande acts_as_taggable_redux. Den var ganska cool, och gjorde att flera personer kunde tagga samma objekt, men bara ändra de taggar de hade satt själva. Däremot kunde alla se alla andras taggar. Perfect för community-taggning.
Sedan hittade jag Haml, ett markupspråk för Rails. Extremt kompakt, och mycket vackert.
Jag installerade det, och började på en tom vy. Nähä, “edit.erb not found”. Hallå nu, filen heter edit.html.haml, och eftersom Haml är installerat ska den automatiskt hitta den. Jag googlade som en tok i går kväll, men hittade inga ledtrådar. Att byta railsversion mellan 2.3.2 och 2.3.4 gjorde ingen skillnad.
Till slut skapade jag ett helt nytt railsprojekt, skapade en haml-vy, och självklart fungerade det perfekt. Genom att jämföra filerna med de jag hade i mitt “trasiga” projekt och sedan stegvis kopiera över en fil i taget tills vyn gick sönder, hittade jag till slut den skyldige. Min tagging-plugin.
Visserligen skulle jag behöva slänga ut den pluginen och skriva en egen som använder named_scope istället för en massa komplex SQL, men det är andra saker som är viktigare. Idag dök dessutom en konkurrentsajt upp på nätet, så nu börjar det bli dags att få det hela releasebart. Lite irriterande, för det hade varit trevligt att kunna använda Haml.
Läs även andra bloggares åsikter om rails, ruby on rails, programmering, haml, acts_as_taggable_redux, markup, taggar.
October 9th, 2009
Posted by
Daniel Brahneborg |
blogg |
one comment
På jobbet hamnade jag i en situation som skulle kunna göra att våra stackars kunder skulle få tusentals samtidiga uppkopplingar till sin MySQL-databas. Tio kanske är ok, i bästa fall hundra, men därefter tror jag inte att databasen blir så glad längre. Alltså behövdes en “connection pool”, så att det bara behövdes så många uppkopplingar som faktiskt användes samtidigt. Om det behövdes för många uppkopplingar, fick de trådarna helt enkelt ställa sig på kö. Så långt var det det ju inget konstigt.
Tyvärr är vår applikation skriven i C, så alla färdiga paket för sådant här, som nästan alltid är skrivna i Java, går inte att använda. Istället för att ägna halva dagen åt att leta upp något existerande som gick att använda från C, bedöma om licensen gjorde det användbart, fundera över prislappen, anpassa till vår produkt osv, så satte vi oss ner och skissade på en egen implementation.
Först behövs en struct för poolen.
- int size // antalet skapade resurser (uppkopplingar)
- int maxsize // det maximala antalet tillåtna resurser, typiskt 10 eller så
- lock // lås, så att inte flera trådar försöker komma åt poolen samtidigt
- list available // lediga resurser
- list waiting // lås för trådar som väntar på en ledig resurs
- creator // funktion som skapar en ny resurs
Sedan behövs två små algoritmer. Först den för att hämta en resurs ur poolen.
- Lås pool.lock.
- Om det finns något i available-listan:
- Plocka ut första objektet i available-listan.
- Annars, om size < maxsize, dvs vi kan skapa fler resurser:
- Anropa creator-funktionen, och låt det objektet bli det som ska returneras.
- Räkna upp size.
- Annars:
- Hämta trådens lås från treadlocal.
- Lås trådens lås, och lägg det sist i waiting-listan.
- Lås upp pool.lock.
- Om vi ska vänta:
- Lås trådens lås igen, vilket gör att tråden hänger.
- När låset släpps, börja om från början.
- Returnera resursen.
Det behövs självklart också en liten algoritm för att lämna tillbaka resursen när den inte längre behövs.
- Lås pool.lock.
- Lägg tillbaka resursen i available-listan.
- Om waiting-listan inte är tom:
- Plocka ut första låset ur waiting-listan, och lås upp det.
- Lås upp pool.lock.
Om det finns lediga objekt krävs därför bara ett lås, annars två. Eftersom man plockar ut låsen ur en ordnad lista, är den helt rättvis. Det sker ingen busywait. Det skulle enkelt gå att öka maxsize i ett körande system, och med ett litet tillägg vid återlämnandet kan maxsize även minskas. Själva implementationen, inklusive några justeringar av algoritmen, var klar strax efter lunch.
Läs även andra bloggares åsikter om datorer, programmering, teknik, databas, mysql, connection pool.
August 26th, 2009
Posted by
Daniel Brahneborg |
blogg |
no comments
I Ruby on Rails använder man “migrations” för att uppgradera sitt databasschema vartefter applikationen utvecklas. De består av två delar, en som körs för att uppgradera databasen, och en annan som körs när man vill återställa databasen till hur den såg ut innan. På det sättet kan man gå fram och tillbaka bland ändringarna ganska smärtfritt. Data i databasen försvinner så klart, men det är ju lätt att göra backup på.
Mitt gamla “projekt S” ligger numera hos Heroku. Det är alltid bra att ha en remotesite att skicka sin kod till, som backup om inte annat. Det har funkat bra, och jag skickar ändringar fram och tillbaka med Linus excellenta versionshanteringssystem Git. Så långt är allting bra.
För ett tag sedan började jag lägga till tags, som ju finns på varenda webapplikation med självaktning. Först använde jag “acts_as_taggable_on_steroids“, som verkade lovande. Efter några tips från Berinder och Anders Andersson hittade jag sedan acts_as_taggable_redux, som hade support för personliga taggar. Det lät ju jättebra, bortsett från att den använde precis samma tabellnamn som den förra.
Dum som jag är så gjorde jag helt enkelt en db:migrate:down för att plocka bort de tabellerna, tog bort den gamla migration-filen, skapade en ny för den nya pluginen, och körde db:migrate:up igen. Det fungerade jättefint på min lokala databas.
Sedan skickade jag upp allting till Heroku, varvid den blev totalt förvirrad. Att backa en migration gick inte, för den filen fanns ju inte längre. Att köra den nya gick inte heller, eftersom tabellerna redan fanns. På något sätt lyckades jag ändå komma vidare, men plötsligt saknades en kolumn, och allting var allmänt kaotiskt.
Databasen är gömd, så någon direkt SQL-access har man inte. Dags att maila support. För en plattform som officiellt inte har någon support. Heja mig.
Rätt svar, vilket Heroku-killen hjälpte mig med, är självklart att kommentera bort det som ska köras för “db:migrate:down”, tills man är på rätt nivå. Sedan kan man glatt köra “up” igen. Och simsalabim så var min databas hos Heroku korrekt igen.
Läs även andra bloggares åsikter om datorer, programmering, ruby on rails, heroku.
June 23rd, 2009
Posted by
Daniel Brahneborg |
blogg |
no comments
Jag håller på att pyssla ihop en ny websajt där man bland annat ska kunna tagga saker. Sådan där vanlig taggning med godtyckliga nyckelord så att man kan hitta relaterade saker, fixa fina tagg-moln osv. Helt normalt. Sakerna man taggar är sådant som man lägger in själv.
Problemet är att jag vill att man ska kunna tagga även andras saker, inte bara ens egna (som hos delicious). Inget konstigt där heller, det är ju bara att lägga till lite poster i en många-till-många-tabell.
Men sedan kommer man till användningsfallet “ta bort en tagg”. Ska man få göra det? Om jag har taggat någonting med ett jättebra nyckelord, så vill jag ju inte att Erik Elak ska komma och ta bort den. Eller är sådant i praktiken inte något problem? Om jag har stavat fel på en tagg eller inser att det nog inte passade där, vill jag ju gärna kunna ta bort det.
Alltså skulle jag behöva hålla ordning på vem som hade lagt till varje enskild tagg, och sedan på websidan dela upp taggarna så att de man själv har satt ligger i en lista, och de som alla andra har satt ligger i en annan. Vilket blir skitfånigt om det finns två-tre taggar sammanlagt.
Finns det någon annan uppenbar lösning som jag inte har tänkt på?
Miljön är Ruby On Rails, även om det i sammanhanget kanske inte spelar någon roll.
Läs även andra bloggares åsikter om datorer, internet, programmering, tagging, ruby on rails.
June 12th, 2009
Posted by
Daniel Brahneborg |
blogg |
6 comments
Det är ju en handfull av de vars twittringar jag följer som brukar lägga ut sina blogginlägg där. Rubrik plus en kortlänk till dem, alltså.
Gör ni detta för hand, eller finns något trevligt verktyg som jag har missat? Att få twittringar till en wordpress-widget är ju inte så svårt. Jag vet också att Jaiku kan plocka inlägg från ett RSS/Atom-feed, men jag hittar inget sådant vare sig hos Twitter eller Bloggy. Är jag blind?
Läs även andra bloggares åsikter om blogg, programmering, internet, twitter, jaiku, bloggy, wordpress, rss, atom, mikroblogg, feed.
April 28th, 2009
Posted by
Daniel Brahneborg |
blogg |
4 comments
Jag blev lite inspirerad av Saltå Kvarns inlägg om biodynamiska bönder, som enligt rykten sår mitt i natten vid fullmåne. Tydligen görs även provsmakningar av vin bara vissa dagar, fast det framgick inte om det var beroende på månens fas eller något annat.
Man kanske skulle kunna följa månens fas även som programmerare? Bara skriva ny kod när månen går mot fullmåne, bara debugga och testa när den går mot nymåne, släppa en ny release när det är vår-, sommar-, höst- eller vintersolstånd, och skriva dokumentation varje gång det är fullständig solförmörkelse?
Hmm, det är ju ungefär så jag gör, nu när jag tänker efter. Åtminstone det sista.
Läs även andra bloggares åsikter om humor, datorer, programmering.
April 20th, 2009
Posted by
Daniel Brahneborg |
blogg |
no comments
« Äldre |