Det behövs ingen cache

Egentligen är det för sent, då jag redan börjat om på ny kula. Men tänk att man skulle kunna trolla bort vad som krånglat till STTylus mest?

Det här slog mig igårkväll i bilen när jag tänkte på något helt annat påväg till grannbyn för att äta taco hos några vänner.

Sttylus långtidslagrar användarnas förkortningar i en databas på serverns hårddisk, och använder en hash map per användare för att cacha förkortningar kortsiktigt. Tanken är att alltid hålla en aktuell sorterad lista, som kombinerar valda listor så att rätt förkortningar ur ämneslistor trumfar dom ur standardlistan. Detta för att i stundens hetta bara behöva leta på ett ställe för att hitta rätt fras till den aktuella förkortningen som tolken vill använda.

    if s.cache.UserSuggestions[q.UserID] == nil { 
        s.cache.UserSuggestions[q.UserID] = make(map[string]int) 
    } 
    cache := make(map[string]*Abbreviation) 
    lookup := make(map[string][]*Abbreviation)

    listIDs := []string{q.Standard} 
    listIDs = append(listIDs, q.Addon...)
    lists, err := s.repo.GetLists(listIDs) 
    if err != nil { return err }

    sort.Slice(lists, func(i, j int) bool { 
        return lists[i].Type < lists[j].Type 
    })

    for _, l := range lists { 
        abbs, err := s.repo.GetAbbs(l.ID)
        if err != nil { 
            return fmt.Errorf("service|Cache failed getting abbs: %q", err)
        }
        for _, a := range abbs { 
            cache[a.Abb] = a 
            lowercaseWord := strings.ToLower(a.Word) 
            lookup[lowercaseWord] = append(lookup[lowercaseWord], a) 
        } 
    }

    s.cache.UserAbbs[q.UserID] = cache 
    s.cache.UserLookup[q.UserID] = lookup
    s.cache.UserAbbLists[q.UserID] = listIDs 
    return nil 
}

Ett problem med det här är att den här kombinerade listan behöver synkas och uppdateras med jämna mellanrum, vilket är en process som tar tid och använder mycket minne. Annars kan gamla förkortningar hänga kvar i arbetsminnets trots att dom inte går att finna i någon förkortningslista. Det här steget behöver göras i någon utsträckning varje gång en förkortning ändras, tas bort eller ändras i någon av dom valda listorna. Ett annat exempel är om man vill föra statistik över förkortningsanvändning. Först måste programmet läsa i cache-listan för att ta reda på vilken förkortning/fras som det gäller, och sen peta på den rätta förkortningen i databasen för att uppdatera kolumnen för “användningar” och “senaste användningen”. Och informationen om vilken lista förekortningen existerar i går förlorad i cachningen…

Ett kanske ännu större problem är att denna cache i egentlig mening är helt onödig och bara introducerar felkällor och dubbelarbete.

Jag åtog mig ärendet att lära mig programmera “på riktigt” för att lösa på ytan sett väldigt specifika problem snabbt. Därför tycks jag oftast ha känt mig färdig i det ögonblicket som jag kunde visa på problemets mest basala lösning. I det ögonblicket kan jag släppa på stressen, och då stressen har varit min huvudsakliga motor så har jag också haft väldigt svårt med disciplinen att göra det som inte varit lika stressigt, typ göra dokumentation eller tänka på användarvänlighet.

När man drivs av stress tänker man kanske heller inte särskilt djupt och utförligt på problemen och vad den kortsiktigt fungerande lösningen kan tänkas innebära på lång sikt eller när den skalas upp. Tanken är inte helt dum om man har överseende med att den bygger på otillräcklig kunskap.

Hade jag istället lärt mig programmera under mer ordnade former, till exempel på en utbildning så jag i ett tidigt skede introducerats för det matematiska begreppet Ordo-notation som är ett sätt att räkna på och tänka kring tidskomplexitet och resursanvändning vid beräkningar och betecknas O(f(n)). Med hjälp av det hade jag kunnat titta på lösningen där programmet handskas med förkortningar i två instanser och säga att en hash map är O(1) och använder konstant tid, vilket betyder att tiden det inte spelar någon roll hur lång en förkortningslista är. Det tar alltid lika lång tid att skriva till, uppdatera, ta bort eller hämta förkortningar, oavsett hur lång listan blir.

Det är alltså mycket rimligare att bara lagra prioriteringsordningen och sen utgå ifrån den och söka igenom vardera lista i tur och ordning tills programmet hittar den första matchningen.