Pathfinding becomes extremely laggy the further away the starting node is from the end node

Hello.
I wrote a custom pathfinding system, it uses A* algorithm, and it can avoid obstacles with the shortest path correctly, but the only problem, the further away the starting node is from the ending node, the more laggy the system, that is because, the amount of nodes generated grows with the distance of the start node from the end node, and eventually, it causes either a stack overflow error, or completely crashes my studio. Here is the code (The Search() function handles pathfinding):

-->> Services
local runService = game:GetService("RunService");

-->> Functions & Events
local VISUALIZE = true;

local nodes = {}
function GenerateNode(x, z, y)
	local node = nil;
	if VISUALIZE then
		node = Instance.new("Part")
		node.Name = string.format("%i, %i, %i", x, y, z)
		node.Anchored = true
		node.CanCollide = false
		node.Position = Vector3.new(x, y, z)
		node.Size = Vector3.one/2
		node.Color = Color3.fromRGB(255, 255, 255)
		node.Material = Enum.Material.SmoothPlastic
		node.Parent = workspace
	end;
	
	local nodeTable = {Node = node, X = x, Z = z, H = nil, G = nil, Parent = nil}
	table.insert(nodes, nodeTable)
	return nodeTable
end;

function Search(startNode, endNode, height, filter)
	local openNodes = {}
	
	local currentNodeH = 0;
	local function NextNode(node)
		if VISUALIZE then
			node.Node.Color = Color3.fromRGB(255, 0, 0)
		end;
		
		local nodeH = currentNodeH + math.sqrt((node.X - startNode.X)^2 + (node.Z - startNode.Z)^2)
		node.H = nodeH
		currentNodeH = nodeH
		
		local nodeG = math.sqrt((node.X - endNode.X)^2 + (node.Z - endNode.Z)^2)
		node.G = nodeG
		
		local nodeF = nodeH + nodeG
		
		local unfoundAdjacentNodes = {
			{X = 1, Z = 0},
			{X = -1, Z = 0},
			{X = 0, Z = 1},
			{X = 0, Z = -1},
			{X = 1, Z = 1},
			{X = -1, Z = -1},
			{X = 1, Z = -1},
			{X = -1, Z = 1},
		};
		
		local adjacentNodes = {};
		for _, someNode in next, nodes do		
			local someNodeDistanceFromNode = math.sqrt((someNode.X - node.X)^2 + (someNode.Z - node.Z)^2)
			if someNodeDistanceFromNode <= math.sqrt(2) and not (someNode == node) then
				if not someNode.Parent then
					someNode.Parent = node
				end;
				
				local someNodeF;
				if someNode.G and someNode.H then
					someNodeF = someNode.G + someNode.H
				end
				
				local someNodeH, someNodeG;
				if not someNodeF then
					someNodeH = someNode.Parent.H + math.sqrt((someNode.Parent.X - someNode.X)^2 + (someNode.Parent.Z + someNode.Z)^2)
					someNode.H = someNodeH
					someNodeG = math.sqrt((endNode.X - someNode.X)^2 + (endNode.Z - someNode.Z)^2)
					someNode.G = someNodeG
				else
					someNodeH = someNode.H
					someNodeG = someNode.G
				end;
				
				if nodeH < someNodeH then
					-->> The path is cheaper.
					someNode.H = nodeH + math.sqrt((node.X - someNode.X)^2 + (node.Z - someNode.Z)^2)
					someNode.Parent = node
				end;
				
				someNodeF = someNodeH + someNodeG
				
				table.insert(adjacentNodes, someNode)
				for index, unfoundAdjacentNode in next, unfoundAdjacentNodes do
					if (unfoundAdjacentNode.X == node.X - someNode.X) and (unfoundAdjacentNode.Z == node.Z - someNode.Z) then
						table.remove(unfoundAdjacentNodes, index)
						break
					end;
				end;
				
				-->> Optimization: If more than 8 nodes have been declared adjacent, break the loop as all of them have certainly been found.
				if table.getn(adjacentNodes) >= 8 then
					break
				end;
			end;
		end;
		
		for _, unfoundAdjacentNode in next, unfoundAdjacentNodes do
			local unfoundAdjacentNodeTable = GenerateNode(node.X - unfoundAdjacentNode.X, node.Z - unfoundAdjacentNode.Z, height/2)
			unfoundAdjacentNodeTable.H = nodeH + math.sqrt((node.X - unfoundAdjacentNodeTable.X)^2 + (node.Z - unfoundAdjacentNodeTable.Z)^2)
			unfoundAdjacentNodeTable.G = math.sqrt((endNode.X - unfoundAdjacentNodeTable.X)^2 + (endNode.Z - unfoundAdjacentNodeTable.Z)^2)
			unfoundAdjacentNodeTable.Parent = node
			table.insert(adjacentNodes, unfoundAdjacentNodeTable)
		end;
				
		for _, adjacentNode in next, adjacentNodes do
			if not table.find(openNodes, adjacentNode) then
				local overlapParams = OverlapParams.new()
				overlapParams.FilterDescendantsInstances = {filter}
				local overlapping = workspace:GetPartBoundsInBox(CFrame.new(Vector3.new(adjacentNode.X, height/2, adjacentNode.Z)), Vector3.one, overlapParams)
				if table.getn(overlapping) <= (VISUALIZE and 1 or 0) then
					table.insert(openNodes, adjacentNode)
				end;
			end;
		end;
		
		table.clear(adjacentNodes)
		
		local smallestF = math.huge;
		local smallestNode = nil;
		
		for _, someNode in next, openNodes do
			local someNodeF = someNode.H + someNode.G
			if someNodeF <= smallestF then
				smallestF = someNodeF
				smallestNode = someNode
			end;
		end;
		
		if smallestNode.X == endNode.X and smallestNode.Z == endNode.Z then
			endNode = smallestNode
			print("Found end node!")
			return
		end;
		
		NextNode(smallestNode)
		
		startNode = node
	end;
	
	NextNode(startNode)
	
	local path = {}
	local function TracePath(node)
		if VISUALIZE then
			node.Node.Color = Color3.fromRGB(85, 255, 0)
		end;
		
		table.insert(path, Vector3.new(node.X, node.Y, node.Z))
		if node.X == startNode.X and node.Z == startNode.Z then
			print("Successfully calculated path.")
			return
		end;
		
		TracePath(node.Parent)
	end;
	
	task.wait(3)
	TracePath(endNode)
	
	local reversedPath = {}
	for i = table.getn(path), 1, -1 do
		table.insert(reversedPath, path[i])
	end;
	
	return reversedPath
end;

Edit: I couldn’t load videos to show the issue, but if the distance between the starting node and the ending nose is less than 20 studs it will be slightly or very laggy (depends on the distance), and above would either cause stack overflow or sometimes crash my studio.