Index: /trunk/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/HiveStatistics/DimJobDao.cs
===================================================================
--- /trunk/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/HiveStatistics/DimJobDao.cs	(revision 17573)
+++ /trunk/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/HiveStatistics/DimJobDao.cs	(revision 17574)
@@ -44,4 +44,8 @@
     }
 
+    public void UpdateExistingDimJobs() {
+      DataContext.ExecuteCommand(UpdateExistingDimJobsQuery);
+    }
+
     #region Compiled queries
     private static readonly Func<DataContext, Guid, DimJob> GetByIdQuery =
@@ -51,4 +55,40 @@
          select dimJob).SingleOrDefault());
     #endregion
+
+    #region String queries
+    private const string UpdateExistingDimJobsQuery = @"
+UPDATE u
+SET
+  u.JobName = case when x.JobId is null then u.JobName else x.JobName end,
+  u.TotalTasks = x.TotalTasks,
+  u.CompletedTasks = x.CompletedTasks,
+  u.DateCompleted =
+    case when x.totaltasks = x.CompletedTasks
+      then (case when x.JobId is null and x.DateCompleted is null then GETDATE() else x.DateCompleted end)
+    else u.DateCompleted
+  end,
+  u.ProjectId = case when x.JobId is null then u.ProjectId else x.ProjectId end
+FROM [statistics].dimjob u
+JOIN (
+	SELECT
+	  dj.JobId as DimJobId,
+	  j.JobId as JobId,
+	  j.Name as JobName,
+	  COUNT(*) as TotalTasks,
+	  SUM(
+	  CASE
+		WHEN TaskState in ('Finished', 'Aborted', 'Failed') then 1
+		ELSE 0
+	  END) as CompletedTasks,
+	  MAX(EndTime) as DateCompleted,
+	  dp.ProjectId as ProjectId
+	from [statistics].DimJob dj
+	join [statistics].FactTask ft on dj.JobId = ft.JobId
+	left join Job j on j.JobId = dj.JobId
+	left join [statistics].DimProject dp on j.ProjectId = dp.ProjectId 
+	where dj.DateCompleted is null and dp.DateExpired is null
+	group by dj.JobId, j.JobId, j.Name, dp.ProjectId 
+) as x on u.JobId = x.DimJobId";
+    #endregion
   }
 }
Index: /trunk/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/JobDao.cs
===================================================================
--- /trunk/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/JobDao.cs	(revision 17573)
+++ /trunk/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/JobDao.cs	(revision 17574)
@@ -37,4 +37,8 @@
     }
 
+    public int DeleteByState(JobState state, int batchSize) {
+      return DataContext.ExecuteCommand(DeleteTopNByStateQueryString, batchSize, Enum.GetName(typeof(JobState), state));
+    }
+
     public IEnumerable<Job> GetByProjectId(Guid id) {
       return GetByProjectIdQuery(DataContext, id);
@@ -43,5 +47,5 @@
     public IEnumerable<Job> GetByProjectIds(IEnumerable<Guid> projectIds) {
       string paramProjectIds = string.Join(",", projectIds.ToList().Select(x => string.Format("'{0}'", x)));
-      if(!string.IsNullOrWhiteSpace(paramProjectIds)) {
+      if (!string.IsNullOrWhiteSpace(paramProjectIds)) {
         string queryString = string.Format(GetByProjectIdsQueryString, paramProjectIds);
         return DataContext.ExecuteQuery<Job>(queryString);
@@ -51,9 +55,9 @@
 
     public IEnumerable<Job> GetByState(JobState state) {
-      return GetByStateQuery(DataContext, state);
+      return GetByStateQuery(DataContext, state.ToString());
     }
 
     public IEnumerable<Guid> GetJobIdsByState(JobState state) {
-      return GetJobIdsByStateQuery(DataContext, state);
+      return GetJobIdsByStateQuery(DataContext, state.ToString());
     }
 
@@ -74,13 +78,13 @@
          where job.ProjectId == projectId
          select job));
-    private static readonly Func<DataContext, JobState, IEnumerable<Job>> GetByStateQuery =
-      CompiledQuery.Compile((DataContext db, JobState jobState) =>
+    private static readonly Func<DataContext, string, IEnumerable<Job>> GetByStateQuery =
+      CompiledQuery.Compile((DataContext db, string jobState) =>
         (from job in db.GetTable<Job>()
-         where job.State == jobState
+         where job.State.ToString() == jobState
          select job));
-    private static readonly Func<DataContext, JobState, IEnumerable<Guid>> GetJobIdsByStateQuery =
-      CompiledQuery.Compile((DataContext db, JobState jobState) =>
+    private static readonly Func<DataContext, string, IEnumerable<Guid>> GetJobIdsByStateQuery =
+      CompiledQuery.Compile((DataContext db, string jobState) =>
         (from job in db.GetTable<Job>()
-         where job.State == jobState
+         where job.State.ToString() == jobState
          select job.JobId));
     private static readonly Func<DataContext, IEnumerable<Job>> GetJobsReadyForDeletionQuery =
@@ -101,4 +105,9 @@
       WHERE JobState = {0}
     ";
+    private const string DeleteTopNByStateQueryString = @"
+      DELETE TOP ({0})
+      FROM [Job]
+      WHERE JobState = {1}
+    ";
     private const string GetStatisticsPendingJobs = @"
       SELECT DISTINCT j.*
Index: /trunk/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/StateLogDao.cs
===================================================================
--- /trunk/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/StateLogDao.cs	(revision 17573)
+++ /trunk/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/StateLogDao.cs	(revision 17574)
@@ -40,4 +40,8 @@
     }
 
+    public int DeleteObsolete(int batchSize) {
+      return DataContext.ExecuteCommand(DeleteObsoleteQueryString, batchSize);
+    }
+
     #region Compiled queries
     private static readonly Func<DataContext, Guid, StateLog> GetByIdQuery =
@@ -54,4 +58,14 @@
          select stateLog).First(x => x.SlaveId != null));
     #endregion
+
+    #region String queries
+    private const string DeleteObsoleteQueryString = @"
+delete top ({0}) s
+from statelog s
+  join task t on t.taskid = s.taskid
+  join job j on j.jobid = t.jobid
+where j.jobstate = 'deletionpending'
+    ";
+    #endregion
   }
 }
Index: /trunk/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/TaskDao.cs
===================================================================
--- /trunk/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/TaskDao.cs	(revision 17573)
+++ /trunk/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/TaskDao.cs	(revision 17574)
@@ -50,8 +50,8 @@
       //Because there is at the moment no case where this makes sense (there don't exist parent tasks which need to be calculated), 
       //we skip this step because it's wasted runtime
-      return DataContext.ExecuteQuery<TaskPriorityInfo>(GetWaitingTasksQueryString, 
-        slave.ResourceId, 
-        Enum.GetName(typeof(TaskState), TaskState.Waiting), 
-        slave.FreeCores, 
+      return DataContext.ExecuteQuery<TaskPriorityInfo>(GetWaitingTasksQueryString,
+        slave.ResourceId,
+        Enum.GetName(typeof(TaskState), TaskState.Waiting),
+        slave.FreeCores,
         slave.FreeMemory).ToList();
     }
@@ -65,14 +65,14 @@
     /// <returns></returns>
     public IEnumerable<Task> GetParentTasks(IEnumerable<Guid> resourceIds, int count, bool finished) {
-    var query = from t in Table
-                where t.State == TaskState.Waiting
-                    && t.IsParentTask
-                    && t.Job.AssignedJobResources.All(x => resourceIds.ToList().Contains(x.ResourceId))
-                    && t.FinishWhenChildJobsFinished == finished
-                    && t.ChildJobs.Any()
-                    && t.ChildJobs.All(x =>
-                      x.State == TaskState.Finished
-                      || x.State == TaskState.Aborted
-                      || x.State == TaskState.Failed)
+      var query = from t in Table
+                  where t.State == TaskState.Waiting
+                      && t.IsParentTask
+                      && t.Job.AssignedJobResources.All(x => resourceIds.ToList().Contains(x.ResourceId))
+                      && t.FinishWhenChildJobsFinished == finished
+                      && t.ChildJobs.Any()
+                      && t.ChildJobs.All(x =>
+                        x.State == TaskState.Finished
+                        || x.State == TaskState.Aborted
+                        || x.State == TaskState.Failed)
                   orderby t.Priority descending
                   select t;
@@ -82,4 +82,8 @@
     public void UpdateExecutionTime(Guid taskId, double executionTime) {
       DataContext.ExecuteCommand(UpdateExecutionTimeQuery, executionTime, DateTime.Now, taskId);
+    }
+
+    public int DeleteObsolete(int batchSize) {
+      return DataContext.ExecuteCommand(DeleteObsoleteQueryString, batchSize);
     }
 
@@ -129,4 +133,12 @@
        WHERE TaskId = {2}
     ";
+
+    private const string DeleteObsoleteQueryString = @"
+delete top ({0}) t1
+from task t1
+  left join task t2 on t1.taskid = t2.parenttaskid
+  join job j on j.jobid = t1.jobid
+where j.jobstate = 'deletionpending' and t2.taskid is null
+";
     #endregion
   }
