DbMerge Performance Test

This blog post provides a simplified example of how to conduct a performance test for the DbMerge component using the ETLBox library in a C# application.

Simple Guide to DbMerge Performance

In this blog post, we will look at a simple example that shows how to test the performance of the DbMerge component using ETLBox in a C# application. The code includes three main parts: MergeRow, Program, and DbHelper. Let’s break down what each part does.

MergeRow Class

The MergeRow class defines the structure of the data we are working with. It includes several properties that will be used to compare and update data during the merge process.

public class MergeRow : MergeableRow
{
    [IdColumn]
    public long Id { get; set; }

    [CompareColumn]
    [UpdateColumn]
    public long LongValue1 { get; set; }

    [CompareColumn]
    [UpdateColumn]
    public long LongValue2 { get; set; }

    [CompareColumn]
    [UpdateColumn]
    public long LongValue3 { get; set; }

    [CompareColumn]
    [UpdateColumn]
    public long LongValue4 { get; set; }

    [CompareColumn]
    [UpdateColumn]
    public long LongValue5 { get; set; }

    [CompareColumn]
    [UpdateColumn]
    public long LongValue6 { get; set; }

    [CompareColumn]
    [UpdateColumn]
    public long LongValue7 { get; set; }

    [CompareColumn]
    [UpdateColumn]
    public long LongValue8 { get; set; }

    [CompareColumn]
    [UpdateColumn]
    public long LongValue9 { get; set; }

    [CompareColumn]
    [UpdateColumn]
    public long LongValue10 { get; set; }

    [CompareColumn]
    [UpdateColumn]
    public string StringValue1 { get; set; }
    
    [CompareColumn]
    [UpdateColumn]
    public string StringValue2 { get; set; }

    [CompareColumn]
    [UpdateColumn]
    public string StringValue3 { get; set; }

    [CompareColumn]
    [UpdateColumn]
    public string StringValue4 { get; set; }

    [CompareColumn]
    [UpdateColumn]
    public string StringValue5 { get; set; }

}

Program Class

The Program class is the main part of the application. It sets up the database connection, configures logging, and starts the ETL (Extract, Transform, Load) process.

internal class Program {
    //Connection string for local SQL Server
    //static string connectionString = "Data Source=localhost;User Id=sa;Password=YourStrong@Passw0rd;Initial Catalog=mergetest;TrustServerCertificate=true";
    //Connection string for azure SQL Server
    static string connectionString = "Server=tcp:azure.database.windows.net,1433;Initial Catalog=etlboxsupport;Persist Security Info=False;User ID=etlbox;Password=YourStrong@Passw0rd;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=True;";

    static void Main(string[] args) {

        ETLBoxOffice.LicenseManager.LicenseCheck.LicenseKey =
            @"2024-01-15|TRIAL|||Support|support@etlbox.net||Lhwz7nAQavV8oa/HHWvX2ukUehx0hIa5IkxoBGMjdPJ9p4tlnDt3iL0XHbj53gRDVqK5vST7Gi9WwM06kPZDLvYyrK8ymvdTFFX/KWkUhjpXZX8CUQ6C86BZzlwm6APLPjlFyXZjoo9OujRMtnyLI4YotO3s2ziPvkrKELhtDMk=";

        InitializeLoggingWithNlog();
        Settings.DisableAllLogging = true;
        var connMan = new SqlConnectionManager(connectionString);
        DbHelper.CleanSourceTable = true;
        DbHelper.CreateDatabaseIfNeeded("mergetest", connectionString);
        DbHelper.CreateTables(connMan);

        Console.WriteLine("Writing test data into source and destination tables ...");
        Console.WriteLine("This might take a while ...");

        //Test with small number of rows
        //DbHelper.InsertTestDataSource(connMan, 55, 107);
        //DbHelper.InsertTestDataDestination(connMan, 25, 70);
        //Test with medium number of rows
        //DbHelper.InsertTestDataSource(connMan, 550_123, 1_050_234);
        //DbHelper.InsertTestDataDestination(connMan, 250_345, 700_456);
        //Test with large number of rows
        DbHelper.InsertTestDataSource(connMan, 5_000_123, 10_000_789);
        DbHelper.InsertTestDataDestination(connMan, 2_500_345, 7_000_456);
        Settings.DisableAllLogging = false;

        DbSource<MergeRow> source = new() {
            ConnectionManager = connMan,
            TableName = "source",
            DisableLogging = true
        };
        DbMerge<MergeRow> merge = new() {
            ConnectionManager = connMan,
            TableName = "destination",
            MergeMode = MergeMode.Full,
            CacheMode = CacheMode.Full,
            DisableLogging = false
        };

        DbDestination<MergeRow> delta = new(connMan, "delta");
        delta.DisableLogging = true;
        source.LinkTo(merge);
        merge.LinkTo(delta);
        Network.Execute(source);


    }

