Skip to content
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ The new policy can be used by setting the environment variable
[#2493](https://github.com/juanfont/headscale/pull/2493)
- If a OIDC provider doesn't include the `email_verified` claim in its ID
tokens, Headscale will attempt to get it from the UserInfo endpoint.
- Improve performance by only querying relevant nodes from the database for node updates
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please link the pr

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done


## 0.25.1 (2025-02-25)

Expand Down
34 changes: 25 additions & 9 deletions hscontrol/mapper/mapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package mapper
import (
"encoding/binary"
"encoding/json"
"errors"
"fmt"
"gorm.io/gorm"
"io/fs"
"net/url"
"os"
Expand Down Expand Up @@ -257,11 +259,6 @@ func (m *Mapper) PeerChangedResponse(
) ([]byte, error) {
resp := m.baseMapResponse()

peers, err := m.ListPeers(node.ID)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree this is not necessary, but instead of listing all, we should just list the one from the change map.

It would make more sense to make ListPeers(node NodeID, peerIDs ...NodeID) and only fetch the ones that are needed here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I opted against changing ListPeers for now. It is also used for full map responses, where I think all peers are needed (although I have not looked too deeply into this function yet). But I can of course change the naming scheme if you like something like ListPeersSubset better than ListNodesSubset.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think modifying both ListNodes and ListPeers to be able to take a optional list arg would be the best.

if err != nil {
return nil, err
}

var removedIDs []tailcfg.NodeID
var changedIDs []types.NodeID
for nodeID, nodeChanged := range changed {
Expand All @@ -273,13 +270,21 @@ func (m *Mapper) PeerChangedResponse(
}

changedNodes := make(types.Nodes, 0, len(changedIDs))
for _, peer := range peers {
if slices.Contains(changedIDs, peer.ID) {
changedNodes = append(changedNodes, peer)
for _, changedID := range changedIDs {
if changedID != node.ID {
changedNode, err := m.GetNode(changedID)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem with doing it this way, and not as mentioned above is that we dont get transactional consistency. In this case, nodes might change in between calls to GetNode. While this might not be an obvious problem, it might lead to incredibly hard to debug problems.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have reworked the code to use exactly one database query. I will also add a test to the database query function at least.

if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
continue
} else {
return nil, err
}
}
changedNodes = append(changedNodes, changedNode)
}
}

err = appendPeerChanges(
err := appendPeerChanges(
&resp,
false, // partial change
m.polMan,
Expand Down Expand Up @@ -496,6 +501,17 @@ func (m *Mapper) ListPeers(nodeID types.NodeID) (types.Nodes, error) {
return peers, nil
}

func (m *Mapper) GetNode(nodeID types.NodeID) (*types.Node, error) {
node, err := m.db.GetNodeByID(nodeID)
if err != nil {
return nil, err
}
online := m.notif.IsLikelyConnected(node.ID)
node.IsOnline = &online

return node, nil
}

func nodeMapToList(nodes map[uint64]*types.Node) types.Nodes {
ret := make(types.Nodes, 0)

Expand Down