Optimierung von Performance und Skalierbarkeit

- Assembly-Versionen auf 2.2.0.0 und 2.8.0.0 aktualisiert.
- Verbesserte Steuerung von `ScheduleScrollUpdate` in SnapPanel.
- Erhöhte Serverkapazität für 300+ Benutzer in EDMIService.
- Einführung von Windows-Authentifizierungs-Caching.
- Neuer atomarer Tabellen-Caching-Mechanismus in EDMIService.
- Lock-Mechanismus in JobListener optimiert (Instanz-Lock).
- Verbesserte Performance durch kürzere Lock-Dauern in SaveDataTables.
- Logging und Snapshot-Veröffentlichung nach Job-Ausführung optimiert.
- Allgemeine Skalierungs- und Performance-Verbesserungen.
This commit is contained in:
Developer01
2026-10-12 10:01:58 +02:00
parent d15d464034
commit 9fbcb97fe4
5 changed files with 124 additions and 64 deletions

View File

@@ -71,16 +71,37 @@ Public Class EDMIService
oThrottling = New ServiceThrottlingBehavior()
Config.Description.Behaviors.Add(oThrottling)
End If
Dim oCpuCores As Integer = Environment.ProcessorCount
oThrottling.MaxConcurrentCalls = Math.Max(200, 32 * oCpuCores) ' war 16×
oThrottling.MaxConcurrentSessions = Math.Max(400, 150 * oCpuCores) ' war 100×
' ← Server-seitige Kapazität für 300+ User
oThrottling.MaxConcurrentCalls = Math.Max(400, 32 * oCpuCores)
oThrottling.MaxConcurrentSessions = Math.Max(600, 150 * oCpuCores)
oThrottling.MaxConcurrentInstances = Integer.MaxValue
' ── Werte für späteres Logging speichern ─────────────────────────────
' ← Server-seitiges Binding: toleriert langsamere Clients (alte DLL!)
Dim oNetTcpBinding = TryCast(oBinding, NetTcpBinding)
If oNetTcpBinding IsNot Nothing Then
' ← Konsistent mit Channel.GetBinding(): CPUs × 50, mind. 500
Dim oMaxConnections = Math.Max(500, oCpuCores * 50)
oNetTcpBinding.SendTimeout = TimeSpan.FromMinutes(5)
oNetTcpBinding.ReceiveTimeout = TimeSpan.FromMinutes(10)
oNetTcpBinding.OpenTimeout = TimeSpan.FromMinutes(2)
oNetTcpBinding.MaxConnections = oMaxConnections
LogConfig?.GetLogger()?.Debug("✔ NetTcpBinding configured | MaxConnections=[{0}] | SendTimeout=[{1}]",
oMaxConnections, oNetTcpBinding.SendTimeout)
End If
' ← NEU: Windows Auth Caching – reduziert DC-Anfragen bei parallelen Verbindungen
Dim oServiceCredentials = Config.Description.Behaviors.Find(Of ServiceCredentials)()
If oServiceCredentials Is Nothing Then
oServiceCredentials = New ServiceCredentials()
Config.Description.Behaviors.Add(oServiceCredentials)
End If
' Gruppenauflösung deaktivieren: weniger DC-Roundtrips pro Verbindung
oServiceCredentials.WindowsAuthentication.IncludeWindowsGroups = False
_ConfiguredEndpoint = oBaseAddress.ToString()
_ConfiguredMaxCalls = oThrottling.MaxConcurrentCalls
_ConfiguredMaxSessions = oThrottling.MaxConcurrentSessions
' ─────────────────────────────────────────────────────────────────────
End Sub
Public Sub New()
@@ -201,27 +222,39 @@ Public Class EDMIService
#Region "=== Database ==="
Private Shared ReadOnly _CachedTablesLock As New Object()
Private Shared _CachedTablesSnapshot As List(Of String) = Nothing
Private Shared _CachedTablesExpiry As DateTime = DateTime.MinValue
' ── CachedTables-Snapshot: wird vom JobListener nach dem DatatableJob gesetzt.
' GetCachedTables() liest nur diesen Snapshot – ruft GlobalState nie direkt auf
' und blockiert daher nicht, auch wenn der DatatableJob den TableStore sperrt.
Private Class CachedTablesState
Public ReadOnly Tables As String()
Public ReadOnly Timestamp As DateTime
Public Sub New(pTables As String(), pTimestamp As DateTime)
Tables = pTables
Timestamp = pTimestamp
End Sub
End Class
Private Shared _CachedTablesState As CachedTablesState = New CachedTablesState(Array.Empty(Of String)(), DateTime.MinValue)
''' <summary>
''' Wird vom JobListener nach erfolgreichem DatatableJob aufgerufen.
''' Setzt den Snapshot atomar – kein Lock nötig, da Array-Referenzzuweisung auf CLR atomar ist.
''' </summary>
Public Shared Sub PublishCachedTablesSnapshot(pTableNames As IEnumerable(Of String))
Dim oTables = pTableNames.Select(Function(t) t.ToUpper()).ToArray()
Dim oNewState = New CachedTablesState(oTables, DateTime.Now)
' Atomar: Timestamp und Array immer gemeinsam sichtbar
Interlocked.Exchange(_CachedTablesState, oNewState)
LogConfig?.GetLogger()?.Debug("PublishCachedTablesSnapshot: [{0}] tables published at [{1}]",
oTables.Length, oNewState.Timestamp.ToString("HH:mm:ss"))
End Sub
Public Function GetCachedTables() As List(Of String) Implements IEDMIService.GetCachedTables
Try
SyncLock _CachedTablesLock
If _CachedTablesSnapshot Is Nothing OrElse DateTime.Now > _CachedTablesExpiry Then
_Logger.Debug("GetCachedTables: Refreshing cache...")
_CachedTablesSnapshot = GlobalState.GetCachedTables()
_CachedTablesExpiry = DateTime.Now.AddSeconds(30)
_Logger.Debug("GetCachedTables: Cache refreshed | Count=[{0}] | NextExpiry=[{1}]",
_CachedTablesSnapshot.Count, _CachedTablesExpiry.ToString("HH:mm:ss"))
End If
Return _CachedTablesSnapshot.ToList()
End SyncLock
Catch ex As Exception
_Logger.Error("GetCachedTables failed | User=[{0}] | Error=[{1}]", _Username, ex.Message)
_Logger.Error(ex)
Return New List(Of String)
End Try
Dim oState = _CachedTablesState ' atomarer Read – eine Referenz
_Logger.Debug("GetCachedTables: returning snapshot | Count=[{0}] | SnapshotAge=[{1}s]",
oState.Tables.Length,
CInt((DateTime.Now - oState.Timestamp).TotalSeconds))
Return oState.Tables.ToList()
End Function
Public Function ReturnDatatableFromCache(Name As String, FilterExpression As String, SortByColumn As String) As TableResult Implements IEDMIService.ReturnDatatableFromCache