    private static void InitializeLoggingWithNlog() {
        using var loggerFactory = LoggerFactory.Create(builder => {
            builder
                .AddFilter("Microsoft", Microsoft.Extensions.Logging.LogLevel.Warning)
                .AddFilter("System", Microsoft.Extensions.Logging.LogLevel.Warning)
                .SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace)
                .AddNLog("nlog.config");
        });
        Settings.LogInstance = loggerFactory.CreateLogger("Default");
        Settings.LogThreshold = 10000;

    }
}

DbHelper Class

The DbHelper class includes helper functions for setting up the database, creating tables, and inserting test data. This class makes sure everything is ready for the ETL process.

internal class DbHelper
{


    public static bool CleanSourceTable = true;
    public static void CreateDatabaseIfNeeded(string dbName, SqlConnectionString connectionString) {
        var masterConnection = new SqlConnectionManager(connectionString.CloneWithMasterDbName());
        if (!IfDatabaseExistsTask.IsExisting(masterConnection, dbName))
            CreateDatabaseTask.Create(masterConnection, dbName);
    }

    public static void CreateTables(SqlConnectionManager connection) {

        TableDefinition SourceTableDef = new TableDefinition("source",
            new List<TableColumn>() {
                new TableColumn("IdentityKey", "int",allowNulls: false, isPrimaryKey:true, isIdentity: true),
                new TableColumn("Id","BIGINT", allowNulls: false),
                new TableColumn("LongValue1","BIGINT", allowNulls: false),
                new TableColumn("LongValue2","BIGINT", allowNulls: false),
                new TableColumn("LongValue3","BIGINT", allowNulls: false),
                new TableColumn("LongValue4","BIGINT", allowNulls: false),
                new TableColumn("LongValue5","BIGINT", allowNulls: false),
                new TableColumn("LongValue6","BIGINT", allowNulls: false),
                new TableColumn("LongValue7","BIGINT", allowNulls: false),
                new TableColumn("LongValue8","BIGINT", allowNulls: false),
                new TableColumn("LongValue9","BIGINT", allowNulls: false),
                new TableColumn("LongValue10","BIGINT", allowNulls: false),
                new TableColumn("StringValue1","VARCHAR(5)", allowNulls: false),
                new TableColumn("StringValue2","VARCHAR(5)", allowNulls: false),
                new TableColumn("StringValue3","VARCHAR(5)", allowNulls: false),
                new TableColumn("StringValue4","VARCHAR(5)", allowNulls: false),
                new TableColumn("StringValue5","VARCHAR(5)", allowNulls: false),
        });

        TableDefinition DestinationTableDef = new TableDefinition("destination",
           new List<TableColumn>() {
                new TableColumn("IdentityKey", "int",allowNulls: false, isPrimaryKey:true, isIdentity: true),
                new TableColumn("Id","BIGINT", allowNulls: false),
                new TableColumn("LongValue1","BIGINT", allowNulls: false),
                new TableColumn("LongValue2","BIGINT", allowNulls: false),
                new TableColumn("LongValue3","BIGINT", allowNulls: false),
                new TableColumn("LongValue4","BIGINT", allowNulls: false),
                new TableColumn("LongValue5","BIGINT", allowNulls: false),
                new TableColumn("LongValue6","BIGINT", allowNulls: false),
                new TableColumn("LongValue7","BIGINT", allowNulls: false),
                new TableColumn("LongValue8","BIGINT", allowNulls: false),
                new TableColumn("LongValue9","BIGINT", allowNulls: false),
                new TableColumn("LongValue10","BIGINT", allowNulls: false),
                new TableColumn("StringValue1","VARCHAR(5)", allowNulls: false),
                new TableColumn("StringValue2","VARCHAR(5)", allowNulls: false),
                new TableColumn("StringValue3","VARCHAR(5)", allowNulls: false),
                new TableColumn("StringValue4","VARCHAR(5)", allowNulls: false),
                new TableColumn("StringValue5","VARCHAR(5)", allowNulls: false),
       });


        TableDefinition DeltaTableDef = new TableDefinition("delta",
            new List<TableColumn>() {
                new TableColumn("IdentityKey", "int",allowNulls: false, isPrimaryKey:true, isIdentity: true),
                new TableColumn("Id","BIGINT", allowNulls: false),
                new TableColumn("ChangeDate","DATETIME2(7)", allowNulls: false),
                new TableColumn("ChangeAction","VARCHAR(100)", allowNulls: false),
        });

        if (!IfTableOrViewExistsTask.IsExisting(connection, SourceTableDef.Name))
            CreateTableTask.Create(connection, SourceTableDef);
        else {
            if (CleanSourceTable) TruncateTableTask.Truncate(connection, SourceTableDef.Name);
        }
        if (!IfTableOrViewExistsTask.IsExisting(connection, DestinationTableDef.Name))
            CreateTableTask.Create(connection, DestinationTableDef);
        else
            TruncateTableTask.Truncate(connection, DestinationTableDef.Name);

        if (!IfTableOrViewExistsTask.IsExisting(connection, DeltaTableDef.Name))
            CreateTableTask.Create(connection, DeltaTableDef);
        else
            TruncateTableTask.Truncate(connection, DeltaTableDef.Name);
    }

