repos / pico

pico services mono repo
git clone https://github.com/picosh/pico.git

commit
83a5183
parent
dc6ed61
author
Eric Bower
date
2026-05-05 10:47:35 -0400 EDT
chore(visits): new indexes and parallel queries
3 files changed,  +162, -88
M Makefile
+2, -1
 1@@ -151,10 +151,11 @@ migrate:
 2 	$(DOCKER_CMD) exec -i $(DB_CONTAINER) psql -U $(PGUSER) -d $(PGDATABASE) < ./sql/migrations/20260116_add_analytics_filter_indexes.sql
 3 	$(DOCKER_CMD) exec -i $(DB_CONTAINER) psql -U $(PGUSER) -d $(PGDATABASE) < ./sql/migrations/20260305_add_forms_table.sql
 4 	$(DOCKER_CMD) exec -i $(DB_CONTAINER) psql -U $(PGUSER) -d $(PGDATABASE) < ./sql/migrations/20260503_add_analytics_summary_tables.sql
 5+	$(DOCKER_CMD) exec -i $(DB_CONTAINER) psql -U $(PGUSER) -d $(PGDATABASE) < ./sql/migrations/20260504_add_analytics_summary_indexes.sql
 6 .PHONY: migrate
 7 
 8 latest:
 9-	$(DOCKER_CMD) exec -i $(DB_CONTAINER) psql -U $(PGUSER) -d $(PGDATABASE) < ./sql/migrations/20260503_add_analytics_summary_tables.sql
