Game starts lagging when there's 500 projectiles

So, im trying to make a projectile system using raycast, however when there’s about 500 projectiles the game starts lagging.

Game Link

my module script is like this :

function module.CreateProjectile(target,character,direction,speed)
	local RayNew = Ray.new
	local tab = {}
	for i,v in pairs(character:GetDescendants()) do
		table.insert(tab,v)
	end
	local Center
	local projectile = workspace.Projectiles
	local findpartonray = workspace.FindPartOnRayWithIgnoreList
	local lastpos
	local hit,pos
	local t,dt
	if target.Parent ~= projectile then
		target.Parent = projectile
	end
	if target:IsA("BasePart") then
		Center = target.Position
		lastpos = target.Position
		repeat
			t,dt = game:GetService("RunService").Stepped:Wait()
			hit,pos = findpartonray(workspace,RayNew(lastpos,direction * speed * dt),tab)
			lastpos = pos
			target.Position = lastpos
			if hit ~= nil then
			if hit:IsA("BasePart") then
				print("Checking3")
				if hit.CanCollide == false then
					if not table.find(tab,hit) then
						table.insert(tab,hit)
					end
				end
			end
			end
			if hit ~= nil then
				if not hit:FindFirstAncestor(character.Name) and hit.Parent ~= projectile and hit.CanCollide == true then
					print("Checking1")
					break
				elseif not hit:FindFirstAncestor(character.Name) and hit.Parent == projectile and hit.CanCollide == true then
					print("Checking2")
					if hit:FindFirstChild("Owner") then
						if hit.Owner.Value ~= target.Owner.Value then
							break
						end
					end
				
				end
			end
		until target.Parent ~= projectile
		if target.Parent == projectile then
			target.Hit.Value = hit
		end
	elseif target:IsA("Model") then
		lastpos = target.PrimaryPart.Position
		Center = target.PrimaryPart.Position
		repeat
			t,dt = game:GetService("RunService").Stepped:Wait()
			hit,pos = findpartonray(workspace,RayNew(lastpos,direction * speed * dt),tab)
			lastpos = pos
			target:SetPrimaryPartCFrame(CFrame.new(lastpos))
			if hit ~= nil then
			if hit:IsA("BasePart") then
				print("Checking3")
				if hit.CanCollide == false then
					if not table.find(tab,hit) then
						table.insert(tab,hit)
					end
				end
			end
			end
			if hit ~= nil then
				if not hit:FindFirstAncestor(character.Name) and hit.Parent ~= projectile and hit.CanCollide == true then
					print("Checking1")
					break
				elseif not hit:FindFirstAncestor(character.Name) and hit.Parent == projectile and hit.CanCollide == true then
					print("Checking2")
					if hit:FindFirstChild("Owner") then
						if hit.Owner.Value ~= target.Owner.Value then
							break
						end
					end
				
				end
			end
		until target.Parent ~= projectile
		if target.Parent == projectile then
			target.Hit.Value = hit
		end
	end
end

the RemoteEvent Script is like this(i didnt add a Debris into the projectile because this is a Lag Test) :

replicated.FireBall.OnServerEvent:Connect(function(plr,mouse)
	local ball = SFX.FireBall:Clone()
	local function onCollide()
		ball:Destroy()
	end
	ball.Parent = workspace.Projectiles
	ball.CFrame = plr.Character.PrimaryPart.CFrame * CFrame.new(0,0,-2)
	ball.CFrame = CFrame.new(ball.Position,mouse.p)
	local sound = replicated.Sounds.GunSFX:Clone()
	sound.Parent = plr.Character.PrimaryPart
	sound:Play()
	game.Debris:AddItem(sound,1)
	--game.Debris:AddItem(ball,200)
	local hit,owner = projectilemodule.AddProjectileThings(ball,plr.Character)
	hit.Changed:Connect(function()
		onCollide()
	end)
	local character = plr.Character
	local direction = mouse.LookVector.Unit
	local speed = 800
	
	projectilemodule.CreateProjectile(ball,character,direction,speed)
	
end)