diff --git a/docs/config/network.en.md b/docs/config/network.en.md index 6fe6bf8b..547b675d 100644 --- a/docs/config/network.en.md +++ b/docs/config/network.en.md @@ -20,6 +20,7 @@ between clients, OSDs and etcd. - [rdma_max_msg](#rdma_max_msg) - [rdma_max_recv](#rdma_max_recv) - [rdma_max_send](#rdma_max_send) +- [rdma_odp](#rdma_odp) - [peer_connect_interval](#peer_connect_interval) - [peer_connect_timeout](#peer_connect_timeout) - [osd_idle_timeout](#osd_idle_timeout) @@ -68,11 +69,14 @@ but they are not connected to the cluster. - Type: string RDMA device name to use for Vitastor OSD communications (for example, -"rocep5s0f0"). Please note that Vitastor RDMA requires Implicit On-Demand -Paging (Implicit ODP) and Scatter/Gather (SG) support from the RDMA device -to work. For example, Mellanox ConnectX-3 and older adapters don't have -Implicit ODP, so they're unsupported by Vitastor. Run `ibv_devinfo -v` as -root to list available RDMA devices and their features. +"rocep5s0f0"). Now Vitastor supports all adapters, even ones without +ODP support, like Mellanox ConnectX-3 and non-Mellanox cards. + +Versions up to Vitastor 1.2.0 required ODP which is only present in +Mellanox ConnectX >= 4. See also [rdma_odp](#rdma_odp). + +Run `ibv_devinfo -v` as root to list available RDMA devices and their +features. Remember that you also have to configure your network switches if you use RoCE/RoCEv2, otherwise you may experience unstable performance. Refer to @@ -147,6 +151,28 @@ less than `rdma_max_recv` so the receiving side doesn't run out of buffers. Doesn't affect memory usage - additional memory isn't allocated for send operations. +## rdma_odp + +- Type: boolean +- Default: false + +Use RDMA with On-Demand Paging. ODP is currently only available on Mellanox +ConnectX-4 and newer adapters. ODP allows to not register memory explicitly +for RDMA adapter to be able to use it. This, in turn, allows to skip memory +copying during sending. One would think this should improve performance, but +**in reality** RDMA performance with ODP is **drastically** worse. Example +3-node cluster with 8 NVMe in each node and 2*25 GBit/s ConnectX-6 RDMA network +without ODP pushes 3950000 read iops, but only 239000 iops with ODP... + +This happens because Mellanox ODP implementation seems to be based on +message retransmissions when the adapter doesn't know about the buffer yet - +it likely uses standard "RNR retransmissions" (RNR = receiver not ready) +which is generally slow in RDMA/RoCE networks. Here's a presentation about +it from ISPASS-2021 conference: https://tkygtr6.github.io/pub/ISPASS21_slides.pdf + +ODP support is retained in the code just in case a good ODP implementation +appears one day. + ## peer_connect_interval - Type: seconds diff --git a/docs/config/network.ru.md b/docs/config/network.ru.md index 43239dc2..de1a65fb 100644 --- a/docs/config/network.ru.md +++ b/docs/config/network.ru.md @@ -20,6 +20,7 @@ - [rdma_max_msg](#rdma_max_msg) - [rdma_max_recv](#rdma_max_recv) - [rdma_max_send](#rdma_max_send) +- [rdma_odp](#rdma_odp) - [peer_connect_interval](#peer_connect_interval) - [peer_connect_timeout](#peer_connect_timeout) - [osd_idle_timeout](#osd_idle_timeout) @@ -71,12 +72,15 @@ RDMA может быть нужно только если у клиентов е - Тип: строка Название RDMA-устройства для связи с Vitastor OSD (например, "rocep5s0f0"). -Имейте в виду, что поддержка RDMA в Vitastor требует функций устройства -Implicit On-Demand Paging (Implicit ODP) и Scatter/Gather (SG). Например, -адаптеры Mellanox ConnectX-3 и более старые не поддерживают Implicit ODP и -потому не поддерживаются в Vitastor. Запустите `ibv_devinfo -v` от имени -суперпользователя, чтобы посмотреть список доступных RDMA-устройств, их -параметры и возможности. +Сейчас Vitastor поддерживает все модели адаптеров, включая те, у которых +нет поддержки ODP, то есть вы можете использовать RDMA с ConnectX-3 и +картами производства не Mellanox. + +Версии Vitastor до 1.2.0 включительно требовали ODP, который есть только +на Mellanox ConnectX 4 и более новых. См. также [rdma_odp](#rdma_odp). + +Запустите `ibv_devinfo -v` от имени суперпользователя, чтобы посмотреть +список доступных RDMA-устройств, их параметры и возможности. Обратите внимание, что если вы используете RoCE/RoCEv2, вам также необходимо правильно настроить для него коммутаторы, иначе вы можете столкнуться с @@ -155,6 +159,29 @@ OSD в любом случае согласовывают реальное зн Не влияет на потребление памяти - дополнительная память на операции отправки не выделяется. +## rdma_odp + +- Тип: булево (да/нет) +- Значение по умолчанию: false + +Использовать RDMA с On-Demand Paging. ODP - функция, доступная пока что +исключительно на адаптерах Mellanox ConnectX-4 и более новых. ODP позволяет +не регистрировать память для её использования RDMA-картой. Благодаря этому +можно не копировать данные при отправке их в сеть и, казалось бы, это должно +улучшать производительность - но **по факту** получается так, что +производительность только ухудшается, причём сильно. Пример - на 3-узловом +кластере с 8 NVMe в каждом узле и сетью 2*25 Гбит/с на чтение с RDMA без ODP +удаётся снять 3950000 iops, а с ODP - всего 239000 iops... + +Это происходит из-за того, что реализация ODP у Mellanox неоптимальная и +основана на повторной передаче сообщений, когда карте не известен буфер - +вероятно, на стандартных "RNR retransmission" (RNR = receiver not ready). +А данные повторные передачи в RDMA/RoCE - всегда очень медленная штука. +Презентация на эту тему с конференции ISPASS-2021: https://tkygtr6.github.io/pub/ISPASS21_slides.pdf + +Возможность использования ODP сохранена в коде на случай, если вдруг в один +прекрасный день появится хорошая реализация ODP. + ## peer_connect_interval - Тип: секунды diff --git a/docs/config/src/network.yml b/docs/config/src/network.yml index 9dea2d0f..b95f48e2 100644 --- a/docs/config/src/network.yml +++ b/docs/config/src/network.yml @@ -48,11 +48,14 @@ type: string info: | RDMA device name to use for Vitastor OSD communications (for example, - "rocep5s0f0"). Please note that Vitastor RDMA requires Implicit On-Demand - Paging (Implicit ODP) and Scatter/Gather (SG) support from the RDMA device - to work. For example, Mellanox ConnectX-3 and older adapters don't have - Implicit ODP, so they're unsupported by Vitastor. Run `ibv_devinfo -v` as - root to list available RDMA devices and their features. + "rocep5s0f0"). Now Vitastor supports all adapters, even ones without + ODP support, like Mellanox ConnectX-3 and non-Mellanox cards. + + Versions up to Vitastor 1.2.0 required ODP which is only present in + Mellanox ConnectX >= 4. See also [rdma_odp](#rdma_odp). + + Run `ibv_devinfo -v` as root to list available RDMA devices and their + features. Remember that you also have to configure your network switches if you use RoCE/RoCEv2, otherwise you may experience unstable performance. Refer to @@ -61,12 +64,15 @@ PFC (Priority Flow Control) and ECN (Explicit Congestion Notification). info_ru: | Название RDMA-устройства для связи с Vitastor OSD (например, "rocep5s0f0"). - Имейте в виду, что поддержка RDMA в Vitastor требует функций устройства - Implicit On-Demand Paging (Implicit ODP) и Scatter/Gather (SG). Например, - адаптеры Mellanox ConnectX-3 и более старые не поддерживают Implicit ODP и - потому не поддерживаются в Vitastor. Запустите `ibv_devinfo -v` от имени - суперпользователя, чтобы посмотреть список доступных RDMA-устройств, их - параметры и возможности. + Сейчас Vitastor поддерживает все модели адаптеров, включая те, у которых + нет поддержки ODP, то есть вы можете использовать RDMA с ConnectX-3 и + картами производства не Mellanox. + + Версии Vitastor до 1.2.0 включительно требовали ODP, который есть только + на Mellanox ConnectX 4 и более новых. См. также [rdma_odp](#rdma_odp). + + Запустите `ibv_devinfo -v` от имени суперпользователя, чтобы посмотреть + список доступных RDMA-устройств, их параметры и возможности. Обратите внимание, что если вы используете RoCE/RoCEv2, вам также необходимо правильно настроить для него коммутаторы, иначе вы можете столкнуться с @@ -160,6 +166,45 @@ у принимающей стороны в процессе работы не заканчивались буферы на приём. Не влияет на потребление памяти - дополнительная память на операции отправки не выделяется. +- name: rdma_odp + type: bool + default: false + online: false + info: | + Use RDMA with On-Demand Paging. ODP is currently only available on Mellanox + ConnectX-4 and newer adapters. ODP allows to not register memory explicitly + for RDMA adapter to be able to use it. This, in turn, allows to skip memory + copying during sending. One would think this should improve performance, but + **in reality** RDMA performance with ODP is **drastically** worse. Example + 3-node cluster with 8 NVMe in each node and 2*25 GBit/s ConnectX-6 RDMA network + without ODP pushes 3950000 read iops, but only 239000 iops with ODP... + + This happens because Mellanox ODP implementation seems to be based on + message retransmissions when the adapter doesn't know about the buffer yet - + it likely uses standard "RNR retransmissions" (RNR = receiver not ready) + which is generally slow in RDMA/RoCE networks. Here's a presentation about + it from ISPASS-2021 conference: https://tkygtr6.github.io/pub/ISPASS21_slides.pdf + + ODP support is retained in the code just in case a good ODP implementation + appears one day. + info_ru: | + Использовать RDMA с On-Demand Paging. ODP - функция, доступная пока что + исключительно на адаптерах Mellanox ConnectX-4 и более новых. ODP позволяет + не регистрировать память для её использования RDMA-картой. Благодаря этому + можно не копировать данные при отправке их в сеть и, казалось бы, это должно + улучшать производительность - но **по факту** получается так, что + производительность только ухудшается, причём сильно. Пример - на 3-узловом + кластере с 8 NVMe в каждом узле и сетью 2*25 Гбит/с на чтение с RDMA без ODP + удаётся снять 3950000 iops, а с ODP - всего 239000 iops... + + Это происходит из-за того, что реализация ODP у Mellanox неоптимальная и + основана на повторной передаче сообщений, когда карте не известен буфер - + вероятно, на стандартных "RNR retransmission" (RNR = receiver not ready). + А данные повторные передачи в RDMA/RoCE - всегда очень медленная штука. + Презентация на эту тему с конференции ISPASS-2021: https://tkygtr6.github.io/pub/ISPASS21_slides.pdf + + Возможность использования ODP сохранена в коде на случай, если вдруг в один + прекрасный день появится хорошая реализация ODP. - name: peer_connect_interval type: sec min: 1 diff --git a/src/messenger.cpp b/src/messenger.cpp index 695404b7..e1cb9db8 100644 --- a/src/messenger.cpp +++ b/src/messenger.cpp @@ -22,7 +22,7 @@ void osd_messenger_t::init() { rdma_context = msgr_rdma_context_t::create( rdma_device != "" ? rdma_device.c_str() : NULL, - rdma_port_num, rdma_gid_index, rdma_mtu, log_level + rdma_port_num, rdma_gid_index, rdma_mtu, rdma_odp, log_level ); if (!rdma_context) { @@ -167,6 +167,7 @@ void osd_messenger_t::parse_config(const json11::Json & config) this->rdma_max_msg = config["rdma_max_msg"].uint64_value(); if (!this->rdma_max_msg || this->rdma_max_msg > 128*1024*1024) this->rdma_max_msg = 129*1024; + this->rdma_odp = config["rdma_odp"].bool_value(); #endif this->receive_buffer_size = (uint32_t)config["tcp_header_buffer_size"].uint64_value(); if (!this->receive_buffer_size || this->receive_buffer_size > 1024*1024*1024) diff --git a/src/messenger.h b/src/messenger.h index e35f8c7d..282586a9 100644 --- a/src/messenger.h +++ b/src/messenger.h @@ -131,6 +131,7 @@ protected: msgr_rdma_context_t *rdma_context = NULL; uint64_t rdma_max_sge = 0, rdma_max_send = 0, rdma_max_recv = 0; uint64_t rdma_max_msg = 0; + bool rdma_odp = false; #endif std::vector read_ready_clients; @@ -197,7 +198,9 @@ protected: void handle_reply_ready(osd_op_t *op); #ifdef WITH_RDMA - bool try_send_rdma(osd_client_t *cl); + void try_send_rdma(osd_client_t *cl); + void try_send_rdma_odp(osd_client_t *cl); + void try_send_rdma_nodp(osd_client_t *cl); bool try_recv_rdma(osd_client_t *cl); void handle_rdma_events(); #endif diff --git a/src/msgr_rdma.cpp b/src/msgr_rdma.cpp index d39105de..9eeb8655 100644 --- a/src/msgr_rdma.cpp +++ b/src/msgr_rdma.cpp @@ -47,11 +47,29 @@ msgr_rdma_connection_t::~msgr_rdma_connection_t() if (qp) ibv_destroy_qp(qp); if (recv_buffers.size()) + { for (auto b: recv_buffers) - free(b); + { + if (b.mr) + ibv_dereg_mr(b.mr); + free(b.buf); + } + recv_buffers.clear(); + } + if (send_out.mr) + { + ibv_dereg_mr(send_out.mr); + send_out.mr = NULL; + } + if (send_out.buf) + { + free(send_out.buf); + send_out.buf = NULL; + } + send_out_size = 0; } -msgr_rdma_context_t *msgr_rdma_context_t::create(const char *ib_devname, uint8_t ib_port, uint8_t gid_index, uint32_t mtu, int log_level) +msgr_rdma_context_t *msgr_rdma_context_t::create(const char *ib_devname, uint8_t ib_port, uint8_t gid_index, uint32_t mtu, bool odp, int log_level) { int res; ibv_device **dev_list = NULL; @@ -136,21 +154,27 @@ msgr_rdma_context_t *msgr_rdma_context_t::create(const char *ib_devname, uint8_t fprintf(stderr, "Couldn't query RDMA device for its features\n"); goto cleanup; } - if (!(ctx->attrx.odp_caps.general_caps & IBV_ODP_SUPPORT) || + ctx->odp = odp; + if (ctx->odp && + (!(ctx->attrx.odp_caps.general_caps & IBV_ODP_SUPPORT) || !(ctx->attrx.odp_caps.general_caps & IBV_ODP_SUPPORT_IMPLICIT) || !(ctx->attrx.odp_caps.per_transport_caps.rc_odp_caps & IBV_ODP_SUPPORT_SEND) || - !(ctx->attrx.odp_caps.per_transport_caps.rc_odp_caps & IBV_ODP_SUPPORT_RECV)) + !(ctx->attrx.odp_caps.per_transport_caps.rc_odp_caps & IBV_ODP_SUPPORT_RECV))) { - fprintf(stderr, "The RDMA device isn't implicit ODP (On-Demand Paging) capable or does not support RC send and receive with ODP\n"); - goto cleanup; + ctx->odp = false; + if (log_level > 0) + fprintf(stderr, "The RDMA device isn't implicit ODP (On-Demand Paging) capable, disabling it\n"); } } - ctx->mr = ibv_reg_mr(ctx->pd, NULL, SIZE_MAX, IBV_ACCESS_LOCAL_WRITE | IBV_ACCESS_ON_DEMAND); - if (!ctx->mr) + if (ctx->odp) { - fprintf(stderr, "Couldn't register RDMA memory region\n"); - goto cleanup; + ctx->mr = ibv_reg_mr(ctx->pd, NULL, SIZE_MAX, IBV_ACCESS_LOCAL_WRITE | IBV_ACCESS_ON_DEMAND); + if (!ctx->mr) + { + fprintf(stderr, "Couldn't register RDMA memory region\n"); + goto cleanup; + } } ctx->channel = ibv_create_comp_channel(ctx->context); @@ -365,12 +389,34 @@ static void try_send_rdma_wr(osd_client_t *cl, ibv_sge *sge, int op_sge) cl->rdma_conn->cur_send++; } -bool osd_messenger_t::try_send_rdma(osd_client_t *cl) +static int try_send_rdma_copy(osd_client_t *cl, uint8_t *dst, int dst_len) +{ + auto rc = cl->rdma_conn; + int total_dst_len = dst_len; + while (dst_len > 0 && rc->send_pos < cl->send_list.size()) + { + iovec & iov = cl->send_list[rc->send_pos]; + uint32_t len = (uint32_t)(iov.iov_len-rc->send_buf_pos < dst_len + ? iov.iov_len-rc->send_buf_pos : dst_len); + memcpy(dst, iov.iov_base+rc->send_buf_pos, len); + dst += len; + dst_len -= len; + rc->send_buf_pos += len; + if (rc->send_buf_pos >= iov.iov_len) + { + rc->send_pos++; + rc->send_buf_pos = 0; + } + } + return total_dst_len-dst_len; +} + +void osd_messenger_t::try_send_rdma_odp(osd_client_t *cl) { auto rc = cl->rdma_conn; if (!cl->send_list.size() || rc->cur_send >= rc->max_send) { - return true; + return; } uint64_t op_size = 0, op_sge = 0; ibv_sge sge[rc->max_sge]; @@ -408,15 +454,70 @@ bool osd_messenger_t::try_send_rdma(osd_client_t *cl) rc->send_sizes.push_back(op_size); try_send_rdma_wr(cl, sge, op_sge); } - return true; } -static void try_recv_rdma_wr(osd_client_t *cl, void *buf) +void osd_messenger_t::try_send_rdma_nodp(osd_client_t *cl) +{ + auto rc = cl->rdma_conn; + if (!rc->send_out_size) + { + // Allocate send ring buffer, if not yet + rc->send_out_size = rc->max_msg*rdma_max_send; + rc->send_out.buf = malloc_or_die(rc->send_out_size); + if (!rdma_context->odp) + { + rc->send_out.mr = ibv_reg_mr(rdma_context->pd, rc->send_out.buf, rc->send_out_size, 0); + if (!rc->send_out.mr) + { + fprintf(stderr, "Failed to register RDMA memory region: %s\n", strerror(errno)); + exit(1); + } + } + } + // Copy data into the buffer and send it + uint8_t *dst = NULL; + int dst_len = 0; + int copied = 1; + while (!rc->send_out_full && copied > 0 && rc->cur_send < rc->max_send) + { + dst = (uint8_t*)rc->send_out.buf + rc->send_out_pos; + dst_len = (rc->send_out_pos < rc->send_out_size ? rc->send_out_size-rc->send_out_pos : rc->send_done_pos-rc->send_out_pos); + if (dst_len > rc->max_msg) + dst_len = rc->max_msg; + copied = try_send_rdma_copy(cl, dst, dst_len); + if (copied > 0) + { + rc->send_out_pos += copied; + if (rc->send_out_pos == rc->send_out_size) + rc->send_out_pos = 0; + assert(rc->send_out_pos < rc->send_out_size); + if (rc->send_out_pos >= rc->send_done_pos) + rc->send_out_full = true; + ibv_sge sge = { + .addr = (uintptr_t)dst, + .length = (uint32_t)copied, + .lkey = rdma_context->odp ? rdma_context->mr->lkey : rc->send_out.mr->lkey, + }; + try_send_rdma_wr(cl, &sge, 1); + rc->send_sizes.push_back(copied); + } + } +} + +void osd_messenger_t::try_send_rdma(osd_client_t *cl) +{ + if (rdma_context->odp) + try_send_rdma_odp(cl); + else + try_send_rdma_nodp(cl); +} + +static void try_recv_rdma_wr(osd_client_t *cl, msgr_rdma_buf_t b) { ibv_sge sge = { - .addr = (uintptr_t)buf, + .addr = (uintptr_t)b.buf, .length = (uint32_t)cl->rdma_conn->max_msg, - .lkey = cl->rdma_conn->ctx->mr->lkey, + .lkey = cl->rdma_conn->ctx->odp ? cl->rdma_conn->ctx->mr->lkey : b.mr->lkey, }; ibv_recv_wr *bad_wr = NULL; ibv_recv_wr wr = { @@ -438,9 +539,19 @@ bool osd_messenger_t::try_recv_rdma(osd_client_t *cl) auto rc = cl->rdma_conn; while (rc->cur_recv < rc->max_recv) { - void *buf = malloc_or_die(rc->max_msg); - rc->recv_buffers.push_back(buf); - try_recv_rdma_wr(cl, buf); + msgr_rdma_buf_t b; + b.buf = malloc_or_die(rc->max_msg); + if (!rdma_context->odp) + { + b.mr = ibv_reg_mr(rdma_context->pd, b.buf, rc->max_msg, IBV_ACCESS_LOCAL_WRITE); + if (!b.mr) + { + fprintf(stderr, "Failed to register RDMA memory region: %s\n", strerror(errno)); + exit(1); + } + } + rc->recv_buffers.push_back(b); + try_recv_rdma_wr(cl, b); } return true; } @@ -492,7 +603,7 @@ void osd_messenger_t::handle_rdma_events() if (!is_send) { rc->cur_recv--; - if (!handle_read_buffer(cl, rc->recv_buffers[rc->next_recv_buf], wc[i].byte_len)) + if (!handle_read_buffer(cl, rc->recv_buffers[rc->next_recv_buf].buf, wc[i].byte_len)) { // handle_read_buffer may stop the client continue; @@ -505,6 +616,14 @@ void osd_messenger_t::handle_rdma_events() rc->cur_send--; uint64_t sent_size = rc->send_sizes.at(0); rc->send_sizes.erase(rc->send_sizes.begin(), rc->send_sizes.begin()+1); + if (!rdma_context->odp) + { + rc->send_done_pos += sent_size; + rc->send_out_full = false; + if (rc->send_done_pos == rc->send_out_size) + rc->send_done_pos = 0; + assert(rc->send_done_pos < rc->send_out_size); + } int send_pos = 0, send_buf_pos = 0; while (sent_size > 0) { diff --git a/src/msgr_rdma.h b/src/msgr_rdma.h index 7c56f4a1..ab8049f4 100644 --- a/src/msgr_rdma.h +++ b/src/msgr_rdma.h @@ -23,6 +23,7 @@ struct msgr_rdma_context_t ibv_device *dev = NULL; ibv_device_attr_ex attrx; ibv_pd *pd = NULL; + bool odp = false; ibv_mr *mr = NULL; ibv_comp_channel *channel = NULL; ibv_cq *cq = NULL; @@ -35,10 +36,16 @@ struct msgr_rdma_context_t int max_cqe = 0; int used_max_cqe = 0; - static msgr_rdma_context_t *create(const char *ib_devname, uint8_t ib_port, uint8_t gid_index, uint32_t mtu, int log_level); + static msgr_rdma_context_t *create(const char *ib_devname, uint8_t ib_port, uint8_t gid_index, uint32_t mtu, bool odp, int log_level); ~msgr_rdma_context_t(); }; +struct msgr_rdma_buf_t +{ + void *buf = NULL; + ibv_mr *mr = NULL; +}; + struct msgr_rdma_connection_t { msgr_rdma_context_t *ctx = NULL; @@ -50,8 +57,11 @@ struct msgr_rdma_connection_t int send_pos = 0, send_buf_pos = 0; int next_recv_buf = 0; - std::vector recv_buffers; + std::vector recv_buffers; std::vector send_sizes; + msgr_rdma_buf_t send_out; + int send_out_pos = 0, send_done_pos = 0, send_out_size = 0; + bool send_out_full = false; ~msgr_rdma_connection_t(); static msgr_rdma_connection_t *create(msgr_rdma_context_t *ctx, uint32_t max_send, uint32_t max_recv, uint32_t max_sge, uint32_t max_msg);