Index: /trunk/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/TaskDataDao.cs
===================================================================
--- /trunk/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/TaskDataDao.cs	(revision 17573)
+++ /trunk/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/TaskDataDao.cs	(revision 17574)
@@ -32,4 +32,8 @@
     }
 
+    public int DeleteObsolete(int batchSize) {
+      return DataContext.ExecuteCommand(DeleteObsoleteQueryString, batchSize);
+    }
+
     #region Compiled queries
     private static readonly Func<DataContext, Guid, TaskData> GetByIdQuery =
@@ -39,4 +43,14 @@
          select taskData).SingleOrDefault());
     #endregion
+
+    #region String queries
+    private const string DeleteObsoleteQueryString = @"
+delete top ({0}) td
+from taskdata td
+  join task t on t.taskid = td.taskid
+  join job j on j.jobid = t.jobid
+where j.jobstate = 'deletionpending'
+    ";
+    #endregion
   }
 }
Index: /trunk/HeuristicLab.Services.Hive.DataAccess/3.3/Interfaces/IPersistenceManager.cs
===================================================================
--- /trunk/HeuristicLab.Services.Hive.DataAccess/3.3/Interfaces/IPersistenceManager.cs	(revision 17573)
+++ /trunk/HeuristicLab.Services.Hive.DataAccess/3.3/Interfaces/IPersistenceManager.cs	(revision 17574)
@@ -64,5 +64,7 @@
     #region Transaction management
     void UseTransaction(Action call, bool repeatableRead = false, bool longRunning = false);
+    void UseTransactionAndSubmit(Action call, bool repeatableRead = false, bool longRunning = false);
     T UseTransaction<T>(Func<T> call, bool repeatableRead = false, bool longRunning = false);
+    T UseTransactionAndSubmit<T>(Func<T> call, bool repeatableRead = false, bool longRunning = false);
     #endregion
 
Index: /trunk/HeuristicLab.Services.Hive.DataAccess/3.3/Manager/PersistenceManager.cs
===================================================================
--- /trunk/HeuristicLab.Services.Hive.DataAccess/3.3/Manager/PersistenceManager.cs	(revision 17573)
+++ /trunk/HeuristicLab.Services.Hive.DataAccess/3.3/Manager/PersistenceManager.cs	(revision 17574)
@@ -184,8 +184,9 @@
     #region Transaction management
     public void UseTransaction(Action call, bool repeatableRead = false, bool longRunning = false) {
-      UseTransaction<object>(() => {
-        call();
-        return null;
-      });
+      UseTransaction<object>(() => { call(); return null; }, repeatableRead, longRunning);
+    }
+
+    public void UseTransactionAndSubmit(Action call, bool repeatableRead = false, bool longRunning = false) {
+      UseTransaction(() => { call(); SubmitChanges(); }, repeatableRead, longRunning);
     }
 
@@ -211,4 +212,8 @@
     }
 
