504 Gateway Timeout: STRANGE!!! while server accepting the file into DB the response is not making it to the Client

huangapple go评论55阅读模式
英文:

504 Gateway Timeout: STRANGE!!! while server accepting the file into DB the response is not making it to the Client

问题

我有一个上传和逐行解析到 DataTable,然后通过 SQL 批量复制到数据库的服务。在本地,该服务的整体实现需要 20 秒,在局域网开发服务器上需要 46 秒。但是,当我在测试服务器上运行它(用于测试的服务器)时,页面加载几乎需要 1 分钟,最后显示 '504 网关超时。服务器未能及时响应' 的错误。尽管 SQL 表正在更新。如果我上传的文件少于一半,那么它在任何地方都能正常工作。我只在更大(484613 行)的文件上遇到此错误。
以下是包含整个逻辑的代码:

public int UploadCardBins(string cardBins, out string rows, out List<string> mismatchedRows, out int fileLines)
{
    // ... 代码略

    try
    {
        DataTable table = RetrieveCardBinFromTxtFile(cardBins, out mismatchedRows, out fileLines);

        // ... 代码略

        using (var connection = new SqlConnection(sql))
        {
            connection.Open();
            SqlTransaction transaction = connection.BeginTransaction();

            using (var bulkCopy = new SqlBulkCopy(connection, SqlBulkCopyOptions.Default, transaction))
            {
                // ... 代码略

                try
                {
                    var command = connection.CreateCommand();
                    if (timeParsed)
                        command.CommandTimeout = timeOut;
                    command.CommandText = "delete from dbo.Dicts_CardBin";
                    command.Transaction = transaction;
                    command.ExecuteNonQuery();

                    bulkCopy.WriteToServer(table);
                    Session.Flush();
                    transaction.Commit();
                }
                catch (Exception ex)
                {
                    transaction.Rollback();
                    logger.Error("[{0}] {1}", ex.GetType(), ex);
                    resultCode = (int)ResultCode.GenericError;
                }
                finally
                {
                    transaction.Dispose();
                    connection.Close();
                }
            }
        }
    }
    catch (Exception ex)
    {
        logger.Error("[{0}] {1}", ex.GetType(), ex);
        resultCode = (int)ResultCode.GenericError;
    }
    return resultCode;
}

// 私有方法,用于从文本文件中检索到 DataTable:
private DataTable RetrieveCardBinFromTxtFile(string cardBins, out List<string> mismatchedLines, out int countedLines)
{
    // ... 代码略

    return table;
}

我尝试过:

我在 app.settings 中将连接超时设置为 120 秒(2 分钟),但仍然出现相同的错误。

注意:.NET Framework 版本是 4.7。

经过修改的建议:

我迄今为止尝试了不使用 try catch,结果出现了 Transportation Exception。尝试使用 Stream,但它在控制器层抛出了读写超时异常。我通过将流转换为字符串,因为字符串可以一直传递到实现层。现在,我只有一个引用 cardBins 而没有副本。但我仍然遇到相同的问题。

从我的角度来看,可以采取什么措施(源代码逻辑、数据库配置、更好的批量复制工具)?线程操作并且耗时相同,我已经尝试过。到目前为止,我已经清除了重复项,并将删除更改为截断表,还将检索数据表的方法中的两个循环改为一个。现在它的运行时间从之前的 22 秒缩短到了 13 秒。但仍然在测试服务器上出现了 504 的问题。感谢您的关注和答案。

英文:

I have service that uploads and parses line by line into DataTable and records it via SQL bulk copy into the DB. Locally this service works fine with overall implementation in 20 secs and in LAN dev server takes a little longer in 46 secs. But when I run it on test server (the server for testing) the page is loading almost 1 minute and finally gives out '504 Gateway Time-out. The server didn't respond in time'. Although the SQL Table is being updated. If I upload less than half of the file then it works everywhere just fine. I am getting this error only on heavier (484613 lines) file.
Here is the code that holds the whole logic:

