Repository: stellar/go
Pull request #4536 (2022-08-11 16:25:07)

all: Merge `master` into the 'lighthorizon' branch again.
### What
Self-explanatory.

### Why
I want generics.

### Known limitations
n/a


Patch:
diff --git a/exp/orderbook/search.go b/exp/orderbook/search.go
index d701bc9d..06b0f0f8 100644
--- a/exp/orderbook/search.go
+++ b/exp/orderbook/search.go
@@ -4,6 +4,7 @@ import (
 	"context"
 
 	"github.com/stellar/go/price"
+	"github.com/stellar/go/support/ordered"
 	"github.com/stellar/go/xdr"
 )
 
@@ -399,7 +400,7 @@ func (state *buyingGraphSearchState) consumeOffers(
 ) (xdr.Int64, error) {
 	nextAmount, err := consumeOffersForBuyingAsset(offers, currentAssetAmount)
 
-	return max(nextAmount, currentBestAmount), err
+	return ordered.Max(nextAmount, currentBestAmount), err
 }
 
 func (state *buyingGraphSearchState) considerPools() bool {
diff --git a/exp/orderbook/utils.go b/exp/orderbook/utils.go
index d1fad24a..ff0ac671 100644
--- a/exp/orderbook/utils.go
+++ b/exp/orderbook/utils.go
@@ -10,13 +10,6 @@ func getPoolAssets(pool xdr.LiquidityPoolEntry) (xdr.Asset, xdr.Asset) {
 	return params.AssetA, params.AssetB
 }
 
-func max(a, b xdr.Int64) xdr.Int64 {
-	if a < b {
-		return b
-	}
-	return a
-}
-
 // positiveMin returns the smallest positive value possible
 func positiveMin(a, b xdr.Int64) xdr.Int64 {
 	if b <= 0 {
diff --git a/exp/tools/dump-ledger-state/Dockerfile b/exp/tools/dump-ledger-state/Dockerfile
index eb5116ca..3935e578 100644
--- a/exp/tools/dump-ledger-state/Dockerfile
+++ b/exp/tools/dump-ledger-state/Dockerfile
@@ -6,7 +6,7 @@ ENV DEBIAN_FRONTEND=noninteractive
 RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates curl wget gnupg apt-utils
 RUN wget -qO - https://apt.stellar.org/SDF.asc | APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=true apt-key add -
 RUN echo "deb https://apt.stellar.org focal stable" >/etc/apt/sources.list.d/SDF.list
-RUN echo "deb https://apt.stellar.org focal unstable" >/etc/apt/sources.list.d/SDF-unstable.list
+# RUN echo "deb https://apt.stellar.org focal unstable" >/etc/apt/sources.list.d/SDF-unstable.list
 RUN apt-get update -y
 
 RUN apt-get install -y stellar-core=${STELLAR_CORE_VERSION} jq
diff --git a/go.mod b/go.mod
index 75de90a3..4b3c377a 100644
--- a/go.mod
+++ b/go.mod
@@ -65,7 +65,7 @@ require (
 	github.com/gobuffalo/packr v1.12.1 // indirect
 	github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
 	github.com/golang/protobuf v1.5.2 // indirect
-	github.com/google/go-cmp v0.5.6 // indirect
+	github.com/google/go-cmp v0.5.8 // indirect
 	github.com/google/go-querystring v0.0.0-20160401233042-9235644dd9e5 // indirect
 	github.com/googleapis/gax-go/v2 v2.0.5 // indirect
 	github.com/hashicorp/golang-lru v0.5.1
@@ -107,8 +107,9 @@ require (
 	github.com/ziutek/mymysql v1.5.4 // indirect
 	go.opencensus.io v0.23.0 // indirect
 	golang.org/x/crypto v0.0.0-20220214200702-86341886e292 // indirect
+	golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e
 	golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 // indirect
-	golang.org/x/mod v0.4.2 // indirect
+	golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
 	golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect
 	golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914 // indirect
 	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
@@ -116,7 +117,7 @@ require (
 	golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
 	golang.org/x/text v0.3.7 // indirect
 	golang.org/x/time v0.0.0-20220224211638-0e9765cccd65
-	golang.org/x/tools v0.1.4 // indirect
+	golang.org/x/tools v0.1.10 // indirect
 	golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
 	google.golang.org/appengine v1.6.7 // indirect
 	google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84 // indirect
diff --git a/go.sum b/go.sum
index ed20d373..d2d588c2 100644
--- a/go.sum
+++ b/go.sum
@@ -156,8 +156,9 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
 github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
 github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
+github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/google/go-querystring v0.0.0-20160401233042-9235644dd9e5 h1:oERTZ1buOUYlpmKaqlO5fYmz8cZ1rYu5DieJzF4ZVmU=
 github.com/google/go-querystring v0.0.0-20160401233042-9235644dd9e5/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
 github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=
@@ -388,6 +389,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
 golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
 golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
+golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA=
+golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA=
 golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
 golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -413,8 +416,9 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
 golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o=
+golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -603,8 +607,9 @@ golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
 golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
-golang.org/x/tools v0.1.4 h1:cVngSRcfgyZCzys3KYOpCFa+4dqX/Oub9tAq00ttGVs=
 golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20=
+golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
diff --git a/historyarchive/range.go b/historyarchive/range.go
index a240ef04..fda445ff 100644
--- a/historyarchive/range.go
+++ b/historyarchive/range.go
@@ -6,8 +6,10 @@ package historyarchive
 
 import (
 	"fmt"
-	"sort"
 	"strings"
+
+	"github.com/stellar/go/support/ordered"
+	"golang.org/x/exp/slices"
 )
 
 const DefaultCheckpointFrequency = uint32(64)
@@ -56,10 +58,8 @@ func (c CheckpointManager) NextCheckpoint(i uint32) uint32 {
 	freq := uint64(c.checkpointFreq)
 	v := uint64(i)
 	n := (((v + freq) / freq) * freq) - 1
-	if n >= 0xffffffff {
-		return 0xffffffff
-	}
-	return uint32(n)
+
+	return uint32(ordered.Min(n, 0xffffffff))
 }
 
 // GetCheckPoint gets the checkpoint containing information about the given ledger sequence
@@ -82,9 +82,7 @@ func (c CheckpointManager) GetCheckpointRange(i uint32) Range {
 }
 
 func (c CheckpointManager) MakeRange(low uint32, high uint32) Range {
-	if high < low {
-		high = low
-	}
+	high = ordered.Max(high, low)
 	return Range{
 		Low:  c.PrevCheckpoint(low),
 		High: c.NextCheckpoint(high),
@@ -92,14 +90,8 @@ func (c CheckpointManager) MakeRange(low uint32, high uint32) Range {
 }
 
 func (r Range) clamp(other Range, cManager CheckpointManager) Range {
-	low := r.Low
-	high := r.High
-	if low < other.Low {
-		low = other.Low
-	}
-	if high > other.High {
-		high = other.High
-	}
+	low := ordered.Max(r.Low, other.Low)
+	high := ordered.Min(r.High, other.High)
 	return cManager.MakeRange(low, high)
 }
 
@@ -125,24 +117,17 @@ func (r Range) SizeInCheckPoints(cManager CheckpointManager) int {
 func (r Range) collapsedString() string {
 	if r.Low == r.High {
 		return fmt.Sprintf("0x%8.8x", r.Low)
-	} else {
-		return fmt.Sprintf("[0x%8.8x-0x%8.8x]", r.Low, r.High)
 	}
+
+	return fmt.Sprintf("[0x%8.8x-0x%8.8x]", r.Low, r.High)
 }
 
 func (r Range) InRange(sequence uint32) bool {
 	return sequence >= r.Low && sequence <= r.High
 }
 
-type byUint32 []uint32
-
-func (a byUint32) Len() int           { return len(a) }
-func (a byUint32) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
-func (a byUint32) Less(i, j int) bool { return a[i] < a[j] }
-
 func fmtRangeList(vs []uint32, cManager CheckpointManager) string {
-
-	sort.Sort(byUint32(vs))
+	slices.Sort(vs)
 
 	s := make([]string, 0, 10)
 	var curr *Range
diff --git a/price/main.go b/price/main.go
index 36c064e7..5effcb5d 100644
--- a/price/main.go
+++ b/price/main.go
@@ -13,6 +13,7 @@ import (
 	"regexp"
 	"strconv"
 
+	"github.com/stellar/go/support/ordered"
 	"github.com/stellar/go/xdr"
 )
 
@@ -163,7 +164,7 @@ func ConvertToBuyingUnits(sellingOfferAmount int64, sellingUnitsNeeded int64, pr
 	}
 
 	// pathPaymentAmountBought
-	result = min(result, sellingUnitsNeeded)
+	result = ordered.Min(result, sellingUnitsNeeded)
 	sellingUnitsExtracted := result
 
 	// pathPaymentAmountSold
@@ -218,11 +219,3 @@ func mulFractionRoundUp(x int64, n int64, d int64) (int64, error) {
 
 	return int64(q), nil
 }
-
-// min impl for int64
-func min(x int64, y int64) int64 {
-	if x <= y {
-		return x
-	}
-	return y
-}
diff --git a/services/horizon/docker/verify-range/dependencies b/services/horizon/docker/verify-range/dependencies
index fda36fdf..fa622f9d 100644
--- a/services/horizon/docker/verify-range/dependencies
+++ b/services/horizon/docker/verify-range/dependencies
@@ -11,8 +11,8 @@ echo "deb https://apt.stellar.org $(lsb_release -cs) stable" | sudo tee -a /etc/
 apt-get update
 apt-get install -y stellar-core=${STELLAR_CORE_VERSION}
 
-wget -q https://dl.google.com/go/go1.17.linux-amd64.tar.gz
-tar -C /usr/local -xzf go1.17.linux-amd64.tar.gz
+wget -q https://dl.google.com/go/go1.18.linux-amd64.tar.gz
+tar -C /usr/local -xzf go1.18.linux-amd64.tar.gz
 
 git clone https://github.com/stellar/go.git stellar-go
 cd stellar-go
diff --git a/services/horizon/internal/db2/history/ledger.go b/services/horizon/internal/db2/history/ledger.go
index 5abf8eac..7d367a84 100644
--- a/services/horizon/internal/db2/history/ledger.go
+++ b/services/horizon/internal/db2/history/ledger.go
@@ -11,6 +11,7 @@ import (
 	"github.com/guregu/null"
 	"github.com/stellar/go/services/horizon/internal/db2"
 	"github.com/stellar/go/support/errors"
+	"github.com/stellar/go/support/ordered"
 	"github.com/stellar/go/toid"
 	"github.com/stellar/go/xdr"
 )
@@ -153,20 +154,6 @@ func (q *Q) GetLedgerGaps(ctx context.Context) ([]LedgerRange, error) {
 	return gaps, nil
 }
 
-func max(a, b uint32) uint32 {
-	if a > b {
-		return a
-	}
-	return b
-}
-
-func min(a, b uint32) uint32 {
-	if a > b {
-		return b
-	}
-	return a
-}
-
 // GetLedgerGapsInRange obtains ingestion gaps in the history_ledgers table within the given range.
 // Returns the gaps and error.
 func (q *Q) GetLedgerGapsInRange(ctx context.Context, start, end uint32) ([]LedgerRange, error) {
@@ -189,7 +176,7 @@ func (q *Q) GetLedgerGapsInRange(ctx context.Context, start, end uint32) ([]Ledg
 	if start < oldestLedger {
 		result = append(result, LedgerRange{
 			StartSequence: start,
-			EndSequence:   min(end, oldestLedger-1),
+			EndSequence:   ordered.Min(end, oldestLedger-1),
 		})
 	}
 	if end <= oldestLedger {
@@ -209,14 +196,14 @@ func (q *Q) GetLedgerGapsInRange(ctx context.Context, start, end uint32) ([]Ledg
 			break
 		}
 		result = append(result, LedgerRange{
-			StartSequence: max(gap.StartSequence, start),
-			EndSequence:   min(gap.EndSequence, end),
+			StartSequence: ordered.Max(gap.StartSequence, start),
+			EndSequence:   ordered.Min(gap.EndSequence, end),
 		})
 	}
 
 	if latestLedger < end {
 		result = append(result, LedgerRange{
-			StartSequence: max(latestLedger+1, start),
+			StartSequence: ordered.Max(latestLedger+1, start),
 			EndSequence:   end,
 		})
 	}
diff --git a/services/horizon/internal/db2/history/ledger_cache.go b/services/horizon/internal/db2/history/ledger_cache.go
index 3318048e..a88ce5c7 100644
--- a/services/horizon/internal/db2/history/ledger_cache.go
+++ b/services/horizon/internal/db2/history/ledger_cache.go
@@ -3,6 +3,7 @@ package history
 import (
 	"context"
 
+	"github.com/stellar/go/support/collections/set"
 	"github.com/stellar/go/support/errors"
 )
 
@@ -11,10 +12,10 @@ func (lc *LedgerCache) Queue(seq int32) {
 	lc.lock.Lock()
 
 	if lc.queued == nil {
-		lc.queued = map[int32]struct{}{}
+		lc.queued = set.Set[int32]{}
 	}
 
-	lc.queued[seq] = struct{}{}
+	lc.queued.Add(seq)
 	lc.lock.Unlock()
 }
 
diff --git a/services/horizon/internal/db2/history/main.go b/services/horizon/internal/db2/history/main.go
index 05126f08..10eb4232 100644
--- a/services/horizon/internal/db2/history/main.go
+++ b/services/horizon/internal/db2/history/main.go
@@ -19,6 +19,7 @@ import (
 	"github.com/lib/pq"
 
 	"github.com/stellar/go/services/horizon/internal/db2"
+	"github.com/stellar/go/support/collections/set"
 	"github.com/stellar/go/support/db"
 	"github.com/stellar/go/support/errors"
 	strtime "github.com/stellar/go/support/time"
@@ -261,7 +262,7 @@ type IngestionQ interface {
 	NewTradeBatchInsertBuilder(maxBatchSize int) TradeBatchInsertBuilder
 	RebuildTradeAggregationTimes(ctx context.Context, from, to strtime.Millis, roundingSlippageFilter int) error
 	RebuildTradeAggregationBuckets(ctx context.Context, fromLedger, toLedger uint32, roundingSlippageFilter int) error
-	ReapLookupTables(ctx context.Context, offsets map[string]int64) (map[string]int64, error)
+	ReapLookupTables(ctx context.Context, offsets map[string]int64) (map[string]int64, map[string]int64, error)
 	CreateAssets(ctx context.Context, assets []xdr.Asset, batchSize int) (map[string]Asset, error)
 	QTransactions
 	QTrustLines
@@ -580,7 +581,7 @@ type LedgerCache struct {
 	Records map[int32]Ledger
 
 	lock   sync.Mutex
-	queued map[int32]struct{}
+	queued set.Set[int32]
 }
 
 type LedgerRange struct {
@@ -838,18 +839,46 @@ type tableObjectFieldPair struct {
 // which aren't used (orphaned), i.e. history entries for them were reaped.
 // This method must be executed inside ingestion transaction. Otherwise it may
 // create invalid state in lookup and history tables.
-func (q Q) ReapLookupTables(ctx context.Context, offsets map[string]int64) (map[string]int64, error) {
+func (q Q) ReapLookupTables(ctx context.Context, offsets map[string]int64) (
+	map[string]int64, // deleted rows count
+	map[string]int64, // new offsets
+	error,
+) {
 	if q.GetTx() == nil {
-		return nil, errors.New("cannot be called outside of an ingestion transaction")
+		return nil, nil, errors.New("cannot be called outside of an ingestion transaction")
 	}
 
-	const batchSize = 10000
+	const batchSize = 1000
+
+	deletedCount := make(map[string]int64)
 
 	if offsets == nil {
 		offsets = make(map[string]int64)
 	}
 
 	for table, historyTables := range map[string][]tableObjectFieldPair{
+		"history_accounts": {
+			{
+				name:        "history_effects",
+				objectField: "history_account_id",
+			},
+			{
+				name:        "history_operation_participants",
+				objectField: "history_account_id",
+			},
+			{
+				name:        "history_trades",
+				objectField: "base_account_id",
+			},
+			{
+				name:        "history_trades",
+				objectField: "counter_account_id",
+			},
+			{
+				name:        "history_transaction_participants",
+				objectField: "history_account_id",
+			},
+		},
 		"history_claimable_balances": {
 			{
 				name:        "history_operation_claimable_balances",
@@ -873,31 +902,37 @@ func (q Q) ReapLookupTables(ctx context.Context, offsets map[string]int64) (map[
 	} {
 		query, err := constructReapLookupTablesQuery(table, historyTables, batchSize, offsets[table])
 		if err != nil {
-			return nil, errors.Wrap(err, "error constructing a query")
+			return nil, nil, errors.Wrap(err, "error constructing a query")
+		}
+
+		// Find new offset before removing the rows
+		var newOffset int64
+		err = q.GetRaw(ctx, &newOffset, fmt.Sprintf("SELECT id FROM %s where id >= %d limit 1 offset %d", table, offsets[table], batchSize))
+		if err != nil {
+			if q.NoRows(err) {
+				newOffset = 0
+			} else {
+				return nil, nil, err
+			}
 		}
 
-		_, err = q.ExecRaw(
+		res, err := q.ExecRaw(
 			context.WithValue(ctx, &db.QueryTypeContextKey, db.DeleteQueryType),
 			query,
 		)
 		if err != nil {
-			return nil, errors.Wrapf(err, "error running query: %s", query)
+			return nil, nil, errors.Wrapf(err, "error running query: %s", query)
 		}
 
-		offsets[table] += batchSize
-
-		// Check if offset exceeds table size and then reset it
-		var count int64
-		err = q.GetRaw(ctx, &count, fmt.Sprintf("SELECT COUNT(*) FROM %s", table))
+		rows, err := res.RowsAffected()
 		if err != nil {
-			return nil, err
+			return nil, nil, errors.Wrapf(err, "error running RowsAffected after query: %s", query)
 		}
 
-		if offsets[table] > count {
-			offsets[table] = 0
-		}
+		deletedCount[table] = rows
+		offsets[table] = newOffset
 	}
-	return offsets, nil
+	return deletedCount, offsets, nil
 }
 
 // constructReapLookupTablesQuery creates a query like (using history_claimable_balances
@@ -907,13 +942,13 @@ func (q Q) ReapLookupTables(ctx context.Context, offsets map[string]int64) (map[
 //
 //	 (select id from
 //	   (select id,
-//			(select count(*) from history_operation_claimable_balances
-//			 where history_claimable_balance_id = hcb.id) as c1,
-//			(select count(*) from history_transaction_claimable_balances
-//			 where history_claimable_balance_id = hcb.id) as c2,
+//			(select 1 from history_operation_claimable_balances
+//			 where history_claimable_balance_id = hcb.id limit 1) as c1,
+//			(select 1 from history_transaction_claimable_balances
+//			 where history_claimable_balance_id = hcb.id limit 1) as c2,
 //			1 as cx,
-//	     from history_claimable_balances hcb order by id limit 100 offset 1000)
-//	 as sub where c1 = 0 and c2 = 0 and 1=1);
+//	     from history_claimable_balances hcb where id > 1000 order by id limit 100)
+//	 as sub where c1 IS NULL and c2 IS NULL and 1=1);
 //
 // In short it checks the 100 rows omiting 1000 row of history_claimable_balances
 // and counts occurences of each row in corresponding history tables.
@@ -936,7 +971,7 @@ func constructReapLookupTablesQuery(table string, historyTables []tableObjectFie
 	for i, historyTable := range historyTables {
 		_, err = fmt.Fprintf(
 			&sb,
-			`(select count(*) from %s where %s = hcb.id) as c%d, `,
+			`(select 1 from %s where %s = hcb.id limit 1) as c%d, `,
 			historyTable.name,
 			historyTable.objectField,
 			i,
@@ -946,13 +981,13 @@ func constructReapLookupTablesQuery(table string, historyTables []tableObjectFie
 		}
 	}
 
-	_, err = fmt.Fprintf(&sb, "1 as cx from %s hcb order by id limit %d offset %d) as sub where ", table, batchSize, offset)
+	_, err = fmt.Fprintf(&sb, "1 as cx from %s hcb where id >= %d order by id limit %d) as sub where ", table, offset, batchSize)
 	if err != nil {
 		return "", err
 	}
 
 	for i := range historyTables {
-		_, err = fmt.Fprintf(&sb, "c%d = 0 and ", i)
+		_, err = fmt.Fprintf(&sb, "c%d IS NULL and ", i)
 		if err != nil {
 			return "", err
 		}
diff --git a/services/horizon/internal/db2/schema/bindata.go b/services/horizon/internal/db2/schema/bindata.go
index 91b28f93..1d6c2b7b 100644
--- a/services/horizon/internal/db2/schema/bindata.go
+++ b/services/horizon/internal/db2/schema/bindata.go
@@ -54,6 +54,7 @@
 // migrations/56_txsub_read_only.sql (784B)
 // migrations/57_trade_aggregation_autovac.sql (282B)
 // migrations/58_add_index_by_id_optimization.sql (868B)
+// migrations/59_remove_foreign_key_constraints.sql (981B)
 // migrations/5_create_trades_table.sql (1.1kB)
 // migrations/6_create_assets_table.sql (366B)
 // migrations/7_modify_trades_table.sql (2.303kB)
@@ -1208,6 +1209,26 @@ func migrations58_add_index_by_id_optimizationSql() (*asset, error) {
 	return a, nil
 }
 
+var _migrations59_remove_foreign_key_constraintsSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xbc\x92\xd1\x4a\xc3\x30\x18\x85\xef\xf3\x14\xe7\x72\x43\xf6\x04\xbb\x8a\xcb\x3f\x19\x96\x44\xb2\x78\xb1\xab\x10\xd7\xa8\x41\x5c\x25\x89\xc8\xde\x5e\x94\x52\xda\xda\xa2\x42\xd8\xf5\x39\x1c\xbe\x7c\xf9\x57\x2b\x5c\xbd\x86\xa7\xe8\xb2\xc7\xfd\x1b\x63\xbc\x32\xa4\x61\xf8\x75\x45\x50\xb2\x3a\xe0\x39\xa4\xdc\xc4\xb3\xcd\xd1\xd5\x3e\x41\x68\x75\x87\x8d\x92\x7b\xa3\xf9\x4e\x9a\x51\x6c\x1f\x5c\xf2\xd6\x1d\x8f\xcd\xfb\x29\xdb\x50\xdb\xc7\x17\x7f\x5e\x97\x19\x4d\xc9\x97\x9b\xfc\x06\xf4\xb1\x38\x6a\xb7\x3b\xa4\x65\x7d\xcd\xa2\xf9\x38\xfd\x2a\x9a\x01\x00\x17\xe2\x9f\xae\xb1\x55\x9a\x76\x37\x12\xb7\x74\xc0\x62\xd4\x58\x42\xd3\x96\x34\xc9\x0d\xed\xbb\xb1\x36\x4e\x8b\x50\x2f\xd7\xe5\xb0\xfa\xef\x9f\x82\x6a\xf3\x69\xa4\xaf\xb0\x1c\xd0\xcc\x5f\x0f\xa9\x7e\x96\x2e\x63\x6b\xf2\x60\x66\xd0\xfe\xe8\x0c\xec\x33\x00\x00\xff\xff\xd7\x39\x62\x6b\xd5\x03\x00\x00")
+
+func migrations59_remove_foreign_key_constraintsSqlBytes() ([]byte, error) {
+	return bindataRead(
+		_migrations59_remove_foreign_key_constraintsSql,
+		"migrations/59_remove_foreign_key_constraints.sql",
+	)
+}
+
+func migrations59_remove_foreign_key_constraintsSql() (*asset, error) {
+	bytes, err := migrations59_remove_foreign_key_constraintsSqlBytes()
+	if err != nil {
+		return nil, err
+	}
+
+	info := bindataFileInfo{name: "migrations/59_remove_foreign_key_constraints.sql", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)}
+	a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x8a, 0xef, 0x36, 0x64, 0x26, 0xdf, 0xec, 0x49, 0x62, 0x47, 0xca, 0x8c, 0x1e, 0x29, 0x37, 0x7c, 0x9f, 0xc8, 0x47, 0x94, 0x28, 0xc9, 0xdb, 0x3e, 0x95, 0xdb, 0x8d, 0xc8, 0xf0, 0x38, 0xf1, 0x8c}}
+	return a, nil
+}
+
 var _migrations5_create_trades_tableSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x94\x94\x51\x6f\xaa\x40\x10\x85\xdf\xf9\x15\x13\x9f\x30\x17\x93\x7b\x6f\x5a\x5f\x4c\x9a\x58\x25\xad\xa9\xc1\xd6\x4a\xd2\x37\xb2\xb0\x23\x6c\xa2\x2c\x99\x1d\xda\xf0\xef\x1b\x68\x69\x10\x57\xad\xaf\x9c\x39\x67\x38\xbb\x5f\x76\x34\x82\x3f\x7b\x95\x92\x60\x84\xb0\x70\x66\x6b\x7f\xba\xf1\x61\x33\xbd\x5f\xfa\x90\x29\xc3\x9a\xaa\x88\x49\x48\x34\xe0\x3a\x00\xf0\xf3\x51\x17\x48\x82\x95\xce\x23\x25\x21\x56\xa9\xca\x19\x82\xd5\x06\x82\x70\xb9\xf4\x9a\xc9\x81\x26\x89\x34\x00\x95\x33\xa6\x48\x1d\xb5\x91\xf5\x76\x8b\x64\x35\x37\xb2\xc1\xdd\xee\x84\x5e\xcb\x71\x59\x9d\x75\xeb\x9d\x8c\x84\x31\xc8\x11\x57\x05\x42\x92\x09\x12\x09\x23\xc1\xbb\xa0\x4a\xe5\xa9\x3b\xbe\x19\xf6\x22\x3b\x1e\x65\x4c\x89\x64\x71\xdd\x8e\xcf\xb8\x12\x2d\x6d\x9b\xfe\xfd\xb7\x7b\xf6\xba\xcc\xb9\xff\xff\x30\x7b\xf4\x67\x4f\xe0\x76\x47\xee\xe0\xef\xf0\xbb\x57\xac\xcb\x34\xe3\x6b\x9b\x1d\xb8\xae\xe8\x76\xe0\xfb\x75\xbb\xd6\x75\xb6\xdf\xe1\x50\xdd\xd0\x19\x4e\x9c\x96\xbf\x30\x58\xbc\x84\x3e\x2c\x82\xb9\xff\x06\x19\x93\x8c\x0a\x25\x61\x15\xf4\x91\x0c\x5f\x17\xc1\x03\xc4\x4c\x88\xe0\xda\xc8\xf4\x5a\x0a\x3b\xe1\x9d\xd4\xb8\x8a\x1a\x0c\x2f\x45\xb7\xac\xda\x52\xea\x90\xfa\xb6\x2e\x65\xf4\x90\xf4\xfa\xe4\x78\xc7\x00\x9e\x5a\xf7\x75\x78\x97\x16\x1e\xb1\xe2\x1d\x5f\xa8\x67\x63\xa3\x5e\xdb\x7d\x17\xe6\xfa\x23\x77\xe6\xeb\xd5\xb3\xfd\x5d\x48\x84\x49\x84\xc4\x89\xf3\x19\x00\x00\xff\xff\x79\x87\x24\x6b\x4c\x04\x00\x00")
 
 func migrations5_create_trades_tableSqlBytes() ([]byte, error) {
@@ -1473,6 +1494,7 @@ var _bindata = map[string]func() (*asset, error){
 	"migrations/56_txsub_read_only.sql":                                  migrations56_txsub_read_onlySql,
 	"migrations/57_trade_aggregation_autovac.sql":                        migrations57_trade_aggregation_autovacSql,
 	"migrations/58_add_index_by_id_optimization.sql":                     migrations58_add_index_by_id_optimizationSql,
+	"migrations/59_remove_foreign_key_constraints.sql":                   migrations59_remove_foreign_key_constraintsSql,
 	"migrations/5_create_trades_table.sql":                               migrations5_create_trades_tableSql,
 	"migrations/6_create_assets_table.sql":                               migrations6_create_assets_tableSql,
 	"migrations/7_modify_trades_table.sql":                               migrations7_modify_trades_tableSql,
@@ -1579,6 +1601,7 @@ var _bintree = &bintree{nil, map[string]*bintree{
 		"56_txsub_read_only.sql":                                  &bintree{migrations56_txsub_read_onlySql, map[string]*bintree{}},
 		"57_trade_aggregation_autovac.sql":                        &bintree{migrations57_trade_aggregation_autovacSql, map[string]*bintree{}},
 		"58_add_index_by_id_optimization.sql":                     &bintree{migrations58_add_index_by_id_optimizationSql, map[string]*bintree{}},
+		"59_remove_foreign_key_constraints.sql":                   &bintree{migrations59_remove_foreign_key_constraintsSql, map[string]*bintree{}},
 		"5_create_trades_table.sql":                               &bintree{migrations5_create_trades_tableSql, map[string]*bintree{}},
 		"6_create_assets_table.sql":                               &bintree{migrations6_create_assets_tableSql, map[string]*bintree{}},
 		"7_modify_trades_table.sql":                               &bintree{migrations7_modify_trades_tableSql, map[string]*bintree{}},
diff --git a/services/horizon/internal/db2/schema/migrations/59_remove_foreign_key_constraints.sql b/services/horizon/internal/db2/schema/migrations/59_remove_foreign_key_constraints.sql
new file mode 100644
index 00000000..7faebe98
--- /dev/null
+++ b/services/horizon/internal/db2/schema/migrations/59_remove_foreign_key_constraints.sql
@@ -0,0 +1,20 @@
+-- +migrate Up
+
+ALTER TABLE ONLY history_trades DROP CONSTRAINT history_trades_base_account_id_fkey;
+ALTER TABLE ONLY history_trades DROP CONSTRAINT history_trades_base_asset_id_fkey;
+ALTER TABLE ONLY history_trades DROP CONSTRAINT history_trades_counter_account_id_fkey;
+ALTER TABLE ONLY history_trades DROP CONSTRAINT history_trades_counter_asset_id_fkey;
+
+-- +migrate Down
+
+ALTER TABLE ONLY history_trades
+    ADD CONSTRAINT history_trades_base_account_id_fkey FOREIGN KEY (base_account_id) REFERENCES history_accounts(id);
+
+ALTER TABLE ONLY history_trades
+    ADD CONSTRAINT history_trades_base_asset_id_fkey FOREIGN KEY (base_asset_id) REFERENCES history_assets(id);
+
+ALTER TABLE ONLY history_trades
+    ADD CONSTRAINT history_trades_counter_account_id_fkey FOREIGN KEY (counter_account_id) REFERENCES history_accounts(id);
+
+ALTER TABLE ONLY history_trades
+    ADD CONSTRAINT history_trades_counter_asset_id_fkey FOREIGN KEY (counter_asset_id) REFERENCES history_assets(id); 
diff --git a/services/horizon/internal/ingest/filters/account.go b/services/horizon/internal/ingest/filters/account.go
index 601f67c8..b2372ae4 100644
--- a/services/horizon/internal/ingest/filters/account.go
+++ b/services/horizon/internal/ingest/filters/account.go
@@ -6,10 +6,11 @@ import (
 	"github.com/stellar/go/ingest"
 	"github.com/stellar/go/services/horizon/internal/db2/history"
 	"github.com/stellar/go/services/horizon/internal/ingest/processors"
+	"github.com/stellar/go/support/collections/set"
 )
 
 type accountFilter struct {
-	whitelistedAccountsSet map[string]struct{}
+	whitelistedAccountsSet set.Set[string]
 	lastModified           int64
 	enabled                bool
 }
@@ -21,7 +22,7 @@ type AccountFilter interface {
 
 func NewAccountFilter() AccountFilter {
 	return &accountFilter{
-		whitelistedAccountsSet: map[string]struct{}{},
+		whitelistedAccountsSet: set.Set[string]{},
 	}
 }
 
@@ -32,7 +33,7 @@ func (filter *accountFilter) RefreshAccountFilter(filterConfig *history.AccountF
 		logger.Infof("New Account Filter config detected, reloading new config %v ", *filterConfig)
 
 		filter.enabled = filterConfig.Enabled
-		filter.whitelistedAccountsSet = listToMap(filterConfig.Whitelist)
+		filter.whitelistedAccountsSet = listToSet(filterConfig.Whitelist)
 		filter.lastModified = filterConfig.LastModified
 	}
 
@@ -53,7 +54,7 @@ func (f *accountFilter) FilterTransaction(ctx context.Context, transaction inges
 	// NOTE: this assumes that the participant list has a small memory footprint
 	//       otherwise, we should be doing the filtering on the DB side
 	for _, p := range participants {
-		if _, ok := f.whitelistedAccountsSet[p.Address()]; ok {
+		if f.whitelistedAccountsSet.Contains(p.Address()) {
 			return true, nil
 		}
 	}
diff --git a/services/horizon/internal/ingest/filters/asset.go b/services/horizon/internal/ingest/filters/asset.go
index f116eacb..ee7a44de 100644
--- a/services/horizon/internal/ingest/filters/asset.go
+++ b/services/horizon/internal/ingest/filters/asset.go
@@ -3,12 +3,12 @@ package filters
 import (
 	"context"
 
+	"github.com/stellar/go/ingest"
 	"github.com/stellar/go/services/horizon/internal/db2/history"
 	"github.com/stellar/go/services/horizon/internal/ingest/processors"
+	"github.com/stellar/go/support/collections/set"
 	"github.com/stellar/go/support/log"
 	"github.com/stellar/go/xdr"
-
-	"github.com/stellar/go/ingest"
 )
 
 var (
@@ -18,7 +18,7 @@ var (
 )
 
 type assetFilter struct {
-	canonicalAssetsLookup map[string]struct{}
+	canonicalAssetsLookup set.Set[string]
 	lastModified          int64
 	enabled               bool
 }
@@ -30,7 +30,7 @@ type AssetFilter interface {
 
 func NewAssetFilter() AssetFilter {
 	return &assetFilter{
-		canonicalAssetsLookup: map[string]struct{}{},
+		canonicalAssetsLookup: set.Set[string]{},
 	}
 }
 
@@ -40,7 +40,7 @@ func (filter *assetFilter) RefreshAssetFilter(filterConfig *history.AssetFilterC
 	if filterConfig.LastModified > filter.lastModified {
 		logger.Infof("New Asset Filter config detected, reloading new config %v ", *filterConfig)
 		filter.enabled = filterConfig.Enabled
-		filter.canonicalAssetsLookup = listToMap(filterConfig.Whitelist)
+		filter.canonicalAssetsLookup = listToSet(filterConfig.Whitelist)
 		filter.lastModified = filterConfig.LastModified
 	}
 
@@ -130,14 +130,13 @@ func (f assetFilter) filterChangeTrustMatched(operation xdr.Operation) bool {
 }
 
 func (f *assetFilter) assetMatchedFilter(asset *xdr.Asset) bool {
-	_, found := f.canonicalAssetsLookup[asset.StringCanonical()]
-	return found
+	return f.canonicalAssetsLookup.Contains(asset.StringCanonical())
 }
 
-func listToMap(list []string) map[string]struct{} {
-	set := make(map[string]struct{}, len(list))
+func listToSet(list []string) set.Set[string] {
+	set := set.NewSet[string](len(list))
 	for i := 0; i < len(list); i++ {
-		set[list[i]] = struct{}{}
+		set.Add(list[i])
 	}
 	return set
 }
diff --git a/services/horizon/internal/ingest/main.go b/services/horizon/internal/ingest/main.go
index eb071ac6..64fb710e 100644
--- a/services/horizon/internal/ingest/main.go
+++ b/services/horizon/internal/ingest/main.go
@@ -86,6 +86,7 @@ type Config struct {
 	HistoryArchiveURL string
 
 	DisableStateVerification     bool
+	EnableReapLookupTables       bool
 	EnableExtendedLogLedgerStats bool
 
 	ReingestEnabled             bool
@@ -682,6 +683,10 @@ func (s *system) maybeVerifyState(lastIngestedLedger uint32) {
 }
 
 func (s *system) maybeReapLookupTables(lastIngestedLedger uint32) {
+	if !s.config.EnableReapLookupTables {
+		return
+	}
+
 	// Check if lastIngestedLedger is the last one available in the backend
 	sequence, err := s.ledgerBackend.GetLatestLedgerSequence(s.ctx)
 	if err != nil {
@@ -714,7 +719,7 @@ func (s *system) maybeReapLookupTables(lastIngestedLedger uint32) {
 	defer cancel()
 
 	reapStart := time.Now()
-	newOffsets, err := s.historyQ.ReapLookupTables(ctx, s.reapOffsets)
+	deletedCount, newOffsets, err := s.historyQ.ReapLookupTables(ctx, s.reapOffsets)
 	if err != nil {
 		log.WithField("err", err).Warn("Error reaping lookup tables")
 		return
@@ -726,6 +731,17 @@ func (s *system) maybeReapLookupTables(lastIngestedLedger uint32) {
 		return
 	}
 
+	totalDeleted := int64(0)
+	reapLog := log
+	for table, c := range deletedCount {
+		totalDeleted += c
+		reapLog = reapLog.WithField(table, c)
+	}
+
+	if totalDeleted > 0 {
+		reapLog.Info("Reaper deleted rows from lookup tables")
+	}
+
 	s.reapOffsets = newOffsets
 	reapDuration := time.Since(reapStart).Seconds()
 	s.Metrics().LedgerIngestionReapLookupTablesDuration.Observe(float64(reapDuration))
diff --git a/services/horizon/internal/ingest/processors/claimable_balances_transaction_processor.go b/services/horizon/internal/ingest/processors/claimable_balances_transaction_processor.go
index 2481687a..33c8bfd9 100644
--- a/services/horizon/internal/ingest/processors/claimable_balances_transaction_processor.go
+++ b/services/horizon/internal/ingest/processors/claimable_balances_transaction_processor.go
@@ -5,6 +5,7 @@ import (
 
 	"github.com/stellar/go/ingest"
 	"github.com/stellar/go/services/horizon/internal/db2/history"
+	set "github.com/stellar/go/support/collections/set"
 	"github.com/stellar/go/support/errors"
 	"github.com/stellar/go/toid"
 	"github.com/stellar/go/xdr"
@@ -12,22 +13,22 @@ import (
 
 type claimableBalance struct {
 	internalID     int64 // Bigint auto-generated by postgres
-	transactionSet map[int64]struct{}
-	operationSet   map[int64]struct{}
+	transactionSet set.Set[int64]
+	operationSet   set.Set[int64]
 }
 
 func (b *claimableBalance) addTransactionID(id int64) {
 	if b.transactionSet == nil {
-		b.transactionSet = map[int64]struct{}{}
+		b.transactionSet = set.Set[int64]{}
 	}
-	b.transactionSet[id] = struct{}{}
+	b.transactionSet.Add(id)
 }
 
 func (b *claimableBalance) addOperationID(id int64) {
 	if b.operationSet == nil {
-		b.operationSet = map[int64]struct{}{}
+		b.operationSet = set.Set[int64]{}
 	}
-	b.operationSet[id] = struct{}{}
+	b.operationSet.Add(id)
 }
 
 type ClaimableBalancesTransactionProcessor struct {
@@ -93,9 +94,9 @@ func claimableBalancesForTransaction(
 }
 
 func dedupeClaimableBalances(in []string) (out []string, err error) {
-	set := map[string]struct{}{}
+	set := set.Set[string]{}
 	for _, id := range in {
-		set[id] = struct{}{}
+		set.Add(id)
 	}
 
 	for id := range set {
diff --git a/services/horizon/internal/ingest/processors/liquidity_pools_transaction_processor.go b/services/horizon/internal/ingest/processors/liquidity_pools_transaction_processor.go
index 91064b89..38010ddb 100644
--- a/services/horizon/internal/ingest/processors/liquidity_pools_transaction_processor.go
+++ b/services/horizon/internal/ingest/processors/liquidity_pools_transaction_processor.go
@@ -5,6 +5,7 @@ import (
 
 	"github.com/stellar/go/ingest"
 	"github.com/stellar/go/services/horizon/internal/db2/history"
+	set "github.com/stellar/go/support/collections/set"
 	"github.com/stellar/go/support/errors"
 	"github.com/stellar/go/toid"
 	"github.com/stellar/go/xdr"
@@ -12,22 +13,22 @@ import (
 
 type liquidityPool struct {
 	internalID     int64 // Bigint auto-generated by postgres
-	transactionSet map[int64]struct{}
-	operationSet   map[int64]struct{}
+	transactionSet set.Set[int64]
+	operationSet   set.Set[int64]
 }
 
 func (b *liquidityPool) addTransactionID(id int64) {
 	if b.transactionSet == nil {
-		b.transactionSet = map[int64]struct{}{}
+		b.transactionSet = set.Set[int64]{}
 	}
-	b.transactionSet[id] = struct{}{}
+	b.transactionSet.Add(id)
 }
 
 func (b *liquidityPool) addOperationID(id int64) {
 	if b.operationSet == nil {
-		b.operationSet = map[int64]struct{}{}
+		b.operationSet = set.Set[int64]{}
 	}
-	b.operationSet[id] = struct{}{}
+	b.operationSet.Add(id)
 }
 
 type LiquidityPoolsTransactionProcessor struct {
@@ -93,9 +94,9 @@ func liquidityPoolsForTransaction(
 }
 
 func dedupeLiquidityPools(in []string) (out []string, err error) {
-	set := map[string]struct{}{}
+	set := set.Set[string]{}
 	for _, id := range in {
-		set[id] = struct{}{}
+		set.Add(id)
 	}
 
 	for id := range set {
diff --git a/services/horizon/internal/ingest/processors/participants_processor.go b/services/horizon/internal/ingest/processors/participants_processor.go
index 517c253d..d908f9ac 100644
--- a/services/horizon/internal/ingest/processors/participants_processor.go
+++ b/services/horizon/internal/ingest/processors/participants_processor.go
@@ -7,6 +7,7 @@ import (
 
 	"github.com/stellar/go/ingest"
 	"github.com/stellar/go/services/horizon/internal/db2/history"
+	set "github.com/stellar/go/support/collections/set"
 	"github.com/stellar/go/support/errors"
 	"github.com/stellar/go/toid"
 	"github.com/stellar/go/xdr"
@@ -30,22 +31,22 @@ func NewParticipantsProcessor(participantsQ history.QParticipants, sequence uint
 
 type participant struct {
 	accountID      int64
-	transactionSet map[int64]struct{}
-	operationSet   map[int64]struct{}
+	transactionSet set.Set[int64]
+	operationSet   set.Set[int64]
 }
 
 func (p *participant) addTransactionID(id int64) {
 	if p.transactionSet == nil {
-		p.transactionSet = map[int64]struct{}{}
+		p.transactionSet = set.Set[int64]{}
 	}
-	p.transactionSet[id] = struct{}{}
+	p.transactionSet.Add(id)
 }
 
 func (p *participant) addOperationID(id int64) {
 	if p.operationSet == nil {
-		p.operationSet = map[int64]struct{}{}
+		p.operationSet = set.Set[int64]{}
 	}
-	p.operationSet[id] = struct{}{}
+	p.operationSet.Add(id)
 }
 
 func (p *ParticipantsProcessor) loadAccountIDs(ctx context.Context, participantSet map[string]participant) error {
diff --git a/services/horizon/internal/init.go b/services/horizon/internal/init.go
index 3c548691..4150a1e2 100644
--- a/services/horizon/internal/init.go
+++ b/services/horizon/internal/init.go
@@ -2,10 +2,11 @@ package horizon
 
 import (
 	"context"
-	"github.com/stellar/go/services/horizon/internal/paths"
 	"net/http"
 	"runtime"
 
+	"github.com/stellar/go/services/horizon/internal/paths"
+
 	"github.com/getsentry/raven-go"
 	"github.com/prometheus/client_golang/prometheus"
 	"github.com/stellar/go/exp/orderbook"
@@ -116,6 +117,7 @@ func initIngester(app *App) {
 		RemoteCaptiveCoreURL:         app.config.RemoteCaptiveCoreURL,
 		EnableCaptiveCore:            app.config.EnableCaptiveCoreIngestion,
 		DisableStateVerification:     app.config.IngestDisableStateVerification,
+		EnableReapLookupTables:       app.config.HistoryRetentionCount > 0,
 		EnableExtendedLogLedgerStats: app.config.IngestEnableExtendedLogLedgerStats,
 		RoundingSlippageFilter:       app.config.RoundingSlippageFilter,
 		EnableIngestionFiltering:     app.config.EnableIngestionFiltering,
diff --git a/services/keystore/cmd/keystored/main.go b/services/keystore/cmd/keystored/main.go
index c5daf9d6..ceb930ff 100644
--- a/services/keystore/cmd/keystored/main.go
+++ b/services/keystore/cmd/keystored/main.go
@@ -18,6 +18,7 @@ import (
 	migrate "github.com/rubenv/sql-migrate"
 	"github.com/sirupsen/logrus"
 	"github.com/stellar/go/services/keystore"
+	"github.com/stellar/go/support/collections/set"
 	"github.com/stellar/go/support/log"
 
 	_ "github.com/lib/pq"
@@ -247,13 +248,13 @@ func getUnappliedMigrations(db *sql.DB) []string {
 		os.Exit(1)
 	}
 
-	unappliedMigrations := make(map[string]struct{})
+	unappliedMigrations := set.Set[string]{}
 	for _, m := range migrations {
-		unappliedMigrations[m.Id] = struct{}{}
+		unappliedMigrations.Add(m.Id)
 	}
 
 	for _, r := range records {
-		if _, ok := unappliedMigrations[r.Id]; !ok {
+		if unappliedMigrations.Contains(r.Id) {
 			fmt.Fprintf(os.Stdout, "Could not find migration file: %v\n", r.Id)
 			continue
 		}
diff --git a/support/collections/set/set.go b/support/collections/set/set.go
new file mode 100644
index 00000000..0cad14dc
--- /dev/null
+++ b/support/collections/set/set.go
@@ -0,0 +1,20 @@
+package set
+
+type Set[T comparable] map[T]struct{}
+
+func NewSet[T comparable](capacity int) Set[T] {
+	return make(map[T]struct{}, capacity)
+}
+
+func (set Set[T]) Add(item T) {
+	set[item] = struct{}{}
+}
+
+func (set Set[T]) Remove(item T) {
+	delete(set, item)
+}
+
+func (set Set[T]) Contains(item T) bool {
+	_, ok := set[item]
+	return ok
+}
diff --git a/support/ordered/math.go b/support/ordered/math.go
new file mode 100644
index 00000000..a07f7064
--- /dev/null
+++ b/support/ordered/math.go
@@ -0,0 +1,21 @@
+package ordered
+
+import (
+	"golang.org/x/exp/constraints"
+)
+
+// Min returns the smaller of the given items.
+func Min[T constraints.Ordered](a, b T) T {
+	if a <= b {
+		return a
+	}
+	return b
+}
+
+// Min returns the larger of the given items.
+func Max[T constraints.Ordered](a, b T) T {
+	if a > b {
+		return a
+	}
+	return b
+}
diff --git a/txnbuild/transaction.go b/txnbuild/transaction.go
index 5e4849db..ce95bba2 100644
--- a/txnbuild/transaction.go
+++ b/txnbuild/transaction.go
@@ -24,6 +24,7 @@ import (
 	"github.com/stellar/go/keypair"
 	"github.com/stellar/go/network"
 	"github.com/stellar/go/strkey"
+	"github.com/stellar/go/support/collections/set"
 	"github.com/stellar/go/support/errors"
 	"github.com/stellar/go/xdr"
 )
@@ -1302,7 +1303,7 @@ func VerifyChallengeTxSigners(challengeTx, serverAccountID, network, webAuthDoma
 	// Deduplicate the client signers and ensure the server is not included
 	// anywhere we check or output the list of signers.
 	clientSigners := []string{}
-	clientSignersSeen := map[string]struct{}{}
+	clientSignersSeen := set.Set[string]{}
 	for _, signer := range signers {
 		// Ignore the server signer if it is in the signers list. It's
 		// important when verifying signers of a challenge transaction that we
@@ -1313,7 +1314,7 @@ func VerifyChallengeTxSigners(challengeTx, serverAccountID, network, webAuthDoma
 			continue
 		}
 		// Deduplicate.
-		if _, seen := clientSignersSeen[signer]; seen {
+		if clientSignersSeen.Contains(signer) {
 			continue
 		}
 		// Ignore non-G... account/address signers.
@@ -1325,7 +1326,7 @@ func VerifyChallengeTxSigners(challengeTx, serverAccountID, network, webAuthDoma
 			continue
 		}
 		clientSigners = append(clientSigners, signer)
-		clientSignersSeen[signer] = struct{}{}
+		clientSignersSeen.Add(signer)
 	}
 
 	// Don't continue if none of the signers provided are in the final list.
@@ -1392,7 +1393,7 @@ func verifyTxSignatures(tx *Transaction, network string, signers ...string) ([]s
 
 	// find and verify signatures
 	signatureUsed := map[int]bool{}
-	signersFound := map[string]struct{}{}
+	signersFound := set.Set[string]{}
 	for _, signer := range signers {
 		kp, err := keypair.ParseAddress(signer)
 		if err != nil {
@@ -1409,7 +1410,7 @@ func verifyTxSignatures(tx *Transaction, network string, signers ...string) ([]s
 			err := kp.Verify(txHash[:], decSig.Signature)
 			if err == nil {
 				signatureUsed[i] = true
-				signersFound[signer] = struct{}{}
+				signersFound.Add(signer)
 				break
 			}
 		}
@@ -1417,7 +1418,7 @@ func verifyTxSignatures(tx *Transaction, network string, signers ...string) ([]s
 
 	signersFoundList := make([]string, 0, len(signersFound))
 	for _, signer := range signers {
-		if _, ok := signersFound[signer]; ok {
+		if signersFound.Contains(signer) {
 			signersFoundList = append(signersFoundList, signer)
 			delete(signersFound, signer)
 		}


Test patch:
diff --git a/services/horizon/internal/db2/history/main_test.go b/services/horizon/internal/db2/history/main_test.go
index 8123f4c7..792f9826 100644
--- a/services/horizon/internal/db2/history/main_test.go
+++ b/services/horizon/internal/db2/history/main_test.go
@@ -102,11 +102,11 @@ func TestConstructReapLookupTablesQuery(t *testing.T) {
 	assert.Equal(t,
 		"delete from history_accounts where id IN "+
 			"(select id from "+
-			"(select id, (select count(*) from history_effects where history_account_id = hcb.id) as c0, "+
-			"(select count(*) from history_operation_participants where history_account_id = hcb.id) as c1, "+
-			"(select count(*) from history_trades where base_account_id = hcb.id) as c2, "+
-			"(select count(*) from history_trades where counter_account_id = hcb.id) as c3, "+
-			"(select count(*) from history_transaction_participants where history_account_id = hcb.id) as c4, "+
-			"1 as cx from history_accounts hcb order by id limit 10 offset 0) as sub "+
-			"where c0 = 0 and c1 = 0 and c2 = 0 and c3 = 0 and c4 = 0 and 1=1);", query)
+			"(select id, (select 1 from history_effects where history_account_id = hcb.id limit 1) as c0, "+
+			"(select 1 from history_operation_participants where history_account_id = hcb.id limit 1) as c1, "+
+			"(select 1 from history_trades where base_account_id = hcb.id limit 1) as c2, "+
+			"(select 1 from history_trades where counter_account_id = hcb.id limit 1) as c3, "+
+			"(select 1 from history_transaction_participants where history_account_id = hcb.id limit 1) as c4, "+
+			"1 as cx from history_accounts hcb where id >= 0 order by id limit 10) as sub "+
+			"where c0 IS NULL and c1 IS NULL and c2 IS NULL and c3 IS NULL and c4 IS NULL and 1=1);", query)
 }
diff --git a/services/horizon/internal/db2/history/reap_test.go b/services/horizon/internal/db2/history/reap_test.go
index cdd16b53..5e62dc60 100644
--- a/services/horizon/internal/db2/history/reap_test.go
+++ b/services/horizon/internal/db2/history/reap_test.go
@@ -21,6 +21,7 @@ func TestReapLookupTables(t *testing.T) {
 
 	var (
 		prevLedgers, curLedgers                     int
+		prevAccounts, curAccounts                   int
 		prevClaimableBalances, curClaimableBalances int
 		prevLiquidityPools, curLiquidityPools       int
 	)
@@ -29,6 +30,8 @@ func TestReapLookupTables(t *testing.T) {
 	{
 		err := db.GetRaw(tt.Ctx, &prevLedgers, `SELECT COUNT(*) FROM history_ledgers`)
 		tt.Require.NoError(err)
+		err = db.GetRaw(tt.Ctx, &prevAccounts, `SELECT COUNT(*) FROM history_accounts`)
+		tt.Require.NoError(err)
 		err = db.GetRaw(tt.Ctx, &prevClaimableBalances, `SELECT COUNT(*) FROM history_claimable_balances`)
 		tt.Require.NoError(err)
 		err = db.GetRaw(tt.Ctx, &prevLiquidityPools, `SELECT COUNT(*) FROM history_liquidity_pools`)
@@ -45,7 +48,7 @@ func TestReapLookupTables(t *testing.T) {
 	err = q.Begin()
 	tt.Require.NoError(err)
 
-	newOffsets, err := q.ReapLookupTables(tt.Ctx, nil)
+	deletedCount, newOffsets, err := q.ReapLookupTables(tt.Ctx, nil)
 	tt.Require.NoError(err)
 
 	err = q.Commit()
@@ -55,6 +58,8 @@ func TestReapLookupTables(t *testing.T) {
 	{
 		err := db.GetRaw(tt.Ctx, &curLedgers, `SELECT COUNT(*) FROM history_ledgers`)
 		tt.Require.NoError(err)
+		err = db.GetRaw(tt.Ctx, &curAccounts, `SELECT COUNT(*) FROM history_accounts`)
+		tt.Require.NoError(err)
 		err = db.GetRaw(tt.Ctx, &curClaimableBalances, `SELECT COUNT(*) FROM history_claimable_balances`)
 		tt.Require.NoError(err)
 		err = db.GetRaw(tt.Ctx, &curLiquidityPools, `SELECT COUNT(*) FROM history_liquidity_pools`)
@@ -63,12 +68,21 @@ func TestReapLookupTables(t *testing.T) {
 
 	tt.Assert.Equal(61, prevLedgers, "prevLedgers")
 	tt.Assert.Equal(1, curLedgers, "curLedgers")
+
+	tt.Assert.Equal(25, prevAccounts, "prevAccounts")
+	tt.Assert.Equal(1, curAccounts, "curAccounts")
+	tt.Assert.Equal(int64(24), deletedCount["history_accounts"], `deletedCount["history_accounts"]`)
+
 	tt.Assert.Equal(1, prevClaimableBalances, "prevClaimableBalances")
 	tt.Assert.Equal(0, curClaimableBalances, "curClaimableBalances")
+	tt.Assert.Equal(int64(1), deletedCount["history_claimable_balances"], `deletedCount["history_claimable_balances"]`)
+
 	tt.Assert.Equal(1, prevLiquidityPools, "prevLiquidityPools")
 	tt.Assert.Equal(0, curLiquidityPools, "curLiquidityPools")
+	tt.Assert.Equal(int64(1), deletedCount["history_liquidity_pools"], `deletedCount["history_liquidity_pools"]`)
 
-	tt.Assert.Len(newOffsets, 2)
+	tt.Assert.Len(newOffsets, 3)
+	tt.Assert.Equal(int64(0), newOffsets["history_accounts"])
 	tt.Assert.Equal(int64(0), newOffsets["history_claimable_balances"])
 	tt.Assert.Equal(int64(0), newOffsets["history_liquidity_pools"])
 }
diff --git a/services/horizon/internal/ingest/main_test.go b/services/horizon/internal/ingest/main_test.go
index b5d0dfb7..7b930e80 100644
--- a/services/horizon/internal/ingest/main_test.go
+++ b/services/horizon/internal/ingest/main_test.go
@@ -374,12 +374,16 @@ func (m *mockDBQ) NewTradeBatchInsertBuilder(maxBatchSize int) history.TradeBatc
 	return args.Get(0).(history.TradeBatchInsertBuilder)
 }
 
-func (m *mockDBQ) ReapLookupTables(ctx context.Context, offsets map[string]int64) (map[string]int64, error) {
+func (m *mockDBQ) ReapLookupTables(ctx context.Context, offsets map[string]int64) (map[string]int64, map[string]int64, error) {
 	args := m.Called(ctx, offsets)
-	if args.Get(0) == nil {
-		return nil, args.Error(1)
+	var r1, r2 map[string]int64
+	if args.Get(0) != nil {
+		r1 = args.Get(0).(map[string]int64)
+	}
+	if args.Get(1) != nil {
+		r1 = args.Get(1).(map[string]int64)
 	}
-	return args.Get(0).(map[string]int64), args.Error(1)
+	return r1, r2, args.Error(2)
 }
 
 func (m *mockDBQ) RebuildTradeAggregationTimes(ctx context.Context, from, to strtime.Millis, roundingSlippageFilter int) error {
diff --git a/services/horizon/internal/ingest/resume_state_test.go b/services/horizon/internal/ingest/resume_state_test.go
index eadae11b..7f336a79 100644
--- a/services/horizon/internal/ingest/resume_state_test.go
+++ b/services/horizon/internal/ingest/resume_state_test.go
@@ -283,8 +283,6 @@ func (s *ResumeTestTestSuite) mockSuccessfulIngestion() {
 	).Return(nil).Once()
 
 	s.historyQ.On("GetExpStateInvalid", s.ctx).Return(false, nil).Once()
-	// Reap lookup tables
-	s.ledgerBackend.On("GetLatestLedgerSequence", s.ctx).Return(uint32(0), nil)
 }
 func (s *ResumeTestTestSuite) TestBumpIngestLedger() {
 	*s.ledgerBackend = ledgerbackend.MockDatabaseBackend{}
@@ -368,8 +366,46 @@ func (s *ResumeTestTestSuite) TestErrorSettingCursorIgnored() {
 
 	s.historyQ.On("GetExpStateInvalid", s.ctx).Return(false, nil).Once()
 	s.historyQ.On("RebuildTradeAggregationBuckets", s.ctx, uint32(101), uint32(101), 0).Return(nil).Once()
-	// Reap lookup tables
-	s.ledgerBackend.On("GetLatestLedgerSequence", s.ctx).Return(uint32(0), nil)
+
+	next, err := resumeState{latestSuccessfullyProcessedLedger: 100}.run(s.system)
+	s.Assert().NoError(err)
+	s.Assert().Equal(
+		transition{
+			node:          resumeState{latestSuccessfullyProcessedLedger: 101},
+			sleepDuration: 0,
+		},
+		next,
+	)
+}
+
+func (s *ResumeTestTestSuite) TestReapingObjectsDisabled() {
+	s.historyQ.On("Begin").Return(nil).Once()
+	s.historyQ.On("GetLastLedgerIngest", s.ctx).Return(uint32(100), nil).Once()
+	s.historyQ.On("GetIngestVersion", s.ctx).Return(CurrentVersion, nil).Once()
+	s.historyQ.On("GetLatestHistoryLedger", s.ctx).Return(uint32(100), nil)
+
+	s.runner.On("RunAllProcessorsOnLedger", mock.AnythingOfType("xdr.LedgerCloseMeta")).
+		Run(func(args mock.Arguments) {
+			meta := args.Get(0).(xdr.LedgerCloseMeta)
+			s.Assert().Equal(uint32(101), meta.LedgerSequence())
+		}).
+		Return(
+			ledgerStats{},
+			nil,
+		).Once()
+	s.historyQ.On("UpdateLastLedgerIngest", s.ctx, uint32(101)).Return(nil).Once()
+	s.historyQ.On("Commit").Return(nil).Once()
+
+	s.stellarCoreClient.On(
+		"SetCursor",
+		mock.AnythingOfType("*context.timerCtx"),
+		defaultCoreCursorName,
+		int32(101),
+	).Return(nil).Once()
+
+	s.historyQ.On("GetExpStateInvalid", s.ctx).Return(false, nil).Once()
+	s.historyQ.On("RebuildTradeAggregationBuckets", s.ctx, uint32(101), uint32(101), 0).Return(nil).Once()
+	// Reap lookup tables not executed
 
 	next, err := resumeState{latestSuccessfullyProcessedLedger: 100}.run(s.system)
 	s.Assert().NoError(err)
@@ -383,6 +419,10 @@ func (s *ResumeTestTestSuite) TestErrorSettingCursorIgnored() {
 }
 
 func (s *ResumeTestTestSuite) TestErrorReapingObjectsIgnored() {
+	s.system.config.EnableReapLookupTables = true
+	defer func() {
+		s.system.config.EnableReapLookupTables = false
+	}()
 	s.historyQ.On("Begin").Return(nil).Once()
 	s.historyQ.On("GetLastLedgerIngest", s.ctx).Return(uint32(100), nil).Once()
 	s.historyQ.On("GetIngestVersion", s.ctx).Return(CurrentVersion, nil).Once()
@@ -413,7 +453,7 @@ func (s *ResumeTestTestSuite) TestErrorReapingObjectsIgnored() {
 	s.ledgerBackend.On("GetLatestLedgerSequence", s.ctx).Return(uint32(101), nil)
 	s.historyQ.On("Begin").Return(nil).Once()
 	s.historyQ.On("GetLastLedgerIngest", s.ctx).Return(uint32(100), nil).Once()
-	s.historyQ.On("ReapLookupTables", mock.AnythingOfType("*context.timerCtx"), mock.Anything).Return(nil, errors.New("error reaping objects")).Once()
+	s.historyQ.On("ReapLookupTables", mock.AnythingOfType("*context.timerCtx"), mock.Anything).Return(nil, nil, errors.New("error reaping objects")).Once()
 	s.historyQ.On("Rollback").Return(nil).Once()
 
 	next, err := resumeState{latestSuccessfullyProcessedLedger: 100}.run(s.system)
diff --git a/services/horizon/internal/integration/db_test.go b/services/horizon/internal/integration/db_test.go
index 43899498..6f3c3316 100644
--- a/services/horizon/internal/integration/db_test.go
+++ b/services/horizon/internal/integration/db_test.go
@@ -9,21 +9,21 @@ import (
 	"testing"
 	"time"
 
-	"github.com/stellar/go/clients/horizonclient"
-	"github.com/stellar/go/keypair"
-	horizon "github.com/stellar/go/services/horizon/internal"
-	"github.com/stellar/go/services/horizon/internal/db2/history"
-	"github.com/stellar/go/xdr"
-
 	"github.com/stretchr/testify/assert"
 
+	"github.com/stellar/go/clients/horizonclient"
 	"github.com/stellar/go/historyarchive"
+	"github.com/stellar/go/keypair"
 	horizoncmd "github.com/stellar/go/services/horizon/cmd"
+	horizon "github.com/stellar/go/services/horizon/internal"
+	"github.com/stellar/go/services/horizon/internal/db2/history"
 	"github.com/stellar/go/services/horizon/internal/db2/schema"
 	"github.com/stellar/go/services/horizon/internal/test/integration"
+	"github.com/stellar/go/support/collections/set"
 	"github.com/stellar/go/support/db"
 	"github.com/stellar/go/support/db/dbtest"
 	"github.com/stellar/go/txnbuild"
+	"github.com/stellar/go/xdr"
 )
 
 func submitLiquidityPoolOps(itest *integration.Test, tt *assert.Assertions) (submittedOperations []txnbuild.Operation, lastLedger int32) {
@@ -417,9 +417,9 @@ func initializeDBIntegrationTest(t *testing.T) (itest *integration.Test, reached
 	submittedOps = append(submittedOps, ops...)
 
 	// Make sure all possible operations are covered by reingestion
-	allOpTypes := map[xdr.OperationType]struct{}{}
+	allOpTypes := set.Set[xdr.OperationType]{}
 	for typ := range xdr.OperationTypeToStringMap {
-		allOpTypes[xdr.OperationType(typ)] = struct{}{}
+		allOpTypes.Add(xdr.OperationType(typ))
 	}
 	// Inflation is not supported
 	delete(allOpTypes, xdr.OperationTypeInflation)
diff --git a/support/collections/set/set_test.go b/support/collections/set/set_test.go
new file mode 100644
index 00000000..798aeea7
--- /dev/null
+++ b/support/collections/set/set_test.go
@@ -0,0 +1,14 @@
+package set
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/require"
+)
+
+func TestSet(t *testing.T) {
+	s := Set[string]{}
+	s.Add("sanity")
+	require.True(t, s.Contains("sanity"))
+	require.False(t, s.Contains("check"))
+}
diff --git a/support/ordered/math_test.go b/support/ordered/math_test.go
new file mode 100644
index 00000000..5e5568de
--- /dev/null
+++ b/support/ordered/math_test.go
@@ -0,0 +1,27 @@
+package ordered
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/require"
+)
+
+func TestMinMax(t *testing.T) {
+	t.Run("int", func(tt *testing.T) {
+		a, b := -5, 10
+		require.Equal(tt, Min(a, b), a)
+		require.Equal(tt, Max(a, b), b)
+	})
+
+	t.Run("float", func(tt *testing.T) {
+		a, b := -5.0, 10.0
+		require.Equal(tt, Min(a, b), a)
+		require.Equal(tt, Max(a, b), b)
+	})
+
+	t.Run("unsigned", func(tt *testing.T) {
+		a, b := uint(5), uint(10)
+		require.Equal(tt, Min(a, b), a)
+		require.Equal(tt, Max(a, b), b)
+	})
+}