From de90020c0f01dedb9e544e36df80ae8e616c371c Mon Sep 17 00:00:00 2001 From: Nigel Date: Thu, 14 Oct 2021 15:03:49 -0600 Subject: [PATCH] Set some sensible options Including normalilzing the transaction isolation level and setting some reasonable tcp timeouts for postgres --- InvenTree/InvenTree/settings.py | 95 ++++++++++++++++++++++++++++----- 1 file changed, 82 insertions(+), 13 deletions(-) diff --git a/InvenTree/InvenTree/settings.py b/InvenTree/InvenTree/settings.py index 2095cab533..ef1a33ce81 100644 --- a/InvenTree/InvenTree/settings.py +++ b/InvenTree/InvenTree/settings.py @@ -457,32 +457,101 @@ Ref: https://docs.djangoproject.com/en/3.2/ref/settings/#std:setting-OPTIONS """ # 'OPTIONS' or 'options' can be specified in config.yaml -db_options = db_config.get('OPTIONS', db_config.get('options', {})) +# Set useful sensible timeouts for a transactional webserver to communicate +# with its database server, that is, if the webserver is having issues +# connecting to the database server (such as a replica failover) don't sit and +# wait for possibly an hour or more, just tell the client something went wrong +# and let the client retry when they want to. +db_options = db_config.get("OPTIONS", db_config.get("options", {})) # Specific options for postgres backend -if 'postgres' in db_engine: - from psycopg2.extensions import ISOLATION_LEVEL_READ_COMMITTED, ISOLATION_LEVEL_SERIALIZABLE +if "postgres" in db_engine: + from psycopg2.extensions import ( + ISOLATION_LEVEL_READ_COMMITTED, + ISOLATION_LEVEL_SERIALIZABLE, + ) # Connection timeout - if 'connect_timeout' not in db_options: - db_options['connect_timeout'] = int(os.getenv('INVENTREE_DB_TIMEOUT', 2)) + if "connect_timeout" not in db_options: + # The DB server is in the same data center, it should not take very + # long to connect to the database server + # # seconds, 2 is minium allowed by libpq + db_options["connect_timeout"] = int( + os.getenv("INVENTREE_DB_TIMEOUT", 2) + ) + + # Setup TCP keepalive + # DB server is in the same DC, it should not become unresponsive for + # very long. With the defaults below we wait 5 seconds for the network + # issue to resolve itself. It it that doesn't happen whatever happened + # is probably fatal and no amount of waiting is going to fix it. + # # 0 - TCP Keepalives disabled; 1 - enabled + if "keepalives" not in db_options: + db_options["keepalives"] = int( + os.getenv("INVENTREE_DB_TCP_KEEPALIVES", "1") + ) + # # Seconds after connection is idle to send keep alive + if "keepalives_idle" not in db_options: + db_options["keepalives_idle"] = int( + os.getenv("INVENTREE_DB_TCP_KEEPALIVES_IDLE", "1") + ) + # # Seconds after missing ACK to send another keep alive + if "keepalives_interval" not in db_options: + db_options["keepalives_interval"] = int( + os.getenv("INVENTREE_DB_TCP_KEEPALIVES_INTERVAL", "1") + ) + # # Number of missing ACKs before we close the connection + if "keepalives_count" not in db_options: + db_options["keepalives_count"] = int( + os.getenv("INVENTREE_DB_TCP_KEEPALIVES_COUNT", "5") + ) + # # Milliseconds for how long pending data should remain unacked + # by the remote server + # TODO: Supported starting in PSQL 11 + # "tcp_user_timeout": int(os.getenv("PGTCP_USER_TIMEOUT", "1000"), # Postgres's default isolation level is Read Committed which is # normally fine, but most developers think the database server is # actually going to do Serializable type checks on the queries to # protect against simultaneous changes. - if 'isolation_level' not in db_options: - serializable = _is_true(os.getenv("PG_ISOLATION_SERIALIZABLE", "true")) - db_options['isolation_level'] = ISOLATION_LEVEL_SERIALIZABLE if serializable else ISOLATION_LEVEL_READ_COMMITTED + # https://www.postgresql.org/docs/devel/transaction-iso.html + # https://docs.djangoproject.com/en/3.2/ref/databases/#isolation-level + if "isolation_level" not in db_options: + serializable = _is_true( + os.getenv("INVENTREE_DB_ISOLATION_SERIALIZABLE", "true") + ) + db_options["isolation_level"] = ( + ISOLATION_LEVEL_SERIALIZABLE + if serializable + else ISOLATION_LEVEL_READ_COMMITTED + ) # Specific options for MySql / MariaDB backend -if 'mysql' in db_engine: - # TODO - pass +if "mysql" in db_engine: + # TODO TCP time outs and keepalives + + # MariaDB's default isolation level is Repeatable Read which is + # normally fine, but most developers think the database server is + # actually going to Serializable type checks on the queries to + # protect against siumltaneous changes. + # https://mariadb.com/kb/en/mariadb-transactions-and-isolation-levels-for-sql-server-users/#changing-the-isolation-level + # https://docs.djangoproject.com/en/3.2/ref/databases/#mysql-isolation-level + if "isolation_level" not in db_options: + serializable = _is_true( + os.getenv("INVENTREE_DB_ISOLATION_SERIALIZABLE", "true") + ) + db_options["isolation_level"] = ( + "serializable" if serializable else "read committed" + ) # Specific options for sqlite backend -if 'sqlite' in db_engine: - # TODO +if "sqlite" in db_engine: + # TODO: Verify timeouts are not an issue because no network is involved for SQLite + + # SQLite's default isolation level is Serializable due to SQLite's + # single writer implementation. Presumably as a result of this, it is + # not possible to implement any lower isolation levels in SQLite. + # https://www.sqlite.org/isolation.html pass # Provide OPTIONS dict back to the database configuration dict