public int UploadCardBins(string cardBins, out string rows, out List&lt;string&gt; mismatchedRows, out int fileLines)
    {
        mismatchedRows = new List&lt;string&gt;();
        fileLines = 0;            
        rows = null;
        int resultCode = (int)ResultCode.Ok;
        bool timeParsed = int.TryParse(ConfigurationManager.AppSettings[&quot;UploadCardBinSqlTimeOut&quot;], out int timeOut);

        try 
        {                
            DataTable table = RetrieveCardBinFromTxtFile(cardBins, out mismatchedRows, out fileLines);               
            
            rows = table.Rows.Count.ToString();              
           
            string sql = ConfigurationManager.ConnectionStrings[&quot;DbConnection&quot;].ConnectionString;

            using (var connection = new SqlConnection(sql))
            {
                connection.Open();                    
                SqlTransaction transaction = connection.BeginTransaction();                    
                using (var bulkCopy = new SqlBulkCopy(connection, SqlBulkCopyOptions.Default, transaction))
                {
                    bulkCopy.BatchSize = table.Rows.Count;
                    bulkCopy.DestinationTableName = &quot;dbo.Dicts_CardBin&quot;;
                    try
                    {                                                      
                        var command = connection.CreateCommand();
                        if(timeParsed)
                            command.CommandTimeout = timeOut;                            
                        command.CommandText = &quot;delete from dbo.Dicts_CardBin&quot;;
                        command.Transaction = transaction;
                        command.ExecuteNonQuery();                            
                        bulkCopy.WriteToServer(table);
                        Session.Flush();
                        transaction.Commit();
                    }
                    catch (Exception ex)
                    {
                        transaction.Rollback();                            
                        logger.Error(&quot;[{0}] {1}&quot;, ex.GetType(), ex);
                        resultCode = (int)ResultCode.GenericError;
                    }
                    finally
                    {
                        transaction.Dispose();                            
                        connection.Close();
                    }
                }
            }                
        }
        catch (Exception ex)
        {
            logger.Error(&quot;[{0}] {1}&quot;, ex.GetType(), ex);
            resultCode = (int)ResultCode.GenericError;
        }            
        return resultCode;
    }

private method to retrieve into DataTable:

