异步在这里真的有意义吗? NpgSQL 与 F#

is async really making sense here? NpgSQL with F#

我有以下交易:

let insertPositionsAsync accountId timestamp (positions: PositionInfo list) : Async<Result<int, ExchangeError>> =
    async {
        try
            use connection = getConnection location
            do! connection.OpenAsync()

            use writer =
                connection.BeginBinaryImport(
                        $"COPY {accountId}.{tablePositionsName} (ts,instrument,average_price,leverage,unrealized_pnl,side,initial_margin,maintenance_margin,position_initial_margin,open_order_initial_margin,quantity,max_notional)
                        FROM STDIN (FORMAT BINARY)"
                    )

            for t in positions do
                do! writer.StartRowAsync()                                                     |> Async.AwaitTask
                do! writer.WriteAsync(timestamp,                       NpgsqlDbType.Timestamp) |> Async.AwaitTask
                do! writer.WriteAsync(t.Instrument.Ticker,             NpgsqlDbType.Varchar)   |> Async.AwaitTask
                do! writer.WriteAsync(t.AveragePrice,                  NpgsqlDbType.Double)    |> Async.AwaitTask
                do! writer.WriteAsync(t.Leverage,                      NpgsqlDbType.Integer)   |> Async.AwaitTask
                do! writer.WriteAsync(t.UnrealizedPnl,                 NpgsqlDbType.Double)    |> Async.AwaitTask
                do! writer.WriteAsync(t.Side.ToString().ToLower(),     NpgsqlDbType.Varchar)   |> Async.AwaitTask
                do! writer.WriteAsync(t.InitialMargin,                 NpgsqlDbType.Double)    |> Async.AwaitTask
                do! writer.WriteAsync(t.MaintenanceMargin,             NpgsqlDbType.Double)    |> Async.AwaitTask
                do! writer.WriteAsync(t.PositionInitialMargin,         NpgsqlDbType.Double)    |> Async.AwaitTask
                do! writer.WriteAsync(t.OpenOrderInitialMargin,        NpgsqlDbType.Double)    |> Async.AwaitTask
                do! writer.WriteAsync(t.Quantity,                      NpgsqlDbType.Double)    |> Async.AwaitTask
                do! writer.WriteAsync(t.MaxNotional,                   NpgsqlDbType.Double)    |> Async.AwaitTask

            let! c = writer.CompleteAsync()
            return Ok (int c)

        with ex ->
            error $"insertPositionsAsync {ex.Humanize()}"
            return Error (ServiceException ex)
    }

我的理解是循环:

            for t in positions do
                do! writer.StartRowAsync()                                                     |> Async.AwaitTask
                do! writer.WriteAsync(timestamp, NpgsqlDbType.Timestamp) |> Async.AwaitTask
                ...

            let! c = writer.CompleteAsync()

正在驱动程序中发生,它只是在某些本地存储中收集数据。那么拥有所有这些异步块是否有意义? (性能方面)。

但是异步 API 由于某种原因必须存在。我可能缺少什么?

对于任何与性能相关的问题,唯一有意义的答案是衡量它并亲自看看!

更一般地说,异步可以在不阻塞操作系统线程的情况下与数据库进行通信。这可能会对性能产生影响,也可能不会产生影响 - 但这取决于您的程序中还发生了什么。

  • 如果您只有一个逻辑进程与数据库通信,那么无论您是在同步代码还是异步代码中执行此操作都没有区别。

  • 如果您想将数据写入数据库,您可以从多个并发写入器中执行此操作。在这种情况下,异步会有所作为,因为您可以创建更多并发编写器。但我认为这可能不会有太大帮助——因为当你有过多的写入线程时,数据库可能无法写入更快。

  • 一个更有趣的例子是当你在你的程序中做其他事情的时候。例如,如果您有一个需要处理许多并发请求的 Web 服务器,则使用与数据库的异步通信是有意义的 - 因为它不会阻塞您需要处理其他 Web 请求的线程。

总而言之,您应该衡量性能 - 但这可能取决于您的程序中发生的其他事情。我认为将单线程编写器脚本更改为异步不会有任何不同(除了增加一些小开销之外)。

有人讨论这个 API here。开头有人说:

It would be helpful to have asynchronous versions of BeginBinaryImport and BeginBinaryExport so they can be used from e.g. api endpoints without blocking the server.

For Import, given the write happens on Close/Dispose I'm guessing the write methods would not become async

但是,其中一位开发人员评论道:

Write can also block. So you need WriteAsync too.

的确,在许多(甚至大多数)情况下,NpgsqlBinaryImporter 上的 APIs 将同步完成,因为它们只是写入 Npgsql 的内存缓冲区。这不仅适用于单个值写入 (WriteAsync),也适用于 StartRowAsync - 这些 API 中的 none 执行 I/O,除非内部缓冲区已满。

所以实际上,您是通过内存缓冲区将任意长的数据流输出到 PostgreSQL。给定足够的数据(毕竟,这是一个“批量导入”),在某个时候缓冲区将被耗尽并且 API 将被刷新;问题是刷新是应该阻塞调用线程还是异步发生。

所以是的,如果您希望 I/O 异步发生(即使大多数调用不会导致 I/O),使用异步 APIs 确实有意义.调用异步方法(与同步方法相反)的开销应该很小,因此异步方法通常应该是默认的;但要自己测量开销以了解全貌。