5580b5b65433239ee9a379172f26c30b30e2ff61

Author
Ayman Bagabas <ayman.bagabas@gmail.com>
Committer
GitHub <noreply@github.com>
Date

Message

fix(backend): cache repos in memory (#274)

* fix(backend): cache repos in memory using lru

Signed-off-by: Ayman Bagabas <ayman.bagabas@gmail.com>

* fix(backend): use mutex instead of rwmutex

---------

Signed-off-by: Ayman Bagabas <ayman.bagabas@gmail.com>

Diff

This diff is truncated to protect this page.

  1diff --git a/go.mod b/go.mod
  2index 9c85d1d8f9683ea35402c02c75e17c9363ba30b5..e7ca5b691bd1264b1114d256ca39ee868b16f46f 100644
  3--- a/go.mod
  4+++ b/go.mod
  5@@ -1,6 +1,6 @@
  6 module github.com/charmbracelet/soft-serve
  7 
  8-go 1.17
  9+go 1.18
 10 
 11 require (
 12 	github.com/alecthomas/chroma v0.10.0
 13@@ -24,6 +24,7 @@ require (
 14 	github.com/charmbracelet/ssh v0.0.0-20221117183211-483d43d97103
 15 	github.com/gobwas/glob v0.2.3
 16 	github.com/gogs/git-module v1.8.2
 17+	github.com/hashicorp/golang-lru/v2 v2.0.2
 18 	github.com/jmoiron/sqlx v1.3.5
 19 	github.com/lrstanley/bubblezone v0.0.0-20220716194435-3cb8c52f6a8f
 20 	github.com/muesli/mango-cobra v1.2.0
 21diff --git a/go.sum b/go.sum
 22index c35eafe2b45fb008585ae5060f9b9ac0823d2c89..3cbdc98fb3c8fb6879af328bfd44a958c92dd536 100644
 23--- a/go.sum
 24+++ b/go.sum
 25@@ -1,67 +1,18 @@
 26-cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 27-cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 28-cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
 29-cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
 30-cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
 31-cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
 32-cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
 33-cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
 34-cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
 35-cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
 36-cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
 37-cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
 38-cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
 39-cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
 40-cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
 41-cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
 42-cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
 43-cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
 44-cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
 45-cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
 46-cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
 47-cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
 48-cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
 49-cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
 50-cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
 51-cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
 52-cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
 53-cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
 54-cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
 55-cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
 56-cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
 57-cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
 58-cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
 59-dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
 60-github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 61-github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
 62 github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
 63 github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8/go.mod h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g=
 64 github.com/acomagu/bufpipe v1.0.4/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4=
 65 github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek=
 66 github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s=
 67-github.com/alecthomas/kingpin/v2 v2.3.1/go.mod h1:oYL5vtsvEHZGHxU7DMp32Dvx+qL+ptGn6lWaot2vCNE=
 68-github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
 69-github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
 70-github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 71-github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 72-github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
 73-github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE=
 74 github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
 75 github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
 76 github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
 77 github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
 78 github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
 79-github.com/aymanbagabas/git-module v1.4.1-0.20230509180555-975c24cdb79a h1:rY724fIR0NNR/UTXJufwKwYz+sNYVve/ZdzWX39xMqM=
 80-github.com/aymanbagabas/git-module v1.4.1-0.20230509180555-975c24cdb79a/go.mod h1:GUSSUH+RM7fZOtjhS6Obh4B9aAvs3EeROpazfMNMF8g=
 81 github.com/aymanbagabas/go-osc52 v1.0.3/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4=
 82-github.com/aymanbagabas/go-osc52 v1.2.1 h1:q2sWUyDcozPLcLabEMd+a+7Ea2DitxZVN9hTxab9L4E=
 83-github.com/aymanbagabas/go-osc52 v1.2.1/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4=
 84 github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
 85 github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
 86 github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
 87 github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
 88-github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
 89-github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
 90 github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
 91 github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
 92 github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
 93@@ -69,16 +20,11 @@ github.com/caarlos0/env/v7 v7.1.0 h1:9lzTF5amyQeWHZzuZeKlCb5FWSUxpG1js43mhbY8ozg
 94 github.com/caarlos0/env/v7 v7.1.0/go.mod h1:LPPWniDUq4JaO6Q41vtlyikhMknqymCLBw0eX4dcH1E=
 95 github.com/caarlos0/sshmarshal v0.1.0 h1:zTCZrDORFfWh526Tsb7vCm3+Yg/SfW/Ub8aQDeosk0I=
 96 github.com/caarlos0/sshmarshal v0.1.0/go.mod h1:7Pd/0mmq9x/JCzKauogNjSQEhivBclCQHfr9dlpDIyA=
 97-github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
 98-github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 99-github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
100 github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
101 github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
102 github.com/charmbracelet/bubbles v0.15.0 h1:c5vZ3woHV5W2b8YZI1q7v4ZNQaPetfHuoHzx+56Z6TI=
103 github.com/charmbracelet/bubbles v0.15.0/go.mod h1:Y7gSFbBzlMpUDR/XM9MhZI374Q+1p1kluf1uLl8iK74=
104-github.com/charmbracelet/bubbletea v0.22.0/go.mod h1:aoVIwlNlr5wbCB26KhxfrqAn0bMp4YpJcoOelbxApjs=
105 github.com/charmbracelet/bubbletea v0.23.1/go.mod h1:JAfGK/3/pPKHTnAS8JIE2u9f61BjWTQY57RbT25aMXU=
106-github.com/charmbracelet/bubbletea v0.23.2/go.mod h1:FaP3WUivcTM0xOKNmhciz60M6I+weYLF76mr1JyI7sM=
107 github.com/charmbracelet/bubbletea v0.24.0 h1:l8PHrft/GIeikDPCUhQe53AJrDD8xGSn0Agirh8xbe8=
108 github.com/charmbracelet/bubbletea v0.24.0/go.mod h1:rK3g/2+T8vOSEkNHvtq40umJpeVYDn6bLaqbgzhL/hg=
109 github.com/charmbracelet/glamour v0.6.0 h1:wi8fse3Y7nfcabbbDuwolqTqMQPMnVPeZhDM273bISc=
110@@ -86,7 +32,6 @@ github.com/charmbracelet/glamour v0.6.0/go.mod h1:taqWV4swIMMbWALc0m7AfE9JkPSU8o
111 github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao=
112 github.com/charmbracelet/keygen v0.4.2 h1:TNHua2MlXc6W1dQB2iW4msSZGKlb8RtxtmYDWUs4iRw=
113 github.com/charmbracelet/keygen v0.4.2/go.mod h1:4e4FT3HSdLU/u83RfJWvzJIaVb8aX4MxtDlfXwpDJaI=
114-github.com/charmbracelet/lipgloss v0.5.0/go.mod h1:EZLha/HbzEt7cYqdFPovlqy5FZPj0xFhg5SaqxScmgs=
115 github.com/charmbracelet/lipgloss v0.6.0/go.mod h1:tHh2wr34xcHjC2HCXIlGSG1jaDF0S0atAUvBMP6Ppuk=
116 github.com/charmbracelet/lipgloss v0.7.1 h1:17WMwi7N1b1rVWOjMT+rCh7sQkvDU75B2hbZpc5Kc1E=
117 github.com/charmbracelet/lipgloss v0.7.1/go.mod h1:yG0k3giv8Qj8edTCbbg6AlQ5e8KNWpFujkNawKNhE2c=
118@@ -96,15 +41,7 @@ github.com/charmbracelet/ssh v0.0.0-20221117183211-483d43d97103 h1:wpHMERIN0pQZE
119 github.com/charmbracelet/ssh v0.0.0-20221117183211-483d43d97103/go.mod h1:0Vm2/8yBljiLDnGJHU8ehswfawrEybGk33j5ssqKQVM=
120 github.com/charmbracelet/wish v1.1.1 h1:KdICASKd2oh2JPvk1Z4CJtAi97cFErXF7NKienPICO4=
121 github.com/charmbracelet/wish v1.1.1/go.mod h1:xh4KZpSULw+Xqb9bcbhw92QAinVB75CVLWrFuyY6IVs=
122-github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
123-github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4MedaY=
124-github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
125diff --git a/server/backend/sqlite/hooks.go b/server/backend/sqlite/hooks.go
126index 0f9d61658d0b061bc37c14e807c614444f5cc6c7..b3691b187e973f43ca0aeb90ba24b274da7d586a 100644
127--- a/server/backend/sqlite/hooks.go
128+++ b/server/backend/sqlite/hooks.go
129@@ -40,24 +40,58 @@ func (d *SqliteBackend) PostUpdate(stdout io.Writer, stderr io.Writer, repo stri
130 	wg.Add(1)
131 	go func() {
132 		defer wg.Done()
133-
134-		rr, err := d.Repository(repo)
135-		if err != nil {
136-			d.logger.Error("error getting repository", "repo", repo, "err", err)
137-			return
138-		}
139-
140-		r, err := rr.Open()
141-		if err != nil {
142-			d.logger.Error("error opening repository", "repo", repo, "err", err)
143+		if err := updateServerInfo(d, repo); err != nil {
144+			d.logger.Error("error updating server-info", "repo", repo, "err", err)
145 			return
146 		}
147+	}()
148 
149-		if err := r.UpdateServerInfo(); err != nil {
150-			d.logger.Error("error updating server-info", "repo", repo, "err", err)
151+	// Populate last-modified file.
152+	wg.Add(1)
153+	go func() {
154+		defer wg.Done()
155+		if err := populateLastModified(d, repo); err != nil {
156+			d.logger.Error("error populating last-modified", "repo", repo, "err", err)
157 			return
158 		}
159 	}()
160 
161 	wg.Wait()
162 }
163+
164+func updateServerInfo(d *SqliteBackend, repo string) error {
165+	rr, err := d.Repository(repo)
166+	if err != nil {
167+		return err
168+	}
169+
170+	r, err := rr.Open()
171+	if err != nil {
172+		return err
173+	}
174+
175+	return r.UpdateServerInfo()
176+}
177+
178+func populateLastModified(d *SqliteBackend, repo string) error {
179+	var rr *Repo
180+	if rr, err := d.Repository(repo); err != nil {
181+		return err
182+	} else if r, ok := rr.(*Repo); ok {
183+		rr = r
184+	} else {
185+		return ErrRepoNotExist
186+	}
187+
188+	r, err := rr.Open()
189+	if err != nil {
190+		return err
191+	}
192+
193+	c, err := r.LatestCommitTime()
194+	if err != nil {
195+		return err
196+	}
197+
198+	return rr.writeLastModified(c)
199+}
200diff --git a/server/backend/sqlite/repo.go b/server/backend/sqlite/repo.go
201index 6b90ca92cc805186a1e7af8fa769283120358a47..34b5e6baa597d7271f61e30189c006105ce8aee0 100644
202--- a/server/backend/sqlite/repo.go
203+++ b/server/backend/sqlite/repo.go
204@@ -1,7 +1,11 @@
205 package sqlite
206 
207 import (
208+	"bufio"
209 	"context"
210+	"os"
211+	"path/filepath"
212+	"sync"
213 	"time"
214 
215 	"github.com/charmbracelet/soft-serve/git"
216@@ -16,12 +20,27 @@ type Repo struct {
217 	name string
218 	path string
219 	db   *sqlx.DB
220+
221+	// cache
222+	// updatedAt is cached in "last-modified" file.
223+	mu          sync.Mutex
224+	desc        *string
225+	projectName *string
226+	isMirror    *bool
227+	isPrivate   *bool
228+	isHidden    *bool
229 }
230 
231 // Description returns the repository's description.
232 //
233 // It implements backend.Repository.
234 func (r *Repo) Description() string {
235+	r.mu.Lock()
236+	defer r.mu.Unlock()
237+	if r.desc != nil {
238+		return *r.desc
239+	}
240+
241 	var desc string
242 	if err := wrapTx(r.db, context.Background(), func(tx *sqlx.Tx) error {
243 		return tx.Get(&desc, "SELECT description FROM repo WHERE name = ?", r.name)
244@@ -29,6 +48,7 @@ func (r *Repo) Description() string {
245 		return ""
246 	}
247 
248+	r.desc = &desc
249 	return desc
250 }
251 
252@@ -36,6 +56,12 @@ func (r *Repo) Description() string {
253 //
254 // It implements backend.Repository.
255 func (r *Repo) IsMirror() bool {
256+	r.mu.Lock()
257+	defer r.mu.Unlock()
258+	if r.isMirror != nil {
259+		return *r.isMirror
260+	}
261+
262 	var mirror bool
263 	if err := wrapTx(r.db, context.Background(), func(tx *sqlx.Tx) error {
264 		return tx.Get(&mirror, "SELECT mirror FROM repo WHERE name = ?", r.name)
265@@ -43,6 +69,7 @@ func (r *Repo) IsMirror() bool {
266 		return false
267 	}
268 
269+	r.isMirror = &mirror
270 	return mirror
271 }
272 
273@@ -50,6 +77,12 @@ func (r *Repo) IsMirror() bool {
274 //
275 // It implements backend.Repository.
276 func (r *Repo) IsPrivate() bool {
277+	r.mu.Lock()
278+	defer r.mu.Unlock()
279+	if r.isPrivate != nil {
280+		return *r.isPrivate
281+	}
282+
283 	var private bool
284 	if err := wrapTx(r.db, context.Background(), func(tx *sqlx.Tx) error {
285 		return tx.Get(&private, "SELECT private FROM repo WHERE name = ?", r.name)
286@@ -57,6 +90,7 @@ func (r *Repo) IsPrivate() bool {
287 		return false
288 	}
289 
290+	r.isPrivate = &private
291 	return private
292 }
293 
294@@ -78,6 +112,12 @@ func (r *Repo) Open() (*git.Repository, error) {
295 //
296 // It implements backend.Repository.
297 func (r *Repo) ProjectName() string {
298+	r.mu.Lock()
299+	defer r.mu.Unlock()
300+	if r.projectName != nil {
301+		return *r.projectName
302+	}
303+
304diff --git a/server/backend/sqlite/sqlite.go b/server/backend/sqlite/sqlite.go
305index a96523c33f299b8b62878f8b1f54e6fac63001b9..e4c9111325d8c0c317325a4ffa39510c932290f3 100644
306--- a/server/backend/sqlite/sqlite.go
307+++ b/server/backend/sqlite/sqlite.go
308@@ -14,6 +14,7 @@ import (
309 	"github.com/charmbracelet/soft-serve/server/config"
310 	"github.com/charmbracelet/soft-serve/server/hooks"
311 	"github.com/charmbracelet/soft-serve/server/utils"
312+	lru "github.com/hashicorp/golang-lru/v2"
313 	"github.com/jmoiron/sqlx"
314 	_ "modernc.org/sqlite" // sqlite driver
315 )
316@@ -26,6 +27,9 @@ type SqliteBackend struct { //nolint: revive
317 	dp     string
318 	db     *sqlx.DB
319 	logger *log.Logger
320+
321+	// Repositories cache
322+	cache *cache
323 }
324 
325 var _ backend.Backend = (*SqliteBackend)(nil)
326@@ -56,6 +60,9 @@ func NewSqliteBackend(ctx context.Context) (*SqliteBackend, error) {
327 		logger: log.FromContext(ctx).WithPrefix("sqlite"),
328 	}
329 
330+	// Set up LRU cache with size 1000
331+	d.cache = newCache(d, 1000)
332+
333 	if err := d.init(); err != nil {
334 		return nil, err
335 	}
336@@ -170,6 +177,9 @@ func (d *SqliteBackend) CreateRepository(name string, opts backend.RepositoryOpt
337 		db:   d.db,
338 	}
339 
340+	// Set cache
341+	d.cache.Set(name, r)
342+
343 	return r, d.initRepo(name)
344 }
345 
346@@ -188,9 +198,9 @@ func (d *SqliteBackend) ImportRepository(name string, remote string, opts backen
347 	}
348 
349 	copts := git.CloneOptions{
350-		Bare:    true,
351-		Mirror:  opts.Mirror,
352-		Quiet:   true,
353+		Bare:   true,
354+		Mirror: opts.Mirror,
355+		Quiet:  true,
356 		CommandOptions: git.CommandOptions{
357 			Timeout: -1,
358 			Context: d.ctx,
359@@ -201,6 +211,7 @@ func (d *SqliteBackend) ImportRepository(name string, remote string, opts backen
360 				),
361 			},
362 		},
363+		// Timeout: time.Hour,
364 	}
365 
366 	if err := git.Clone(remote, rp, copts); err != nil {
367@@ -223,6 +234,9 @@ func (d *SqliteBackend) DeleteRepository(name string) error {
368 	rp := filepath.Join(d.reposPath(), repo)
369 
370 	return wrapTx(d.db, d.ctx, func(tx *sqlx.Tx) error {
371+		// Delete repo from cache
372+		defer d.cache.Delete(name)
373+
374 		if err := os.RemoveAll(rp); err != nil {
375 			return err
376 		}
377@@ -268,6 +282,12 @@ func (d *SqliteBackend) RenameRepository(oldName string, newName string) error {
378 		return err
379 	}
380 
381+	// Delete cache
382+	d.cache.Delete(oldName)
383+	defer func() {
384+		d.Repository(newName) // nolint: errcheck
385+	}()
386+
387 	return os.Rename(op, np)
388 }
389 
390@@ -276,6 +296,7 @@ func (d *SqliteBackend) RenameRepository(oldName string, newName string) error {
391 // It implements backend.Backend.
392 func (d *SqliteBackend) Repositories() ([]backend.Repository, error) {
393 	repos := make([]backend.Repository, 0)
394+
395 	if err := wrapTx(d.db, d.ctx, func(tx *sqlx.Tx) error {
396 		rows, err := tx.Query("SELECT name FROM repo")
397 		if err != nil {
398@@ -289,11 +310,21 @@ func (d *SqliteBackend) Repositories() ([]backend.Repository, error) {
399 				return err
400 			}
401 
402-			repos = append(repos, &Repo{
403+			if r, ok := d.cache.Get(name); ok && r != nil {
404+				repos = append(repos, r)
405+				continue
406+			}
407+