private DataTable RetrieveCardBinFromTxtFile(string cardBins, out List&lt;string&gt; mismatchedLines, out int countedLines)
    {
        countedLines = 0;
        mismatchedLines = new List&lt;string&gt;();
        DataTable table = new DataTable();
        string pattern = @&quot;^(?!.*[-\\/_+&amp;!@#$%^&amp;.,*={}();:?&quot;&quot;])(\d.{8})\s\s\s(\d.{8})\s.{21}(\D\D)&quot;;
        MatchCollection matches = Regex.Matches(cardBins, pattern, RegexOptions.Multiline);

        table.Columns.Add(&quot;lk&quot;, typeof(string));
        table.Columns.Add(&quot;hk&quot;, typeof(string));
        table.Columns.Add(&quot;cb&quot;, typeof(string));

        // Remove empty lines at the end of the file
        string[] lines = cardBins.Split(new[] { Environment.NewLine }, StringSplitOptions.None);
        int lastIndex = lines.Length - 1;
        while (lastIndex &gt;= 0 &amp;&amp; string.IsNullOrWhiteSpace(lines[lastIndex]))
        {
            lastIndex--;
        }
        Array.Resize(ref lines, lastIndex + 1);

        ////Check for lines that do not match the pattern
        for (int i = 0; i &lt; lines.Length; i++)
        {
            string line = lines[i];
            if (!Regex.IsMatch(line, pattern))
            {
                mismatchedLines.Add($&quot;Строка {i + 1} not matching: {line}&quot;);
            }
            countedLines++;
        }

        foreach (Match match in matches)
        {
            DataRow row = table.NewRow();

            row[&quot;lk&quot;] = match.Groups[2].Value.Trim();
            row[&quot;hk&quot;] = match.Groups[1].Value.Trim();
            row[&quot;cb&quot;] = match.Groups[3].Value.Trim();

            table.Rows.Add(row);
        }

        return table;
    }

I tried(I thought maybe because of SQL):

I had set in app.settings the connection timeout to 120 seconds(2minutes), but still getting the same error.
Note: .net framework is 4.7
Edited by advises:
I have tried so far no using try catch and it gave Transportatiion Excception, tried the Stream but it kept throwing reading and writing timeout exception on a Controller layer. I've edited out the question by adding. So I just converted stream to string cause string can get through till the implementation layer. Instead of allocating several memory initializations now I have one reference cardBins without copies. Still I have the same issue

What could be done from my side(source logic, db config, better tools for bulk copies). Threads operate and take the same amount of period by the way, tried it.

So far I cleared out the duplicates and changed the Delete to Truncate table, also changed 2 loops with one in the method of retrieving data table. Now it waroks in 13 secs instead of 22 secs of before. But still have the issue though on the side of the Test server returning 504. Thank you for all of your interest and answers.

答案1

得分: 2

以下是翻译好的部分:

  • cardBins 改为一个 Stream。如果需要,可以使用 IFormFile 来获取它。
  • RetrieveCardBinFromTxtFile(我看不到)更改为接受 Stream 并返回 DbDataReader。您可以考虑使用 CsvHelpers 或 FastMember 库。
  • 之后从该 reader 中检索 mismatchedRows 等值。您需要在您的 reader 上创建一个自定义属性来处理这个。
  • 不要使用 delete from,而要使用 TRUNCATE TABLE
  • 到处使用 async
  • 您不需要那么多的错误处理。只需确保在事务和连接对象上放置 using,回滚将自动发生。
  • async 函数不能具有 out 参数。您需要将其更改为返回一个元组。

请考虑同时使用 CancellationToken

英文:

You can significantly increase the performance in the following ways:

  • Change cardBins to be a Stream. You can get this using IFormFile if you want.
  • Change RetrieveCardBinFromTxtFile (which I can't see) to both accept a Stream, and return a DbDataReader. You may want to use either the CsvHelpers or FastMember library.
  • Retrieve the mismatchedRows etc values from this reader afterwards. You would need to make a custom property on your reader that could handle this.
  • Instead of delete from, use TRUNCATE TABLE.
  • Use async everywhere.
  • You don't need all that error handling. Just make sure to put using on your transaction and connection objects, and the rollback will happen automatically.
  • async functions cannot have out parameters. You need to change that to return a tuple instead.
public async Task&lt;(int Result, List&lt;string&gt; mismatchedRows, int fileLines)&gt; UploadCardBins(Stream cardBins)
{
    bool timeParsed = int.TryParse(ConfigurationManager.AppSettings[&quot;UploadCardBinSqlTimeOut&quot;], out int timeOut);

    try 
    {
        using var reader = new StreamReader(cardBins, System.Text.Encoding.UTF8);
        using var table = RetrieveCardBinFromTxtFile(reader);
            
        string sql = ConfigurationManager.ConnectionStrings[&quot;DbConnection&quot;].ConnectionString;

        await using var connection = new SqlConnection(sql);
        await connection.OpenAsync();
        await using var transaction = await connection.BeginTransactionAsync();

        using (var command = new SqlCommand(&quot;TRUNCATE TABLE dbo.Dicts_CardBin;&quot;, connection, transaction)
        {
            if(timeParsed)
                command.CommandTimeout = timeOut;                            
            await command.ExecuteNonQueryAsync();
        }

        using var bulkCopy = new SqlBulkCopy(connection, SqlBulkCopyOptions.Default, transaction);
        bulkCopy.DestinationTableName = &quot;dbo.Dicts_CardBin&quot;;
        await bulkCopy.WriteToServerAsync(table);
        Session.Flush();
        await transaction.CommitAsync();
        return ((int)ResultCode.Ok, table.MismatchedRows, table.FileLines);
    }
    catch (Exception ex)
    {
        logger.Error(&quot;[{0}] {1}&quot;, ex.GetType(), ex);
        return ((int)ResultCode.GenericError, -1, -1);
    }            
}

Consider using CancellationToken as well.

huangapple
  • 本文由 发表于 2023年7月20日 16:32:30
  • 转载请务必保留本文链接:https://go.coder-hub.com/76728041.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定