All Products
Search
Document Center

ApsaraDB for MongoDB:Impacts testing of SSL encryption on the instance performance

Last Updated:Nov 21, 2024

This topic describes how to test the impacts of SSL encryption on the performance of an ApsaraDB for MongoDB instance.

Test environment

Create an ECS instance and an ApsaraDB for MongoDB instance. For more information, see Create a replica set instance and Create and manage an ECS instance in the console (express version).

The following table describes the settings of the ECS instance and the ApsaraDB for MongoDB instance used in the testing.

  • ApsaraDB for MongoDB instance

    • Instance architecture: replica set instance

    • Number of primary and secondary nodes: three nodes

    • Number of read-only nodes: one node

    • Instance specifications: 1 core and 2 GB of memory (general-purpose)

    • Instance version: MongoDB 4.2

  • ECS Instance

    • Instance type: ecs.e-c1m2.large (2 cores and 4 GB of memory)

Test method

Import test data

In the testing, Yahoo! Cloud Serving Benchmark (YCSB) is used to import 1,000,000 entries to a collection named usertable in a database named ycsb. Run the following command to import the entries:

./bin/ycsb load mongodb -s -p workload=site.ycsb.workloads.CoreWorkload -p recordcount=1000000 -p mongodb.url="mongodb://root:******@dds-bp103175eafac****.mongodb.rds.aliyuncs.com:3717,dds-bp103175eafac****.mongodb.rds.aliyuncs.com:3717,dds-bp103175eafac****.mongodb.rds.aliyuncs.com:3717/admin?readPreference=secondary&replicaSet=mgset-82894753" -threads 8

The collection generated by YCSB has the following structure:

{
  "_id": "user1352498093671118016",
  "field1": "BinData(0,\"L1s/KS8+MlYhISUuIz8qNVx9PTRuNiooNlolPVMzJCE0ODNuP15zLjB6NSU4LV0pPC98LkovOFttOyQkL1chNV8tOlxrKlstPkp3IzkiLTcuLU93ITRqOzEwPScsIEx5L1o5JQ==\")",
  "field0": "BinData(0,\"I0I/NDAmNUkrLFA3PEA5IEI3KC4qKEJ1LCc6JV8rJlo7KUR3MDRqMk95KjhoIlUjLy1qMls9OFU7PTpuKTZsNlUpMVYvNSI4KDlwK1trIUpnMlInKSAmNDg8LSpmOl85PkozMA==\")",
  "field7": "BinData(0,\"NzxyPjUkNiYsOEp1MUIpIDA0M1U/JE1nMjMmODhkIko/LEh1NSRqKl1jKDBqJjMqMiw8PSc6KjgsNEwrLFkpKFh9OzQ+MS86LiB4P1oxLVxjIiQgMCdsM0AhO0QzNTFqI1dpMw==\")",
  "field6": "BinData(0,\"MVhpIj1uI0RlJ1AtP0t/LkAxMVp9MlU3KFJpK0Z3M0M/OE55IFBhPyA6Njp+L1tnJjh4Iic4K1F/Oi9gKz44PFFpPE5jNydqKUJrPyV4KDY2LDd0Nic6PC9wPDhgNykwOj10Iw==\")",
  "field9": "BinData(0,\"OEA/NDRwLilwLzVoMz5oIzVuLStoPlF9NVU5MVd3PDUkPV0nPl07OU05LkYnKDQ8L0VrIkwlJDg+NCRuIitoNjV4PkM9NCt0LyE4PCFkKUI/LVwxKjdsMkgnICsgKFQrJit8Lg==\")",
  "field8": "BinData(0,\"P0J/P0ozMCBwIjJwP0IpJUMlPkhhIkI3MU91OjUmMFM9NEBhJD8+OEdpJDh2OyxmMCQkKUgnJjYoKC0kKVMnKTQuODp6JjVwKSRwI1FhJy88PVAhPiE2OCMsKlYvJF1xOEUrJA==\")",
  "field3": "BinData(0,\"Mjc6KE4zJTEuJCJ4P18lNCZ4NkAtKTAwLFUvMDEsOzUkIEs3MVEzKT4mL1Q5IVF9N0N3O1snIUZ1L0k9IUY7IV8nNiQ+KTJ0I097Llg1MjE0PCQ4KDV2KzYsIDtwNzhuKkQrMA==\")",
  "field2": "BinData(0,\"Jit6L0olLTByJzpuPygyKSE8Li9iK00xIig4PVQhKTBqLkI/O0N9Izs+NVk/ISVmIy14Nko3NyxoI1Z1Pyo4JVt/MylsK1t9PS9yP1kzJFMjPF43P1l5P1xtPDAqLF57IiAsPQ==\")",
  "field5": "BinData(0,\"NSd2NiskPzcmITU4Oz58NzliP0ZpOz5wKiI0OVhlNDx0PlV1OllnNzJiOyMmOF45IEg7OD90KUZnP0g5OUJzK0M/OyN4O15lPzA6LTUgNUw9NDVoL0QrOTssISQ2KCw6KUJlKg==\")",
  "field4": "BinData(0,\"NDNkKjFuPyxsJzEmNzMqLVJzL0YpMTNwMjs8MTRiNEYtLE05P0I9NEU1LS4yNEFpNjUsOUs9JEs1IU5jOF5vIjpiNTMuKSY6K0s1LDp2JEp9KyFiIS1+J1U/LkoxMEp5OzJiIw==\")"
}