    public static void InsertTestDataSource(SqlConnectionManager connection, int start, int end) {

        if (!CleanSourceTable && RowCountTask.Count(connection, "source") > 0) return;
        sourceStart = start;
        sourceEnd = end;
        var source = new MemorySource<MergeRow>();
        source.Data = ProduceSource();
        source.DisableLogging = true;
        var dest = new DbDestination<MergeRow>(connection, "source");
        dest.DisableLogging = true;

        source.LinkTo(dest);
        Network.Execute(source);

    }

    public static void InsertTestDataDestination(SqlConnectionManager connection, int start, int end) {

        destinationStart = start;
        destinationEnd = end;
        var source = new MemorySource<MergeRow>();
        source.Data = ProduceDest();
        source.DisableLogging = true;
        var dest = new DbDestination<MergeRow>(connection, "destination");
        dest.DisableLogging = true;

        source.LinkTo(dest);
        Network.Execute(source);

    }

    static int sourceStart = 50;
    static int sourceEnd = 100;
    static IEnumerable<MergeRow> ProduceSource() {
        while (sourceStart < sourceEnd) {
            yield return new MergeRow() {
                Id = sourceStart,
                LongValue1 = sourceStart % 5,
                LongValue2 = sourceStart % 5,
                LongValue3 = sourceStart % 5,
                LongValue4 = sourceStart % 5,
                LongValue5 = sourceStart % 5,
                LongValue6 = sourceStart % 5,
                LongValue7 = sourceStart % 5,
                LongValue8 = sourceStart % 5,
                LongValue9 = sourceStart % 5,
                LongValue10 = sourceStart % 5,
                StringValue1 = "ABCD" + (sourceStart % 5),
                StringValue2 = "ABCD" + (sourceStart % 5),
                StringValue3 = "ABCD" + (sourceStart % 5),
                StringValue4 = "ABCD" + (sourceStart % 5),
                StringValue5 = "ABCDE",
            };
            sourceStart++;
        }

    }

    static int destinationStart = 1;
    static int destinationEnd = 75;
    static IEnumerable<MergeRow> ProduceDest() {
        while (destinationStart < destinationEnd) {
            yield return new MergeRow() {
                Id = destinationStart,
                LongValue1 = destinationStart % 3,
                LongValue2 = destinationStart % 3,
                LongValue3 = destinationStart % 3,
                LongValue4 = destinationStart % 3,
                LongValue5 = destinationStart % 3,
                LongValue6 = destinationStart % 5,
                LongValue7 = destinationStart % 5,
                LongValue8 = destinationStart % 5,
                LongValue9 = destinationStart % 5,
                LongValue10 = destinationStart % 5,
                StringValue1 = "ABCD" + (destinationStart % 3),
                StringValue2 = "ABCD" + (destinationStart % 3),
                StringValue3 = "ABCD" + (destinationStart % 3),
                StringValue4 = "ABCD" + (destinationStart % 5),
                StringValue5 = "ABCDE",
            };
            destinationStart++;
        }

    }
}

Conclusion

This example shows how to set up a simple performance test for the DbMerge component using ETLBox in C#. The MergeRow class defines the data structure, the Program class runs the ETL process, and the DbHelper class helps with setting up the database and inserting test data. This setup is useful for testing how well the DbMerge component handles large amounts of data.

Code on Github

The entire code for this example, along with additional resources and documentation, is available on GitHub for further exploration and contribution   .