+    public T UseTransactionAndSubmit<T>(Func<T> call, bool repeatableRead = false, bool longRunning = false) {
+      return UseTransaction(() => { var res = call(); SubmitChanges(); return res; }, repeatableRead, longRunning);
+    }
+
     private static TransactionScope CreateTransaction(bool repeatableRead, bool longRunning) {
       var options = new TransactionOptions {
Index: /trunk/HeuristicLab.Services.Hive.DataAccess/3.3/SQL Scripts/Prepare Hive Database.sql
===================================================================
--- /trunk/HeuristicLab.Services.Hive.DataAccess/3.3/SQL Scripts/Prepare Hive Database.sql	(revision 17573)
+++ /trunk/HeuristicLab.Services.Hive.DataAccess/3.3/SQL Scripts/Prepare Hive Database.sql	(revision 17574)
@@ -141,60 +141,2 @@
 INCLUDE ([TaskId],[ExecutionTimeMs],[LastHeartbeat],[ParentTaskId],[Priority],[Command],[JobId])
 GO
-
-
-
--- OBSOLETE - DO NOT PERFORM (start)
-/****** Object:  Trigger [dbo].[tr_JobDeleteCascade]    Script Date: 04/19/2011 16:31:53 ******/
-SET ANSI_NULLS ON
-GO
-SET QUOTED_IDENTIFIER ON
-GO
--- =============================================
--- Author:		cneumuel
--- Create date: 19.04.2011
--- Description:	(1) Writes the execution times of deleted jobs into DeletedJobStats to ensure correct statistics
---				(2) Deletes all associated jobs. This cannot be done with cascading delete, 
---              because the job table defines a INSTEAD OF DELETE trigger itself, which
---              is not compatible with cascading deletes.
--- =============================================
-CREATE TRIGGER [dbo].[tr_JobDeleteCascade] ON [dbo].[Job] INSTEAD OF DELETE AS 
-BEGIN
-    DELETE Task FROM deleted, Task WHERE deleted.JobId = Task.JobId
-    DELETE Job FROM deleted, Job WHERE deleted.JobId = Job.JobId
-END
-GO
-
--- =============================================
--- Author:		cneumuel
--- Create date: 11.11.2010
--- Description:	Recursively deletes all child-jobs of a job when it is deleted. (Source: http://devio.wordpress.com/2008/05/23/recursive-delete-in-sql-server/)
--- =============================================DeletedJobStatistics
-CREATE TRIGGER [dbo].[tr_TaskDeleteCascade] ON [dbo].[Task] INSTEAD OF DELETE AS 
-BEGIN
-    -- recursively delete jobs
-    CREATE TABLE #Table( 
-        TaskId uniqueidentifier 
-    )
-    INSERT INTO #Table (TaskId)
-    SELECT TaskId FROM deleted
-    
-    DECLARE @c INT
-    SET @c = 0
-    
-    WHILE @c <> (SELECT COUNT(TaskId) FROM #Table) BEGIN
-        SELECT @c = COUNT(TaskId) FROM #Table
-        
-        INSERT INTO #Table (TaskId)
-            SELECT Task.TaskId
-            FROM Task
-            LEFT OUTER JOIN #Table ON Task.TaskId = #Table.TaskId
-            WHERE Task.ParentTaskId IN (SELECT TaskId FROM #Table)
-                AND #Table.TaskId IS NULL
-    END
-    
-    DELETE TaskData FROM TaskData INNER JOIN #Table ON TaskData.TaskId = #Table.TaskId
-    DELETE Task FROM Task INNER JOIN #Table ON Task.TaskId = #Table.TaskId
-END
-GO
--- OBSOLETE (end)
-
Index: /trunk/HeuristicLab.Services.Hive.JanitorService/3.3/JanitorService.cs
===================================================================
--- /trunk/HeuristicLab.Services.Hive.JanitorService/3.3/JanitorService.cs	(revision 17573)
+++ /trunk/HeuristicLab.Services.Hive.JanitorService/3.3/JanitorService.cs	(revision 17574)
@@ -26,6 +26,5 @@
   public partial class JanitorService : ServiceBase {
     private HiveJanitor janitor;
-    private Thread janitorCleanupThread;
-    private Thread janitorGenerateStatisticsThread;
+    private Thread janitorThread;
 
     public JanitorService() {
@@ -36,19 +35,14 @@
       janitor = new HiveJanitor();
 
-      janitorCleanupThread = new Thread(janitor.RunCleanup) {
-        IsBackground = true
-      };
-      janitorGenerateStatisticsThread = new Thread(janitor.RunGenerateStatistics) {
+      janitorThread = new Thread(janitor.Run) {
         IsBackground = true
       };
 
-      janitorCleanupThread.Start();
-      janitorGenerateStatisticsThread.Start();
+      janitorThread.Start();
     }
 
     protected override void OnStop() {
       janitor.StopJanitor();
-      janitorCleanupThread.Join();
-      janitorGenerateStatisticsThread.Join();
+      janitorThread.Join();
     }
   }
Index: /trunk/HeuristicLab.Services.Hive/3.3/HeuristicLab.Services.Hive-3.3.csproj
===================================================================
--- /trunk/HeuristicLab.Services.Hive/3.3/HeuristicLab.Services.Hive-3.3.csproj	(revision 17573)
+++ /trunk/HeuristicLab.Services.Hive/3.3/HeuristicLab.Services.Hive-3.3.csproj	(revision 17574)
@@ -173,4 +173,5 @@
       <DesignTimeSharedInput>True</DesignTimeSharedInput>
     </Compile>
+    <Compile Include="Scheduler\TaskScheduler.cs" />
     <Compile Include="ServiceContracts\IHiveService.cs" />
     <Compile Include="ServiceFaults\PluginAlreadyExistsFault.cs" />
Index: /trunk/HeuristicLab.Services.Hive/3.3/HiveJanitor.cs
===================================================================
--- /trunk/HeuristicLab.Services.Hive/3.3/HiveJanitor.cs	(revision 17573)
+++ /trunk/HeuristicLab.Services.Hive/3.3/HiveJanitor.cs	(revision 17574)
@@ -28,6 +28,5 @@
   public class HiveJanitor {
     private bool stop;
-    private AutoResetEvent cleanupWaitHandle;
-    private AutoResetEvent generateStatisticsWaitHandle;
+    private AutoResetEvent runWaitHandle;
 
     private IPersistenceManager PersistenceManager {
@@ -44,60 +43,55 @@
     public HiveJanitor() {
       stop = false;
-      cleanupWaitHandle = new AutoResetEvent(false);
-      generateStatisticsWaitHandle = new AutoResetEvent(false);
+      runWaitHandle = new AutoResetEvent(false);
     }
 
     public void StopJanitor() {
       stop = true;
-      cleanupWaitHandle.Set();
-      generateStatisticsWaitHandle.Set();
+      runWaitHandle.Set();
+    }
+
+    public void Run() {
+      while (!stop) {
+        RunCleanup();
+        RunGenerateStatistics();
+        runWaitHandle.WaitOne(Properties.Settings.Default.GenerateStatisticsInterval);
+      }
+      runWaitHandle.Close();
     }
 
     public void RunCleanup() {
       var pm = PersistenceManager;
-      while (!stop) {
-        try {
-          LogFactory.GetLogger(typeof(HiveJanitor).Namespace).Log("HiveJanitor: starting cleanup.");
-          bool cleanup = false;
+      try {
+        LogFactory.GetLogger(typeof(HiveJanitor).Namespace).Log("HiveJanitor: starting cleanup.");
+        bool cleanup = false;
 
-          var lifecycleDao = pm.LifecycleDao;
-          pm.UseTransaction(() => {
-            var lifecycle = lifecycleDao.GetLastLifecycle();
-            if (lifecycle == null
-                || DateTime.Now - lifecycle.LastCleanup > Properties.Settings.Default.CleanupInterval) {
-              lifecycleDao.UpdateLifecycle();
-              cleanup = true;
-            }
-            pm.SubmitChanges();
-          }, true);
+        var lifecycleDao = pm.LifecycleDao;
+        pm.UseTransaction(() => {
+          var lifecycle = lifecycleDao.GetLastLifecycle();
+          if (lifecycle == null
+              || DateTime.Now - lifecycle.LastCleanup > Properties.Settings.Default.CleanupInterval) {
+            lifecycleDao.UpdateLifecycle();
+            cleanup = true;
+          }
+          pm.SubmitChanges();
+        }, true);
 
-          if (cleanup) {
-            EventManager.Cleanup();
-          }
-          LogFactory.GetLogger(typeof(HiveJanitor).Namespace).Log("HiveJanitor: cleanup finished.");
+        if (cleanup) {
+          EventManager.Cleanup();
         }
-        catch (Exception e) {
-          LogFactory.GetLogger(typeof(HiveJanitor).Namespace).Log(string.Format("HiveJanitor: The following exception occured: {0}", e.ToString()));
-        }
-        cleanupWaitHandle.WaitOne(Properties.Settings.Default.CleanupInterval);
+        LogFactory.GetLogger(typeof(HiveJanitor).Namespace).Log("HiveJanitor: cleanup finished.");
+      } catch (Exception e) {
+        LogFactory.GetLogger(typeof(HiveJanitor).Namespace).Log(string.Format("HiveJanitor: The following exception occured: {0}", e.ToString()));
       }
-      cleanupWaitHandle.Close();
     }
 
     public void RunGenerateStatistics() {
-      while (!stop) {
-        try {
-          LogFactory.GetLogger(typeof(HiveJanitor).Namespace).Log("HiveJanitor: starting generate statistics.");
-          StatisticsGenerator.GenerateStatistics();
-          LogFactory.GetLogger(typeof(HiveJanitor).Namespace).Log("HiveJanitor: generate statistics finished.");
-        }
-        catch (Exception e) {
-          LogFactory.GetLogger(typeof(HiveJanitor).Namespace).Log(string.Format("HiveJanitor: The following exception occured: {0}", e));
-        }
-
-        generateStatisticsWaitHandle.WaitOne(Properties.Settings.Default.GenerateStatisticsInterval);
+      try {
+        LogFactory.GetLogger(typeof(HiveJanitor).Namespace).Log("HiveJanitor: starting generate statistics.");
+        StatisticsGenerator.GenerateStatistics();
+        LogFactory.GetLogger(typeof(HiveJanitor).Namespace).Log("HiveJanitor: generate statistics finished.");
+      } catch (Exception e) {
+        LogFactory.GetLogger(typeof(HiveJanitor).Namespace).Log(string.Format("HiveJanitor: The following exception occured: {0}", e));
       }
-
-      generateStatisticsWaitHandle.Close();
     }
   }
Index: /trunk/HeuristicLab.Services.Hive/3.3/HiveStatisticsGenerator.cs
===================================================================
--- /trunk/HeuristicLab.Services.Hive/3.3/HiveStatisticsGenerator.cs	(revision 17573)
+++ /trunk/HeuristicLab.Services.Hive/3.3/HiveStatisticsGenerator.cs	(revision 17574)
@@ -22,5 +22,4 @@
 using System;
 using System.Collections.Generic;
-using System.Data.Linq;
 using System.Linq;
 using HeuristicLab.Services.Access.DataAccess;
@@ -36,49 +35,83 @@
 
     public void GenerateStatistics() {
-      using (var pm = new PersistenceManager()) {
-
-        pm.UseTransaction(() => {
-          UpdateDimProjectTable(pm);
-          pm.SubmitChanges();
+      Console.WriteLine("started generate statistics");
+
+      using (var pm = new PersistenceManager(true)) {
+        var sw = new System.Diagnostics.Stopwatch();
+
+        sw.Start();
+        pm.UseTransactionAndSubmit(() => { UpdateDimProjectTable(pm); });
+        sw.Stop();
+        LogFactory.GetLogger(typeof(HiveJanitor).Namespace).Log($"UpdateDimProjectTable: {sw.Elapsed}");
+        Console.WriteLine($"UpdateDimProjectTable: {sw.Elapsed}");
+        sw.Reset();
+
+        pm.UseTransactionAndSubmit(() => {
+          sw.Start();
+          UpdateDimUserTable(pm);
+          sw.Stop();
+          LogFactory.GetLogger(typeof(HiveJanitor).Namespace).Log($"UpdateDimUserTable: {sw.Elapsed}");
+          Console.WriteLine($"UpdateDimUserTable: {sw.Elapsed}");
+          sw.Reset();
+
+          sw.Start();
+          UpdateDimJobTable(pm);
+          sw.Stop();
+          LogFactory.GetLogger(typeof(HiveJanitor).Namespace).Log($"UpdateDimJobTable: {sw.Elapsed}");
+          Console.WriteLine($"UpdateDimJobTable: {sw.Elapsed}");
+          sw.Reset();
+
+          sw.Start();
+          UpdateDimClientsTable(pm);
+          sw.Stop();
+          LogFactory.GetLogger(typeof(HiveJanitor).Namespace).Log($"UpdateDimClientsTable: {sw.Elapsed}");
+          Console.WriteLine($"UpdateDimClientsTable: {sw.Elapsed}");
+          sw.Reset();
         });
 
-        pm.UseTransaction(() => {
-          UpdateDimUserTable(pm);
-          
-          UpdateDimJobTable(pm);
-          UpdateDimClientsTable(pm);
-          pm.SubmitChanges();
-        });
-
-        DimTime time = null;
-        pm.UseTransaction(() => {
-          time = UpdateDimTimeTable(pm);
-          pm.SubmitChanges();
-        });
-
-        if (time != null) {
-          pm.UseTransaction(() => {
-            UpdateFactClientInfoTable(time, pm);
-            pm.SubmitChanges();
-            UpdateFactProjectInfoTable(time, pm);
-            pm.SubmitChanges();
-          });
-
-          pm.UseTransaction(() => {
-            try {
-              UpdateFactTaskTable(pm);
-              UpdateExistingDimJobs(pm);
-              FlagJobsForDeletion(pm);
-              pm.SubmitChanges();
-            }
-            catch (DuplicateKeyException e) {
-              var logger = LogFactory.GetLogger(typeof(HiveStatisticsGenerator).Namespace);
-              logger.Log(string.Format(
-                @"Propable change from summertime to wintertime, resulting in overlapping times.
-                          On wintertime to summertime change, slave timeouts and a fact gap will occur. 
-                          Exception Details: {0}", e));
-            }
-          });
-        }
+        pm.UseTransactionAndSubmit(() => {
+          sw.Start();
+          var time = UpdateDimTimeTable(pm);
+          sw.Stop();
+          LogFactory.GetLogger(typeof(HiveJanitor).Namespace).Log($"UpdateDimTimeTable: {sw.Elapsed}");
+          Console.WriteLine($"UpdateDimTimeTable: {sw.Elapsed}");
+          sw.Reset();
+
+          sw.Start();
+          UpdateFactClientInfoTable(time, pm);
+          sw.Stop();
+          LogFactory.GetLogger(typeof(HiveJanitor).Namespace).Log($"UpdateFactClientInfoTable: {sw.Elapsed}");
+          Console.WriteLine($"UpdateFactClientInfoTable: {sw.Elapsed}");
+          sw.Reset();
+
+          sw.Start();
+          UpdateFactProjectInfoTable(time, pm);
+          sw.Stop();
+          LogFactory.GetLogger(typeof(HiveJanitor).Namespace).Log($"UpdateFactProjectInfoTable: {sw.Elapsed}");
+          Console.WriteLine($"UpdateFactProjectInfoTable: {sw.Elapsed}");
+          sw.Reset();
+
+
+          sw.Start();
+          UpdateFactTaskTable(pm);
+          sw.Stop();
+          LogFactory.GetLogger(typeof(HiveJanitor).Namespace).Log($"UpdateFactTaskTable: {sw.Elapsed}");
+          Console.WriteLine($"UpdateFactTaskTable: {sw.Elapsed}");
+          sw.Reset();
+
+          sw.Start();
+          UpdateExistingDimJobs(pm);
+          sw.Stop();
+          LogFactory.GetLogger(typeof(HiveJanitor).Namespace).Log($"UpdateExistingDimJobs: {sw.Elapsed}");
+          Console.WriteLine($"UpdateExistingDimJobs: {sw.Elapsed}");
+          sw.Reset();
+
+          sw.Start();
+          FlagJobsForDeletion(pm);
+          sw.Stop();
+          LogFactory.GetLogger(typeof(HiveJanitor).Namespace).Log($"FlagJobsForDeletion: {sw.Elapsed}");
+          Console.WriteLine($"FlagJobsForDeletion: {sw.Elapsed}");
+          sw.Reset();
+        }, longRunning: true);
       }
     }
@@ -207,40 +240,6 @@
 
     private void UpdateExistingDimJobs(PersistenceManager pm) {
-      var dimProjectDao = pm.DimProjectDao;
-      var jobDao = pm.JobDao;
       var dimJobDao = pm.DimJobDao;
-      var factTaskDao = pm.FactTaskDao;
-      foreach (var dimJob in dimJobDao.GetNotCompletedJobs()) {
-        var taskStates = factTaskDao.GetByJobId(dimJob.JobId)
-            .GroupBy(x => x.TaskState)
-            .Select(x => new {
-              State = x.Key,
-              Count = x.Count()
-            }).ToList();
-        int totalTasks = 0, completedTasks = 0;
-        foreach (var state in taskStates) {
-          totalTasks += state.Count;
-          if (CompletedStates.Contains(state.State)) {
-            completedTasks += state.Count;
-          }
-        }
-        var job = jobDao.GetById(dimJob.JobId);
-        if (totalTasks == completedTasks) {
-          var completeDate = factTaskDao.GetLastCompletedTaskFromJob(dimJob.JobId);
-          if (completeDate == null) {
-            if (job == null) {
-              completeDate = DateTime.Now;
-            }
-          }
-          dimJob.DateCompleted = completeDate;
-        }
-        if(job != null) {
-          dimJob.JobName = job.Name;
-          dimJob.ProjectId = dimProjectDao.GetLastValidIdByProjectId(job.ProjectId);
-        }
-
-        dimJob.TotalTasks = totalTasks;
-        dimJob.CompletedTasks = completedTasks;
-      }
+      dimJobDao.UpdateExistingDimJobs();
     }
 
@@ -248,5 +247,5 @@
       var jobDao = pm.JobDao;
       var jobs = jobDao.GetJobsReadyForDeletion();
-      foreach(var job in jobs) {
+      foreach (var job in jobs) {
         job.State = JobState.DeletionPending;
       }
@@ -257,6 +256,6 @@
       var resourceDao = pm.ResourceDao;
 
-      var resources = resourceDao.GetAll().ToList();
-      var dimClients = dimClientDao.GetAllOnlineClients().ToList();
+      var resources = resourceDao.GetAll().ToList(); // all live now
+      var dimClients = dimClientDao.GetAllOnlineClients().ToList(); // all in statistics which are online (i.e. not expired)
 
       var onlineClients = dimClients.Where(x => resources.Select(y => y.ResourceId).Contains(x.ResourceId));
@@ -265,5 +264,5 @@
 
       // set expiration time of removed resources
-      foreach(var r in removedResources) {
+      foreach (var r in removedResources) {
         r.DateExpired = DateTime.Now;
       }
@@ -281,8 +280,8 @@
       // expire client if its parent has changed and create a new entry
       // otherwise perform "normal" update
-      foreach(var dimc in onlineClients) {
+      foreach (var dimc in onlineClients) {
         var r = resources.Where(x => x.ResourceId == dimc.ResourceId).SingleOrDefault();
-        if(r != null) {
-          if(dimc.ParentResourceId == null ? r.ParentResourceId != null : dimc.ParentResourceId != r.ParentResourceId) {
+        if (r != null) {
+          if (dimc.ParentResourceId == null ? r.ParentResourceId != null : dimc.ParentResourceId != r.ParentResourceId) {
             var now = DateTime.Now;
             dimc.DateExpired = now;
@@ -416,11 +415,11 @@
         let uStats = projectUsageStats.Where(x => x.ProjectId == dimp.ProjectId).SingleOrDefault()
         select new FactProjectInfo {
-            ProjectId = dimp.Id,
-            DimTime = newTime,
-            NumTotalCores = aStats != null ? aStats.Cores : 0,
-            TotalMemory = aStats != null ? aStats.Memory : 0,
-            NumUsedCores = uStats != null ? uStats.Cores : 0,
-            UsedMemory = uStats != null ? uStats.Memory : 0
-          }
+          ProjectId = dimp.Id,
+          DimTime = newTime,
+          NumTotalCores = aStats != null ? aStats.Cores : 0,
+          TotalMemory = aStats != null ? aStats.Memory : 0,
+          NumUsedCores = uStats != null ? uStats.Cores : 0,
+          UsedMemory = uStats != null ? uStats.Memory : 0
+        }
         );
     }
@@ -431,25 +430,40 @@
       var dimClientDao = pm.DimClientDao;
 
-      var factTaskIds = factTaskDao.GetAll().Select(x => x.TaskId);
-      var notFinishedFactTasks = factTaskDao.GetNotFinishedTasks();
-      //var notFinishedFactTasks = factTaskDao.GetNotFinishedTasks().Select(x => new {
-      //  x.TaskId,
-      //  x.LastClientId
-      //});
-
-      // query several properties for all new and not finished tasks
-      // in order to use them later either...
-      // (1) to update the fact task entry of not finished tasks
-      // (2) to insert a new fact task entry for new tasks
+      var preselectedNewAndNotFinishedTasks =
+        (from task in taskDao.GetAll()
+         from factTask in factTaskDao.GetAll().Where(f => task.TaskId == f.TaskId).DefaultIfEmpty()
+         let stateLogs = task.StateLogs.OrderByDescending(x => x.DateTime).ToList()
+         let lastSlaveId = stateLogs.First(x => x.SlaveId != null)
+         where !task.IsParentTask && (factTask.TaskId == null || factTask.EndTime == null)
+         select new { Task = task, StateLogs = stateLogs, LastSlaveId = lastSlaveId }).ToList();
+
+      Console.WriteLine("preselectedNewAndNotFinishedTasks.Count = {0}", preselectedNewAndNotFinishedTasks.Count);
+
+      // jkarder: maybe we can split this query into multiple ones to retrieve state logs and the last slave
+
+      var clients = dimClientDao.GetAllOnlineClients().ToList();
+      Console.WriteLine("clients.Count = {0}", clients.Count);
+      var notFinishedFactTasks = factTaskDao.GetNotFinishedTasks().ToList();
+      Console.WriteLine("notFinishedFactTasks.Count = {0}", notFinishedFactTasks.Count);
+
       var newAndNotFinishedTasks =
-        (from task in taskDao.GetAllChildTasks()
-         let stateLogs = task.StateLogs.OrderByDescending(x => x.DateTime)
-         let lastSlaveId = stateLogs.First(x => x.SlaveId != null).SlaveId
-         where (!factTaskIds.Contains(task.TaskId)
-                || notFinishedFactTasks.Select(x => x.TaskId).Contains(task.TaskId))
+        (from x in preselectedNewAndNotFinishedTasks
+         let task = x.Task
+         let stateLogs = x.StateLogs // jkarder: if multiple join results in multiple rows, statelogs of all equal tasks must be the same ...
+         let lastSlaveId = x.LastSlaveId
          join lastFactTask in notFinishedFactTasks on task.TaskId equals lastFactTask.TaskId into lastFactPerTask
          from lastFact in lastFactPerTask.DefaultIfEmpty()
-         join client in dimClientDao.GetAllOnlineClients() on lastSlaveId equals client.ResourceId into clientsPerSlaveId
-         from client in clientsPerSlaveId.DefaultIfEmpty()
+           // jkarder:
+           // we can still fix this another way, if we only select that one row from dimclients that fits the given statelog/lastslave entry
+           // dimclient has multiple entires for one and the same client, because we track changes in resource group hierarchies
+           // -> left join from task/statelog to dimclient results in multiple rows because multiple rows in dimclient with the same resource id exist
+           // -> further down the road we call singleordefault to single out tasks to get the data for the update
+           // -> dimclient should only contain one valid row for a given time span that fits to the lastslaveid.datetime
+           // -> client.datecreated <= lastslaveid.datetime <= client.dateexpired
+           // it's aweful ...
+         from client in clients.Where(c => lastSlaveId != null && lastSlaveId.SlaveId == c.ResourceId && c.DateCreated <= lastSlaveId.DateTime && (c.DateExpired == null || lastSlaveId.DateTime <= c.DateExpired)).DefaultIfEmpty()
+           // jkarder:
+           //join client in clients on lastSlaveId.SlaveId equals client.ResourceId into clientsPerSlaveId
+           //from client in clientsPerSlaveId.DefaultIfEmpty()
          select new {
            TaskId = task.TaskId,
@@ -459,16 +473,20 @@
            MemoryRequired = task.MemoryNeeded,
            State = task.State,
-           StateLogs = stateLogs.OrderBy(x => x.DateTime),
+           StateLogs = stateLogs.OrderBy(x => x.DateTime).ToList(),
            LastClientId = client != null
-                          ? client.Id : lastFact != null
-                          ? lastFact.LastClientId : (Guid?)null,
+                        ? client.Id : lastFact != null
+                        ? lastFact.LastClientId : (Guid?)null,
            NotFinishedTask = notFinishedFactTasks.Any(y => y.TaskId == task.TaskId)
          }).ToList();
+
+      Console.WriteLine("newAndNotFinishedTasks.Count = {0}", newAndNotFinishedTasks.Count);
+
 
       // (1) update data of already existing facts
       // i.e. for all in newAndNotFinishedTasks where NotFinishedTask = true
       foreach (var notFinishedFactTask in notFinishedFactTasks) {
-        var nfftUpdate = newAndNotFinishedTasks.Where(x => x.TaskId == notFinishedFactTask.TaskId).SingleOrDefault();
-        if(nfftUpdate != null) {
+        // jkarder: firstordefault should work too, because statelogs of multiple task rows that result from the earlier join have to be the same
+        var nfftUpdate = newAndNotFinishedTasks.Where(x => x.TaskId == notFinishedFactTask.TaskId).FirstOrDefault();
+        if (nfftUpdate != null) {
           var taskData = CalculateFactTaskData(nfftUpdate.StateLogs);
 
@@ -487,56 +505,37 @@
           notFinishedFactTask.Exception = taskData.Exception;
           notFinishedFactTask.InitialWaitingTime = taskData.InitialWaitingTime;
+        } else {
+          //Console.WriteLine("could not update task {0}", notFinishedFactTask.TaskId);
         }
       }
+
+      Console.WriteLine("nfft update complete");
 
       // (2) insert facts for new tasks
       // i.e. for all in newAndNotFinishedTasks where NotFinishedTask = false
-      factTaskDao.Save(
-        from x in newAndNotFinishedTasks
-        where !x.NotFinishedTask
-        let taskData = CalculateFactTaskData(x.StateLogs)
-        select new FactTask {
-          TaskId = x.TaskId,
-          JobId = x.JobId,
-          StartTime = taskData.StartTime,
-          EndTime = taskData.EndTime,
-          LastClientId = x.LastClientId,
-          Priority = x.Priority,
-          CoresRequired = x.CoresRequired,
-          MemoryRequired = x.MemoryRequired,
-          NumCalculationRuns = taskData.CalculationRuns,
-          NumRetries = taskData.Retries,
-          WaitingTime = taskData.WaitingTime,
-          CalculatingTime = taskData.CalculatingTime,
-          TransferTime = taskData.TransferTime,
-          TaskState = x.State,
-          Exception = taskData.Exception,
-          InitialWaitingTime = taskData.InitialWaitingTime
-        });
-
-
-      ////update data of already existing facts
-      //foreach (var notFinishedTask in factTaskDao.GetNotFinishedTasks()) {
-      //  var ntc = newTasks.Where(x => x.TaskId == notFinishedTask.TaskId);
-      //  if (ntc.Any()) {
-      //    var x = ntc.Single();
-      //    var taskData = CalculateFactTaskData(x.StateLogs);
-
-      //    notFinishedTask.StartTime = taskData.StartTime;
-      //    notFinishedTask.EndTime = taskData.EndTime;
-      //    notFinishedTask.LastClientId = x.LastClientId;
-      //    notFinishedTask.Priority = x.Priority;
-      //    notFinishedTask.CoresRequired = x.CoresRequired;
-      //    notFinishedTask.MemoryRequired = x.MemoryRequired;
-      //    notFinishedTask.NumCalculationRuns = taskData.CalculationRuns;
-      //    notFinishedTask.NumRetries = taskData.Retries;
-      //    notFinishedTask.WaitingTime = taskData.WaitingTime;
-      //    notFinishedTask.CalculatingTime = taskData.CalculatingTime;
-      //    notFinishedTask.TransferTime = taskData.TransferTime;
-      //    notFinishedTask.TaskState = x.State;
-      //    notFinishedTask.Exception = taskData.Exception;
-      //    notFinishedTask.InitialWaitingTime = taskData.InitialWaitingTime;
-      //  }
-      //}
+      var newFactTasks = (from x in newAndNotFinishedTasks
+                          where !x.NotFinishedTask
+                          let taskData = CalculateFactTaskData(x.StateLogs)
+                          select new FactTask {
+                            TaskId = x.TaskId,
+                            JobId = x.JobId,
+                            StartTime = taskData.StartTime,
+                            EndTime = taskData.EndTime,
+                            LastClientId = x.LastClientId,
+                            Priority = x.Priority,
+                            CoresRequired = x.CoresRequired,
+                            MemoryRequired = x.MemoryRequired,
+                            NumCalculationRuns = taskData.CalculationRuns,
+                            NumRetries = taskData.Retries,
+                            WaitingTime = taskData.WaitingTime,
+                            CalculatingTime = taskData.CalculatingTime,
+                            TransferTime = taskData.TransferTime,
+                            TaskState = x.State,
+                            Exception = taskData.Exception,
+                            InitialWaitingTime = taskData.InitialWaitingTime
+                          }).ToList();
+      Console.WriteLine("newFactTasks.Count = {0}", newFactTasks.Count);
+      factTaskDao.Save(newFactTasks);
+      Console.WriteLine("save of new fact tasks completed");
     }
 
@@ -551,6 +550,5 @@
           return user != null ? user.UserName : UnknownUserName;
         }
-      }
-      catch (Exception) {
+      } catch (Exception) {
         return UnknownUserName;
       }
Index: /trunk/HeuristicLab.Services.Hive/3.3/Interfaces/ITaskScheduler.cs
===================================================================
--- /trunk/HeuristicLab.Services.Hive/3.3/Interfaces/ITaskScheduler.cs	(revision 17573)
+++ /trunk/HeuristicLab.Services.Hive/3.3/Interfaces/ITaskScheduler.cs	(revision 17574)
@@ -20,10 +20,11 @@
 #endregion
 
+using System;
 using System.Collections.Generic;
-using HeuristicLab.Services.Hive.DataTransfer;
+using HeuristicLab.Services.Hive.DataAccess;
 
 namespace HeuristicLab.Services.Hive {
   public interface ITaskScheduler {
-    IEnumerable<TaskInfoForScheduler> Schedule(IEnumerable<TaskInfoForScheduler> tasks, int count = 1);
+    IEnumerable<Guid> Schedule(Slave slave, int count = 1);
   }
 }
Index: /trunk/HeuristicLab.Services.Hive/3.3/Manager/EventManager.cs
===================================================================
--- /trunk/HeuristicLab.Services.Hive/3.3/Manager/EventManager.cs	(revision 17573)
+++ /trunk/HeuristicLab.Services.Hive/3.3/Manager/EventManager.cs	(revision 17574)
@@ -28,4 +28,6 @@
   public class EventManager : IEventManager {
     private const string SlaveTimeout = "Slave timed out.";
+    private static readonly TaskState[] CompletedStates = { TaskState.Finished, TaskState.Aborted, TaskState.Failed };
+
     private IPersistenceManager PersistenceManager {
       get { return ServiceLocator.Instance.PersistenceManager; }
@@ -33,22 +35,54 @@
 
     public void Cleanup() {
+      Console.WriteLine("started cleanup");
       var pm = PersistenceManager;
 
-      pm.UseTransaction(() => {
-        FinishJobDeletion(pm);
-        pm.SubmitChanges();
-      });
+      // preemptiv delete obsolete entities
+      // speeds up job deletion
+      BatchDelete((p, s) => p.StateLogDao.DeleteObsolete(s), 100, 100, true, pm, "DeleteObsoleteStateLogs");
+      BatchDelete((p, s) => p.TaskDataDao.DeleteObsolete(s), 100, 20, true, pm, "DeleteObsoleteTaskData");
+      BatchDelete((p, s) => p.TaskDao.DeleteObsolete(s), 100, 20, false, pm, "DeleteObsoleteTasks");
+      BatchDelete((p, s) => p.JobDao.DeleteByState(JobState.DeletionPending, s), 100, 20, true, pm, "DeleteObsoleteJobs");
 
-      pm.UseTransaction(() => {
-        SetTimeoutSlavesOffline(pm);
-        SetTimeoutTasksWaiting(pm);
-        DeleteObsoleteSlaves(pm);
-        pm.SubmitChanges();
-      });
+      LogFactory.GetLogger(typeof(HiveJanitor).Namespace).Log("HiveJanitor: SetTimeoutSlavesOffline");
+      Console.WriteLine("5");
+      pm.UseTransactionAndSubmit(() => { SetTimeoutSlavesOffline(pm); });
+      LogFactory.GetLogger(typeof(HiveJanitor).Namespace).Log("HiveJanitor: SetTimeoutTasksWaiting");
+      Console.WriteLine("6");
+      pm.UseTransactionAndSubmit(() => { SetTimeoutTasksWaiting(pm); });
+      LogFactory.GetLogger(typeof(HiveJanitor).Namespace).Log("HiveJanitor: DeleteObsoleteSlaves");
+      Console.WriteLine("7");
+      pm.UseTransactionAndSubmit(() => { DeleteObsoleteSlaves(pm); });
+      LogFactory.GetLogger(typeof(HiveJanitor).Namespace).Log("HiveJanitor: AbortObsoleteTasks");
+      Console.WriteLine("8");
+      pm.UseTransactionAndSubmit(() => { AbortObsoleteTasks(pm); });
+      LogFactory.GetLogger(typeof(HiveJanitor).Namespace).Log("HiveJanitor: FinishParentTasks");
+      Console.WriteLine("9");
+      pm.UseTransactionAndSubmit(() => { FinishParentTasks(pm); });
+      LogFactory.GetLogger(typeof(HiveJanitor).Namespace).Log("HiveJanitor: DONE");
+      Console.WriteLine("10");
+    }
 
-      pm.UseTransaction(() => {
-        FinishParentTasks(pm);
-        pm.SubmitChanges();
-      });
+    private void BatchDelete(
+      Func<IPersistenceManager, int, int> deletionFunc,
+      int batchSize,
+      int maxCalls,
+      bool limitIsBatchSize,
+      IPersistenceManager pm,
+      string logMessage
+    ) {
+      int totalDeleted = 0;
+      while (maxCalls > 0) {
+        maxCalls--;
+        LogFactory.GetLogger(typeof(HiveJanitor).Namespace).Log($"HiveJanitor: {logMessage}");
+        Console.WriteLine($"HiveJanitor: {logMessage}");
+        var deleted = pm.UseTransactionAndSubmit(() => { return deletionFunc(pm, batchSize); });
+        LogFactory.GetLogger(typeof(HiveJanitor).Namespace).Log($"HiveJanitor: {logMessage} DONE (deleted {deleted}, {maxCalls} calls left)");
+        Console.WriteLine($"HiveJanitor: {logMessage} DONE (deleted {deleted}, {maxCalls} calls left)");
+        totalDeleted += deleted;
+        if (limitIsBatchSize && deleted < batchSize || deleted <= 0) return;
+      }
+      LogFactory.GetLogger(typeof(HiveJanitor).Namespace).Log($"HiveJanitor: Possible rows left to delete (total deleted: {totalDeleted}).");
+      Console.WriteLine($"HiveJanitor: Possible rows left to delete (total deleted: {totalDeleted}).");
     }
 
@@ -136,4 +170,22 @@
       }
     }
+
+    /// <summary>
+    /// Aborts tasks whose jobs have already been marked for deletion
+    /// </summary>
+    /// <param name="pm"></param>
+    private void AbortObsoleteTasks(IPersistenceManager pm) {
+      var jobDao = pm.JobDao;
+      var taskDao = pm.TaskDao;
+
+      var obsoleteTasks = (from jobId in jobDao.GetJobIdsByState(JobState.StatisticsPending)
+                           join task in taskDao.GetAll() on jobId equals task.JobId
+                           where !CompletedStates.Contains(task.State) && task.Command == null
+                           select task).ToList();
+
+      foreach (var t in obsoleteTasks) {
+        t.State = TaskState.Aborted;
+      }
+    }
   }
 }
Index: /trunk/HeuristicLab.Services.Hive/3.3/Manager/HeartbeatManager.cs
===================================================================
--- /trunk/HeuristicLab.Services.Hive/3.3/Manager/HeartbeatManager.cs	(revision 17573)
+++ /trunk/HeuristicLab.Services.Hive/3.3/Manager/HeartbeatManager.cs	(revision 17574)
@@ -83,29 +83,16 @@
             mutexAquired = mutex.WaitOne(Properties.Settings.Default.SchedulingPatience);
             if (mutexAquired) {
-              var waitingTasks = pm.UseTransaction(() => taskDao.GetWaitingTasks(slave)
-                  .Select(x => new TaskInfoForScheduler {
-                    TaskId = x.TaskId,
-                    JobId = x.JobId,
-                    Priority = x.Priority
-                  })
-                  .ToList()
-              );
-              var availableTasks = TaskScheduler.Schedule(waitingTasks).ToArray();
-              if (availableTasks.Any()) {
-                var task = availableTasks.First();
-                AssignTask(pm, slave, task.TaskId);
-                actions.Add(new MessageContainer(MessageContainer.MessageType.CalculateTask, task.TaskId));
+              var scheduledTaskIds = TaskScheduler.Schedule(slave, 1).ToArray();
+              foreach (var id in scheduledTaskIds) {
+                actions.Add(new MessageContainer(MessageContainer.MessageType.CalculateTask, id));
               }
             } else {
-              LogFactory.GetLogger(this.GetType().Namespace).Log("HeartbeatManager: The mutex used for scheduling could not be aquired.");
+              LogFactory.GetLogger(this.GetType().Namespace).Log($"HeartbeatManager: The mutex used for scheduling could not be aquired. (HB from Slave {slave.ResourceId})");
             }
-          }
-          catch (AbandonedMutexException) {
-            LogFactory.GetLogger(this.GetType().Namespace).Log("HeartbeatManager: The mutex used for scheduling has been abandoned.");
-          }
-          catch (Exception ex) {
-            LogFactory.GetLogger(this.GetType().Namespace).Log(string.Format("HeartbeatManager threw an exception in ProcessHeartbeat: {0}", ex));
-          }
-          finally {
+          } catch (AbandonedMutexException) {
+            LogFactory.GetLogger(this.GetType().Namespace).Log($"HeartbeatManager: The mutex used for scheduling has been abandoned. (HB from Slave {slave.ResourceId})");
+          } catch (Exception ex) {
+            LogFactory.GetLogger(this.GetType().Namespace).Log($"HeartbeatManager threw an exception in ProcessHeartbeat (HB from Slave {slave.ResourceId}): {ex}");
+          } finally {
             if (mutexAquired) mutex.ReleaseMutex();
           }
@@ -113,25 +100,4 @@
       }
       return actions;
-    }
-
-    private void AssignTask(IPersistenceManager pm, DA.Slave slave, Guid taskId) {
-      const DA.TaskState transferring = DA.TaskState.Transferring;
-      DateTime now = DateTime.Now;
-      var taskDao = pm.TaskDao;
-      var stateLogDao = pm.StateLogDao;
-      pm.UseTransaction(() => {
-        var task = taskDao.GetById(taskId);
-        stateLogDao.Save(new DA.StateLog {
-          State = transferring,
-          DateTime = now,
-          TaskId = taskId,
-          SlaveId = slave.ResourceId,
-          UserId = null,
-          Exception = null
-        });
-        task.State = transferring;
-        task.LastHeartbeat = now;
-        pm.SubmitChanges();
-      });
     }
 
Index: /trunk/HeuristicLab.Services.Hive/3.3/MessageContainer.cs
===================================================================
--- /trunk/HeuristicLab.Services.Hive/3.3/MessageContainer.cs	(revision 17573)
+++ /trunk/HeuristicLab.Services.Hive/3.3/MessageContainer.cs	(revision 17574)
@@ -24,5 +24,4 @@
 using HEAL.Attic;
 using HeuristicLab.Common;
-using HeuristicLab.Persistence.Default.CompositeSerializers.Storable;
 
 namespace HeuristicLab.Services.Hive {
@@ -31,9 +30,9 @@
   /// the actual message itself and the TaskId, refered by the message
   /// </summary>
-  [StorableClass]
+  [StorableType("67DEE81F-81FA-4B47-B043-93DBC2028DB5")]
   [Serializable]
   [DataContract]
   public class MessageContainer : IDeepCloneable {
-
+    [StorableType("A907BDB0-99E3-4EE2-BA31-72FFD29F7B19")]
     public enum MessageType {
       // *** commands from hive server ***
@@ -62,5 +61,5 @@
 
     [StorableConstructor]
-    protected MessageContainer(bool deserializing) { }
+    protected MessageContainer(StorableConstructorFlag _) { }
     protected MessageContainer() { }
     public MessageContainer(MessageType message) {
Index: /trunk/HeuristicLab.Services.Hive/3.3/Properties/Settings.Designer.cs
===================================================================
--- /trunk/HeuristicLab.Services.Hive/3.3/Properties/Settings.Designer.cs	(revision 17573)
+++ /trunk/HeuristicLab.Services.Hive/3.3/Properties/Settings.Designer.cs	(revision 17574)
@@ -1,9 +1,9 @@
 ﻿//------------------------------------------------------------------------------
 // <auto-generated>
-//     Dieser Code wurde von einem Tool generiert.
-//     Laufzeitversion:4.0.30319.42000
+//     This code was generated by a tool.
+//     Runtime Version:4.0.30319.42000
 //
-//     Änderungen an dieser Datei können falsches Verhalten verursachen und gehen verloren, wenn
-//     der Code erneut generiert wird.
+//     Changes to this file may cause incorrect behavior and will be lost if
+//     the code is regenerated.
 // </auto-generated>
 //------------------------------------------------------------------------------
@@ -13,5 +13,5 @@
     
     [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
-    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "14.0.0.0")]
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.5.0.0")]
     public sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
         
@@ -80,5 +80,5 @@
         [global::System.Configuration.ApplicationScopedSettingAttribute()]
         [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
-        [global::System.Configuration.DefaultSettingValueAttribute("00:00:20")]
+        [global::System.Configuration.DefaultSettingValueAttribute("00:01:10")]
         public global::System.TimeSpan SchedulingPatience {
             get {
Index: /trunk/HeuristicLab.Services.Hive/3.3/Properties/Settings.settings
===================================================================
--- /trunk/HeuristicLab.Services.Hive/3.3/Properties/Settings.settings	(revision 17573)
+++ /trunk/HeuristicLab.Services.Hive/3.3/Properties/Settings.settings	(revision 17574)
@@ -22,5 +22,5 @@
     </Setting>
     <Setting Name="SchedulingPatience" Type="System.TimeSpan" Scope="Application">
-      <Value Profile="(Default)">00:00:20</Value>
+      <Value Profile="(Default)">00:01:10</Value>
     </Setting>
     <Setting Name="ProfileServicePerformance" Type="System.Boolean" Scope="Application">
Index: /trunk/HeuristicLab.Services.Hive/3.3/Scheduler/RoundRobinTaskScheduler.cs
===================================================================
--- /trunk/HeuristicLab.Services.Hive/3.3/Scheduler/RoundRobinTaskScheduler.cs	(revision 17573)
+++ /trunk/HeuristicLab.Services.Hive/3.3/Scheduler/RoundRobinTaskScheduler.cs	(revision 17574)
@@ -23,83 +23,50 @@
 using System.Collections.Generic;
 using System.Linq;
-using HeuristicLab.Services.Hive.DataAccess.Interfaces;
 using DA = HeuristicLab.Services.Hive.DataAccess;
 
 namespace HeuristicLab.Services.Hive {
-  public class RoundRobinTaskScheduler : ITaskScheduler {
-    private IPersistenceManager PersistenceManager {
-      get { return ServiceLocator.Instance.PersistenceManager; }
+  public class RoundRobinTaskScheduler : TaskScheduler {
+    private class TaskPriorityResult {
+      public Guid TaskId { get; set; }
+      public Guid OwnerUserId { get; set; }
     }
 
-    public IEnumerable<TaskInfoForScheduler> Schedule(IEnumerable<TaskInfoForScheduler> tasks, int count = 1) {
-      if (!tasks.Any()) return Enumerable.Empty<TaskInfoForScheduler>();
+    protected override IReadOnlyList<Guid> ScheduleInternal(DA.Slave slave, int count) {
+      var pm = PersistenceManager;
 
-      var pm = PersistenceManager;
-      var userPriorityDao = pm.UserPriorityDao;
-      var jobDao = pm.JobDao;
+      var result = pm.DataContext.ExecuteQuery<TaskPriorityResult>(
+        GetHighestPriorityWaitingTasksQuery, slave.ResourceId, count, slave.FreeCores, slave.FreeMemory).ToList();
 
-      var userPriorities = pm.UseTransaction(() => userPriorityDao.GetAll()
-        .OrderBy(x => x.DateEnqueued)
-        .ToArray()
-      );
+      foreach (var row in result) {
+        pm.DataContext.ExecuteCommand("UPDATE UserPriority SET DateEnqueued = SYSDATETIME() WHERE UserId = {0}", row.OwnerUserId);
+      }
 
-      var userIds = userPriorities.Select(x => x.UserId).ToList();
-      var jobs = pm.UseTransaction(() => {
-        return jobDao.GetAll()
-          .Where(x => userIds.Contains(x.OwnerUserId))
-          .Select(x => new {
-            Id = x.JobId,
-            DateCreated = x.DateCreated,
-            OwnerUserId = x.OwnerUserId
-          })
-          .ToList();
-      });
-
-      var taskJobRelations = tasks.Join(jobs,
-        task => task.JobId,
-        job => job.Id,
-        (task, job) => new { Task = task, JobInfo = job })
-        .OrderByDescending(x => x.Task.Priority)
-        .ToList();
-
-      var scheduledTasks = new List<TaskInfoForScheduler>();
-      int priorityIndex = 0;
-
-      if (count == 0 || count > taskJobRelations.Count) count = taskJobRelations.Count;
-
-      for (int i = 0; i < count; i++) {
-        var defaultEntry = taskJobRelations.First(); // search first task which is not included yet
-        var priorityEntries = taskJobRelations.Where(x => x.JobInfo.OwnerUserId == userPriorities[priorityIndex].UserId).ToArray(); // search for tasks with desired user priority
-        while (!priorityEntries.Any() && priorityIndex < userPriorities.Length - 1) {
-          priorityIndex++;
-          priorityEntries = taskJobRelations.Where(x => x.JobInfo.OwnerUserId == userPriorities[priorityIndex].UserId).ToArray();
-        }
-        if (priorityEntries.Any()) { // tasks with desired user priority found
-          var priorityEntry = priorityEntries.OrderByDescending(x => x.Task.Priority).ThenBy(x => x.JobInfo.DateCreated).First();
-          if (defaultEntry.Task.Priority <= priorityEntry.Task.Priority) {
-            taskJobRelations.Remove(priorityEntry);
-            scheduledTasks.Add(priorityEntry.Task);
-            UpdateUserPriority(pm, userPriorities[priorityIndex]);
-            priorityIndex++;
-          } else { // there are other tasks with higher priorities
-            taskJobRelations.Remove(defaultEntry);
-            scheduledTasks.Add(defaultEntry.Task);
-          }
-        } else {
-          taskJobRelations.Remove(defaultEntry);
-          scheduledTasks.Add(defaultEntry.Task);
-        }
-        if (priorityIndex >= (userPriorities.Length - 1)) priorityIndex = 0;
-      }
-      return scheduledTasks;
-
+      return result.Select(x => x.TaskId).ToArray();
     }
 
-    private void UpdateUserPriority(IPersistenceManager pm, DA.UserPriority up) {
-      pm.UseTransaction(() => {
-        up.DateEnqueued = DateTime.Now;
-        pm.SubmitChanges();
-      });
-    }
+    #region Query Strings
+    private string GetHighestPriorityWaitingTasksQuery = @"
+WITH rbranch AS(
+  SELECT ResourceId, ParentResourceId
+  FROM [Resource]
+  WHERE ResourceId = {0}
+  UNION ALL
+  SELECT r.ResourceId, r.ParentResourceId
+  FROM [Resource] r
+  JOIN rbranch rb ON rb.ParentResourceId = r.ResourceId
+)
+SELECT TOP ({1}) t.TaskId, j.OwnerUserId
+FROM Task t
+  JOIN Job j on t.JobId = j.JobId
+  JOIN AssignedJobResource ajr on j.JobId = ajr.JobId
+  JOIN rbranch on ajr.ResourceId = rbranch.ResourceId
+  JOIN UserPriority u on j.OwnerUserId = u.UserId
+WHERE NOT (t.IsParentTask = 1 AND t.FinishWhenChildJobsFinished = 1)
+AND t.TaskState = 'Waiting'
+AND t.CoresNeeded <= {2}
+AND t.MemoryNeeded <= {3}
+AND j.JobState = 'Online'
+ORDER BY t.Priority DESC, u.DateEnqueued ASC, j.DateCreated ASC";
+    #endregion
   }
 }
Index: /trunk/HeuristicLab.Services.Hive/3.3/Scheduler/TaskScheduler.cs
===================================================================
--- /trunk/HeuristicLab.Services.Hive/3.3/Scheduler/TaskScheduler.cs	(revision 17574)
+++ /trunk/HeuristicLab.Services.Hive/3.3/Scheduler/TaskScheduler.cs	(revision 17574)
@@ -0,0 +1,43 @@
+﻿using System;
+using System.Collections.Generic;
+using HeuristicLab.Services.Hive.DataAccess;
+using HeuristicLab.Services.Hive.DataAccess.Interfaces;
+
+
+namespace HeuristicLab.Services.Hive {
+  public abstract class TaskScheduler : ITaskScheduler {
+    protected IPersistenceManager PersistenceManager => ServiceLocator.Instance.PersistenceManager;
+
+    public IEnumerable<Guid> Schedule(Slave slave, int count = 1) {
+      return PersistenceManager.UseTransactionAndSubmit(() => {
+        var ids = ScheduleInternal(slave, count);
+        foreach (var id in ids) AssignTask(slave, id);
+        return ids;
+      });
+    }
+
+    protected abstract IReadOnlyList<Guid> ScheduleInternal(Slave slave, int count);
+
+    private void AssignTask(Slave slave, Guid taskId) {
+      const TaskState transferring = TaskState.Transferring;
+      DateTime now = DateTime.Now;
+
+      var pm = PersistenceManager;
+      var taskDao = pm.TaskDao;
+      var stateLogDao = pm.StateLogDao;
+
+      var task = taskDao.GetById(taskId);
+      task.State = transferring;
+      task.LastHeartbeat = now;
+
+      stateLogDao.Save(new StateLog {
+        State = transferring,
+        DateTime = now,
+        TaskId = taskId,
+        SlaveId = slave.ResourceId,
+        UserId = null,
+        Exception = null
+      });
+    }
+  }
+}
Index: /trunk/HeuristicLab.Services.Hive/3.3/app.config
===================================================================
--- /trunk/HeuristicLab.Services.Hive/3.3/app.config	(revision 17573)
+++ /trunk/HeuristicLab.Services.Hive/3.3/app.config	(revision 17574)
@@ -28,5 +28,5 @@
       </setting>
       <setting name="SchedulingPatience" serializeAs="String">
-        <value>00:00:20</value>
+        <value>00:01:10</value>
       </setting>
       <setting name="ProfileServicePerformance" serializeAs="String">
Index: /trunk/Installers/HiveJanitorServiceInstaller.nsi
===================================================================
--- /trunk/Installers/HiveJanitorServiceInstaller.nsi	(revision 17573)
+++ /trunk/Installers/HiveJanitorServiceInstaller.nsi	(revision 17574)
@@ -46,7 +46,9 @@
 	SetOutPath $INSTDIR
 
-	File "${JANITORBUILDPATH}\GeoIP.dat"
+	File "${JANITORBUILDPATH}\Google.Protobuf.dll"
+	File "${JANITORBUILDPATH}\HEAL.Attic.dll"
+	;File "${JANITORBUILDPATH}\GeoIP.dat"
 	File "${JANITORBUILDPATH}\HeuristicLab.Common-3.3.dll"
-	File "${JANITORBUILDPATH}\HeuristicLab.Persistence-3.3.dll"
+	;File "${JANITORBUILDPATH}\HeuristicLab.Persistence-3.3.dll"
 	File "${JANITORBUILDPATH}\HeuristicLab.PluginInfrastructure-3.3.dll"
 	File "${JANITORBUILDPATH}\HeuristicLab.Services.Access.dll"
@@ -56,5 +58,5 @@
 	File "${JANITORBUILDPATH}\HeuristicLab.Services.Hive.JanitorService-3.3.exe.config"
 	File "${JANITORBUILDPATH}\HeuristicLab.Services.Hive-3.3.dll"
-	File "${JANITORBUILDPATH}\HeuristicLab.Tracing-3.3.dll"
+	;File "${JANITORBUILDPATH}\HeuristicLab.Tracing-3.3.dll"
 	
 	WriteRegStr HKLM SOFTWARE\HeuristicLabHiveJanitorService "Install_Dir" "$INSTDIR"
@@ -76,14 +78,17 @@
 	nsExec::ExecToLog '"$INSTDIR\HeuristicLab.Services.Hive.JanitorService-3.3.exe" --uninstall'
 	  
-	Delete "$INSTDIR\GeoIP.dat"
+	Delete "$INSTDIR\Google.Protobuf.dll"
+	Delete "$INSTDIR\HEAL.Attic.dll"
+	;Delete "$INSTDIR\GeoIP.dat"
 	Delete "$INSTDIR\HeuristicLab.Common-3.3.dll"
-	Delete "$INSTDIR\HeuristicLab.Persistence-3.3.dll"
+	;Delete "$INSTDIR\HeuristicLab.Persistence-3.3.dll"
 	Delete "$INSTDIR\HeuristicLab.PluginInfrastructure-3.3.dll"
 	Delete "$INSTDIR\HeuristicLab.Services.Access.dll"
+	Delete "$INSTDIR\HeuristicLab.Services.Access.DataAccess.dll"
 	Delete "$INSTDIR\HeuristicLab.Services.Hive.DataAccess-3.3.dll"
 	Delete "$INSTDIR\HeuristicLab.Services.Hive.JanitorService-3.3.exe"
 	Delete "$INSTDIR\HeuristicLab.Services.Hive.JanitorService-3.3.exe.config"
 	Delete "$INSTDIR\HeuristicLab.Services.Hive-3.3.dll"
-	Delete "$INSTDIR\HeuristicLab.Tracing-3.3.dll"
+	;Delete "$INSTDIR\HeuristicLab.Tracing-3.3.dll"
 	Delete "$INSTDIR\uninstall.exe"
 