10+	$(DOCKER_CMD) exec -i $(DB_CONTAINER) psql -U $(PGUSER) -d $(PGDATABASE) < ./sql/migrations/20260504_add_analytics_summary_indexes.sql
11 .PHONY: latest
12 
13 psql:
M pkg/db/postgres/storage.go
+153, -87
  1@@ -9,6 +9,7 @@ import (
  2 	"slices"
  3 	"sort"
  4 	"strings"
  5+	"sync"
  6 	"time"
  7 
  8 	"github.com/jmoiron/sqlx"
  9@@ -724,14 +725,28 @@ func visitFilterBy(opts *db.SummaryOpts) (string, string) {
 10 }
 11 
 12 func (me *PsqlDB) visitUnique(opts *db.SummaryOpts) ([]*db.VisitInterval, error) {
 13-	intervals, err := me.visitUniqueFromSummary(opts)
 14-	if err != nil {
 15-		return nil, fmt.Errorf("query summary visits: %w", err)
 16-	}
 17+	var intervals, currentIntervals []*db.VisitInterval
 18+	var sumErr, rawErr error
 19 
 20-	currentIntervals, err := me.visitUniqueFromRaw(opts)
 21-	if err != nil {
 22-		return nil, fmt.Errorf("query raw visits: %w", err)
 23+	var wg sync.WaitGroup
 24+	wg.Add(2)
 25+
 26+	go func() {
 27+		defer wg.Done()
 28+		intervals, sumErr = me.visitUniqueFromSummary(opts)
 29+	}()
 30+	go func() {
 31+		defer wg.Done()
 32+		currentIntervals, rawErr = me.visitUniqueFromRaw(opts)
 33+	}()
 34+
 35+	wg.Wait()
 36+
 37+	if sumErr != nil {
 38+		return nil, fmt.Errorf("query summary visits: %w", sumErr)
 39+	}
 40+	if rawErr != nil {
 41+		return nil, fmt.Errorf("query raw visits: %w", rawErr)
 42 	}
 43 
 44 	// Merge: current month data may overlap with summary data, combine counts
 45@@ -744,23 +759,13 @@ func (me *PsqlDB) visitUniqueFromSummary(opts *db.SummaryOpts) ([]*db.VisitInter
 46 	currentMonthStart := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, time.UTC)
 47 	previousMonthStart := currentMonthStart.AddDate(0, -1, 0)
 48 
 49-	// Always include at least the previous month in the summary query, since raw data
 50-	// for the previous month may have been deleted by aggregation. visitUniqueFromRaw
 51-	// covers the previous and current months, but once aggregation runs and deletes raw
 52-	// data, the summary table is the only source. Using min(origin, previousMonthStart)
 53-	// ensures we don't skip the previous month even when origin is set to the current month.
 54-	effectiveOrigin := opts.Origin
 55-	if opts.Origin.After(previousMonthStart) {
 56-		effectiveOrigin = previousMonthStart
 57-	}
 58-
 59-	// If effective origin is in or after current month, no historical data to fetch
 60-	if !effectiveOrigin.Before(currentMonthStart) {
 61+	// If origin is in the previous month or later, raw data covers it — no summary to fetch.
 62+	if !opts.Origin.Before(previousMonthStart) {
 63 		return nil, nil
 64 	}
 65 
 66 	where := ""
 67-	args := []interface{}{opts.UserID, effectiveOrigin, currentMonthStart}
 68+	args := []interface{}{opts.UserID, opts.Origin, currentMonthStart}
 69 	argIdx := 4
 70 	if opts.Host != "" {
 71 		where = "AND host = $" + fmt.Sprintf("%d", argIdx)
 72@@ -953,14 +958,28 @@ func mergeHosts(historical, current []*db.VisitUrl) []*db.VisitUrl {
 73 }
 74 
 75 func (me *PsqlDB) visitReferer(opts *db.SummaryOpts) ([]*db.VisitUrl, error) {
 76-	historical, err := me.visitRefererFromSummary(opts)
 77-	if err != nil {
 78-		return nil, fmt.Errorf("query summary referers: %w", err)
 79-	}
 80+	var historical, current []*db.VisitUrl
 81+	var histErr, rawErr error
 82 
 83-	current, err := me.visitRefererFromRaw(opts)
 84-	if err != nil {
 85-		return nil, fmt.Errorf("query raw referers: %w", err)
 86+	var wg sync.WaitGroup
 87+	wg.Add(2)
 88+
 89+	go func() {
 90+		defer wg.Done()
 91+		historical, histErr = me.visitRefererFromSummary(opts)
 92+	}()
 93+	go func() {
 94+		defer wg.Done()
 95+		current, rawErr = me.visitRefererFromRaw(opts)
 96+	}()
 97+
 98+	wg.Wait()
 99+
100+	if histErr != nil {
101+		return nil, fmt.Errorf("query summary referers: %w", histErr)
102+	}
103+	if rawErr != nil {
104+		return nil, fmt.Errorf("query raw referers: %w", rawErr)
105 	}
106 
107 	return mergeTopReferers(historical, current), nil
108@@ -972,18 +991,13 @@ func (me *PsqlDB) visitRefererFromSummary(opts *db.SummaryOpts) ([]*db.VisitUrl,
109 	currentMonthStart := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, time.UTC)
110 	previousMonthStart := currentMonthStart.AddDate(0, -1, 0)
111 
112-	effectiveOrigin := opts.Origin
113-	if opts.Origin.After(previousMonthStart) {
114-		effectiveOrigin = previousMonthStart
115-	}
116-
117-	// If effective origin is in or after current month, no historical data to fetch
118-	if !effectiveOrigin.Before(currentMonthStart) {
119+	// If origin is in the previous month or later, raw data covers it — no summary to fetch.
120+	if !opts.Origin.Before(previousMonthStart) {
121 		return nil, nil
122 	}
123 
124 	// Clamp origin to month boundary for summary table lookup
125-	originMonthStart := time.Date(effectiveOrigin.Year(), effectiveOrigin.Month(), 1, 0, 0, 0, 0, time.UTC)
126+	originMonthStart := time.Date(opts.Origin.Year(), opts.Origin.Month(), 1, 0, 0, 0, 0, time.UTC)
127 
128 	where := ""
129 	args := []interface{}{opts.UserID, originMonthStart, currentMonthStart}
130@@ -1059,14 +1073,28 @@ func (me *PsqlDB) visitRefererFromRaw(opts *db.SummaryOpts) ([]*db.VisitUrl, err
131 }
132 
133 func (me *PsqlDB) visitUrl(opts *db.SummaryOpts) ([]*db.VisitUrl, error) {
134-	historical, err := me.visitUrlFromSummary(opts)
135-	if err != nil {
136-		return nil, fmt.Errorf("query summary urls: %w", err)
137-	}
138+	var historical, current []*db.VisitUrl
139+	var histErr, rawErr error
140 
141-	current, err := me.visitUrlFromRaw(opts)
142-	if err != nil {
143-		return nil, fmt.Errorf("query raw urls: %w", err)
144+	var wg sync.WaitGroup
145+	wg.Add(2)
146+
147+	go func() {
148+		defer wg.Done()
149+		historical, histErr = me.visitUrlFromSummary(opts)
150+	}()
151+	go func() {
152+		defer wg.Done()
153+		current, rawErr = me.visitUrlFromRaw(opts)
154+	}()
155+
156+	wg.Wait()
157+
158+	if histErr != nil {
159+		return nil, fmt.Errorf("query summary urls: %w", histErr)
160+	}
161+	if rawErr != nil {
162+		return nil, fmt.Errorf("query raw urls: %w", rawErr)
163 	}
164 
165 	return mergeTopUrls(historical, current), nil
166@@ -1078,18 +1106,13 @@ func (me *PsqlDB) visitUrlFromSummary(opts *db.SummaryOpts) ([]*db.VisitUrl, err
167 	currentMonthStart := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, time.UTC)
168 	previousMonthStart := currentMonthStart.AddDate(0, -1, 0)
169 
170-	effectiveOrigin := opts.Origin
171-	if opts.Origin.After(previousMonthStart) {
172-		effectiveOrigin = previousMonthStart
173-	}
174-
175-	// If effective origin is in or after current month, no historical data to fetch
176-	if !effectiveOrigin.Before(currentMonthStart) {
177+	// If origin is in the previous month or later, raw data covers it — no summary to fetch.
178+	if !opts.Origin.Before(previousMonthStart) {
179 		return nil, nil
180 	}
181 
182 	// Clamp origin to month boundary for summary table lookup
183-	originMonthStart := time.Date(effectiveOrigin.Year(), effectiveOrigin.Month(), 1, 0, 0, 0, 0, time.UTC)
184+	originMonthStart := time.Date(opts.Origin.Year(), opts.Origin.Month(), 1, 0, 0, 0, 0, time.UTC)
185 
186 	where := ""
187 	args := []interface{}{opts.UserID, originMonthStart, currentMonthStart}
188@@ -1170,14 +1193,28 @@ func (me *PsqlDB) VisitUrlNotFound(opts *db.SummaryOpts) ([]*db.VisitUrl, error)
189 		limit = 10
190 	}
191 
192-	historical, err := me.visitUrlNotFoundFromSummary(opts, limit)
193-	if err != nil {
194-		return nil, fmt.Errorf("query summary 404 urls: %w", err)
195-	}
196+	var historical, current []*db.VisitUrl
197+	var histErr, rawErr error
198 
199-	current, err := me.visitUrlNotFoundFromRaw(opts, limit)
200-	if err != nil {
201-		return nil, fmt.Errorf("query raw 404 urls: %w", err)
202+	var wg sync.WaitGroup
203+	wg.Add(2)
204+
205+	go func() {
206+		defer wg.Done()
207+		historical, histErr = me.visitUrlNotFoundFromSummary(opts, limit)
208+	}()
209+	go func() {
210+		defer wg.Done()
211+		current, rawErr = me.visitUrlNotFoundFromRaw(opts, limit)
212+	}()
213+
214+	wg.Wait()
215+
216+	if histErr != nil {
217+		return nil, fmt.Errorf("query summary 404 urls: %w", histErr)
218+	}
219+	if rawErr != nil {
220+		return nil, fmt.Errorf("query raw 404 urls: %w", rawErr)
221 	}
222 
223 	return mergeTopUrls(historical, current), nil
224@@ -1189,18 +1226,13 @@ func (me *PsqlDB) visitUrlNotFoundFromSummary(opts *db.SummaryOpts, limit int) (
225 	currentMonthStart := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, time.UTC)
226 	previousMonthStart := currentMonthStart.AddDate(0, -1, 0)
227 
228-	effectiveOrigin := opts.Origin
229-	if opts.Origin.After(previousMonthStart) {
230-		effectiveOrigin = previousMonthStart
231-	}
232-
233-	// If effective origin is in or after current month, no historical data to fetch
234-	if !effectiveOrigin.Before(currentMonthStart) {
235+	// If origin is in the previous month or later, raw data covers it — no summary to fetch.
236+	if !opts.Origin.Before(previousMonthStart) {
237 		return nil, nil
238 	}
239 
240 	// Clamp origin to month boundary for summary table lookup
241-	originMonthStart := time.Date(effectiveOrigin.Year(), effectiveOrigin.Month(), 1, 0, 0, 0, 0, time.UTC)
242+	originMonthStart := time.Date(opts.Origin.Year(), opts.Origin.Month(), 1, 0, 0, 0, 0, time.UTC)
243 
244 	where := ""
245 	args := []interface{}{opts.UserID, originMonthStart, currentMonthStart}
246@@ -1277,14 +1309,28 @@ func (me *PsqlDB) visitUrlNotFoundFromRaw(opts *db.SummaryOpts, limit int) ([]*d
247 }
248 
249 func (me *PsqlDB) visitHost(opts *db.SummaryOpts) ([]*db.VisitUrl, error) {
250-	historical, err := me.visitHostFromSummary(opts)
251-	if err != nil {
252-		return nil, fmt.Errorf("query summary hosts: %w", err)
253-	}
254+	var historical, current []*db.VisitUrl
255+	var histErr, rawErr error
256 
257-	current, err := me.visitHostFromRaw(opts)
258-	if err != nil {
259-		return nil, fmt.Errorf("query raw hosts: %w", err)
260+	var wg sync.WaitGroup
261+	wg.Add(2)
262+
263+	go func() {
264+		defer wg.Done()
265+		historical, histErr = me.visitHostFromSummary(opts)
266+	}()
267+	go func() {
268+		defer wg.Done()
269+		current, rawErr = me.visitHostFromRaw(opts)
270+	}()
271+
272+	wg.Wait()
273+
274+	if histErr != nil {
275+		return nil, fmt.Errorf("query summary hosts: %w", histErr)
276+	}
277+	if rawErr != nil {
278+		return nil, fmt.Errorf("query raw hosts: %w", rawErr)
279 	}
280 
281 	return mergeHosts(historical, current), nil
282@@ -1341,24 +1387,44 @@ func (me *PsqlDB) visitHostFromRaw(opts *db.SummaryOpts) ([]*db.VisitUrl, error)
283 }
284 
285 func (me *PsqlDB) VisitSummary(opts *db.SummaryOpts) (*db.SummaryVisits, error) {
286-	visitors, err := me.visitUnique(opts)
287-	if err != nil {
288-		return nil, err
289-	}
290+	var (
291+		visitors    []*db.VisitInterval
292+		urls        []*db.VisitUrl
293+		refs        []*db.VisitUrl
294+		notFound    []*db.VisitUrl
295+		visitorsErr error
296+		urlsErr     error
297+		refsErr     error
298+		nfErr       error
299+	)
300 
301-	urls, err := me.visitUrl(opts)
302-	if err != nil {
303-		return nil, err
304-	}
305+	var wg sync.WaitGroup
306+	wg.Add(4)
307 
308-	notFound, err := me.VisitUrlNotFound(opts)
309-	if err != nil {
310-		return nil, err
311-	}
312+	go func() {
313+		defer wg.Done()
314+		visitors, visitorsErr = me.visitUnique(opts)
315+	}()
316+	go func() {
317+		defer wg.Done()
318+		urls, urlsErr = me.visitUrl(opts)
319+	}()
320+	go func() {
321+		defer wg.Done()
322+		refs, refsErr = me.visitReferer(opts)
323+	}()
324+	go func() {
325+		defer wg.Done()
326+		notFound, nfErr = me.VisitUrlNotFound(opts)
327+	}()
328 
329-	refs, err := me.visitReferer(opts)
330-	if err != nil {
331-		return nil, err
332+	wg.Wait()
333+
334+	// Return the first error encountered
335+	for _, err := range []error{visitorsErr, urlsErr, refsErr, nfErr} {
336+		if err != nil {
337+			return nil, err
338+		}
339 	}
340 
341 	return &db.SummaryVisits{
A sql/migrations/20260504_add_analytics_summary_indexes.sql
+7, -0
1@@ -0,0 +1,7 @@
2+-- Composite index for visitUniqueFromSummary: filters by (user_id, host, visit_date)
3+CREATE INDEX IF NOT EXISTS idx_monthly_visits_user_host_date
4+ON analytics_monthly_visits (user_id, host, visit_date);
5+
6+-- Covering index for visitHostFromRaw: filters by (user_id, created_at), groups by host, counts distinct ip_address
7+CREATE INDEX IF NOT EXISTS idx_analytics_visits_user_created_host_ip
8+ON analytics_visits (user_id, created_at, host, ip_address);