Run a test script

YCSB does not support stress testing performed with SSL encryption enabled. Therefore, a test script is used for stress testing in this topic. The script contains the following content.

import time
import pymongo
import datetime
import logging as log
from functools import wraps

log.basicConfig(
    level=log.INFO, format='%(asctime)s - [line %(lineno)d] : %(levelname)s - %(message)s')
console = log.StreamHandler()
console.setLevel(log.INFO)
formatter = log.Formatter(
    '%(asctime)s - [line %(lineno)d] : %(levelname)s - %(message)s')
console.setFormatter(formatter)


def time_recorder():
    def inner(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            start_time = time.time()
            result = func(*args, **kwargs)
            end_time = time.time()
            elapsed_time = (end_time - start_time) * 1000

            print_latency = kwargs.get("print_latency", False)
            return_latency = kwargs.get("return_latency", False)

            if print_latency:
                log.info(
                    f"Function {func.__name__} took {elapsed_time:.4f} millseconds to execute.")

            if return_latency:
                return result, elapsed_time
            return result
        return wrapper
    return inner


@time_recorder()
def build_client_without_ssl(**kwargs):
    client = pymongo.MongoClient(
        "mongodb://root:******@dds-bp103175eafac****.mongodb.rds.aliyuncs.com:3717,dds-bp103175eafac****.mongodb.rds.aliyuncs.com:3717,dds-bp103175eafac****.mongodb.rds.aliyuncs.com:3717/admin?readPreference=secondary&replicaSet=mgset-82894753")
    return client


@time_recorder()
def build_client_with_ssl(**kwargs):
    client = pymongo.MongoClient(
        "mongodb://root:******@dds-bp103175eafac****.mongodb.rds.aliyuncs.com:3717,dds-bp103175eafac****.mongodb.rds.aliyuncs.com:3717,dds-bp103175eafac****.mongodb.rds.aliyuncs.com:3717/admin?readPreference=secondary&replicaSet=mgset-82894753&tls=true&tlsAllowInvalidHostnames=true&tlsCAFile=/root/ApsaraDB-CA-Chain.pem")
    return client


@time_recorder()
def find_one(client, **kwargs):
    client["ycsb"]["usertable"].find_one()


def test_connection():
    client = build_client_without_ssl(print_latency=True)
    find_one(client)

    client = build_client_with_ssl(print_latency=True)
    find_one(client)


test_connection()


@time_recorder()
def test(with_ssl=False, repeat=5000, **kwargs):
    log.info(f"test, with_ssl: {with_ssl}, repeat: {repeat}")
    ssl_suffix = "without-ssl"
    if with_ssl:
        ssl_suffix = "with-ssl"
    suffix = f"{repeat}-{ssl_suffix}.log"
    only_connection_file = f"only-connection-{suffix}"
    first_query_file = f"first-query-{suffix}"
    connection_and_first_query_file = f"connection-and-first-query-{suffix}"

    only_connection_f = open(only_connection_file, 'w')
    first_query_f = open(first_query_file, 'w')
    connection_and_first_query_f = open(connection_and_first_query_file, 'w')

    for _ in range(repeat):
        client = None
        connection_latency = 0

        if with_ssl:
            client, connection_latency = build_client_with_ssl(
                return_latency=True)
        else:
            client, connection_latency = build_client_without_ssl(
                return_latency=True)

        _, query_latency = find_one(client, return_latency=True)

        only_connection_f.write(f"{connection_latency}\n")
        first_query_f.write(f"{query_latency}\n")
        connection_and_first_query_f.write(f"{connection_latency + query_latency}\n")

    only_connection_f.close()
    first_query_f.close()
    connection_and_first_query_f.close()

    log.info(f"test end, write to {only_connection_file}, {first_query_file} and {connection_and_first_query_file}")


test(False, 5000, print_latency=True)
test(True, 5000, print_latency=True)


@time_recorder()
def run(client, log_file, repeat=5000, **kwargs):
    start = time.time()
    log.info(f"start to query, repeat: {repeat}")

    with open(log_file, 'w') as f:
        for _ in range(repeat):
            latency = find_one(client, return_latency=True)
            f.write(f"{latency}\n")

    end = time.time()
    total = (end - start) * 1000
    avg = total / repeat
    log.info(
        f"end query, repeat: {repeat}, total: {total} millseconds, avg: {avg} millseconds")


client = build_client_without_ssl()
run(client, "without-ssl.log")

client = build_client_with_ssl()
run(client, "with-ssl.log")

During the testing, you can modify the following parameters based on your requirements:

  • client: Replace the connection string, username, and password in the parentheses of the pymongo.MongoClient() parameter with the actual information of the ApsaraDB for MongoDB instance.

  • repeat: the number of times that the testing is executed. The testing is repeatedly executed 5,000 times. You can modify the parameter based on your requirements.

The script outputs the following parameters:

  • only-connection: records the time consumed to establish a connection.

  • first-query: records the duration of the first query after a connection is established.

  • connection-and-first-query: records the total duration that starts from the connection establishment to the completion of the first query. The parameter value is the sum of the only-connection and first-query values.

  • latency: records the average duration of 5,000 queries when the client specified by pymongo.MongoClient() is reused.

Test results

The following table describes the average values of 5,000 tests.

Item

Consumed time with SSL encryption disabled (ms)

Consumed time with SSL encryption enabled (ms)

Time consumed to establish a connection

3.7009

19.8515

Duration of the first query after a connection is established

20.7456

43.5884

Total duration from the connection establishment to the completion of the first query

24.4465

63.4399

Average query duration when the specified client is reused

0.7506

0.7643

After testing, the difference between the durations with SSL encryption enabled and disabled mainly lies in the connection establishment. If existing connections are reused, a small difference exists between the average query durations with SSL encryption enabled and disabled. The reasons for the small duration difference mainly include the following two aspects:

  • Additional Round-Trip Time (RTT) exists when a connection is established between your client and server due to the use of Transport Layer Security (TLS).

    If you configure connection pools, the additional RTT overhead of the connection establishment is spread equally. If you reuse the specified client after enabling the SSL encryption feature, the TLS RTT can be minimized during each connection establishment.

  • After a connection is established, the additional overhead is generated when encryption and decryption algorithms are used by the message body.

    CPU can quickly encrypt and decrypt the message body, other system aspects may consume a long time in a query, such as disk I/O and queue waiting, and the impacts of encryption and decryption on the overall performance are relatively small.