Repository: StackStorm/st2

Issue:
MongoDB collection/index name used by ST2 is too long for AWS DocumentDB
##### SUMMARY

While migrating from ST2 v2.9 to ST2 v3.0, we encountered the following problem: one MongoDB index name used by ST2, namely `st2.action_execution_scheduling_queue_item_d_b.scheduled_start_timestamp` (C.F. [st2/st2common/st2common/models/db/execution_queue.py](https://github.com/StackStorm/st2/blob/master/st2common/st2common/models/db/execution_queue.py)) **is too long for AWS DocumentDB**. It _does_ work with an **official** MongoDB server.

AWS DocumentDB _should_ be compatible/honor the official limits of MongoDB (here, 127 characters for fully qualified index names), but I contacted the AWS DocumentDB support which confirmed me that it was **a known issue in DocumentDB and that they didn't have an estimated time of resolution for this problem**. They suggested me to **shorten the index name** if I can, but I think it's currently not customizable (which is okay, this should not be needed).

I realize that **this is not a StackStorm bug _per se_**, however AWS DocumentDB is a big player and it might be interesting for you to shorten this index name so ST2 users/customers can use DocumentDB with ST2 V3+.


##### ISSUE TYPE
 - Bug Report


##### STACKSTORM VERSION
st2 3.0.0, on Python 2.7.12


##### OS / ENVIRONMENT / INSTALL METHOD
N/A


##### STEPS TO REPRODUCE
* Install StackStorm V3.0+
* Configure an AWS DocumentDB as the MongoDB server
* Run a command that will try to create the DB, e.g. `st2ctl reload --register-all`

##### EXPECTED RESULTS
`st2ctl reload --register-all` should successfully create the DB and work.

##### ACTUAL RESULTS
`st2ctl reload --register-all` exits with an error because it can't create the index name `st2.action_execution_scheduling_queue_item_d_b.scheduled_start_timestamp`.



Discussion:
Thanks for reporting this.

As you have said - that's not a bug in StackStorm per say, because we officially only support MongoDB 3.6 and 4.0 on Ubuntu Bionic.

Having said that, if a change is relatively easy and ensures it also works with AWS DocumentDB, then I'm fine with it.

There is a bigger question here though - in the future, we plan to use and support MongoDB 4.0 (that's already the case for Ubuntu Bionic packages). As far as I know, AWS only claims API compatibility with 3.6 because of the licensing change on the MongoDB side.

Right now, we don't actually utilize any specific functionality from 4.0 yet, but we might in the future.
@GuiTeK On a related note - do you have information on what's the current limit for index name on AWS DocumentDB?

It looks like it may be 70 characters, but it would be good to confirm that.

Resolving patch:
diff --git a/st2common/st2common/models/db/execution_queue.py b/st2common/st2common/models/db/execution_queue.py
--- a/st2common/st2common/models/db/execution_queue.py
+++ b/st2common/st2common/models/db/execution_queue.py
@@ -61,10 +61,13 @@ class ActionExecutionSchedulingQueueItemDB(stormbase.StormFoundationDB,
 
     meta = {
         'indexes': [
-            {'fields': ['action_execution_id']},
-            {'fields': ['liveaction_id']},
-            {'fields': ['original_start_timestamp']},
-            {'fields': ['scheduled_start_timestamp']},
+            # NOTE: We limit index names to 65 characters total for compatibility with AWS
+            # DocumentDB.
+            # See https://github.com/StackStorm/st2/pull/4690 for details.
+            {'fields': ['action_execution_id'], 'name': 'ac_exc_id'},
+            {'fields': ['liveaction_id'], 'name': 'lv_ac_id'},
+            {'fields': ['original_start_timestamp'], 'name': 'orig_s_ts'},
+            {'fields': ['scheduled_start_timestamp'], 'name': 'schd_s_ts'},
         ]
     }
 
diff --git a/st2tests/st2tests/base.py b/st2tests/st2tests/base.py
--- a/st2tests/st2tests/base.py
+++ b/st2tests/st2tests/base.py
@@ -66,6 +66,7 @@
 import st2common.models.db.runner as runner_model
 import st2common.models.db.execution as execution_model
 import st2common.models.db.executionstate as executionstate_model
+import st2common.models.db.execution_queue as execution_queue_model
 import st2common.models.db.liveaction as liveaction_model
 import st2common.models.db.actionalias as actionalias_model
 import st2common.models.db.policy as policy_model
@@ -111,6 +112,7 @@
 ALL_MODELS.extend(runner_model.MODELS)
 ALL_MODELS.extend(execution_model.MODELS)
 ALL_MODELS.extend(executionstate_model.MODELS)
+ALL_MODELS.extend(execution_queue_model.MODELS)
 ALL_MODELS.extend(liveaction_model.MODELS)
 ALL_MODELS.extend(actionalias_model.MODELS)
 ALL_MODELS.extend(policy_model.MODELS)


Test patch:
diff --git a/st2common/tests/unit/test_db.py b/st2common/tests/unit/test_db.py
--- a/st2common/tests/unit/test_db.py
+++ b/st2common/tests/unit/test_db.py
@@ -38,6 +38,9 @@
 from st2common.persistence.trigger import TriggerType, Trigger, TriggerInstance
 from st2tests import DbTestCase
 
+from unittest2 import TestCase
+from st2tests.base import ALL_MODELS
+
 
 __all__ = [
     'DbConnectionTestCase',
@@ -51,6 +54,38 @@
 DUMMY_DESCRIPTION = 'Sample Description.'
 
 
+class DbIndexNameTestCase(TestCase):
+    """
+    Test which verifies that model index name are not longer than the specified limit.
+    """
+    LIMIT = 65
+
+    def test_index_name_length(self):
+        db_name = 'st2'
+
+        for model in ALL_MODELS:
+            collection_name = model._get_collection_name()
+            model_indexes = model._meta['index_specs']
+
+            for index_specs in model_indexes:
+                index_name = index_specs.get('name', None)
+                if index_name:
+                    # Custom index name defined by the developer
+                    index_field_name = index_name
+                else:
+                    # No explicit index name specified, one is auto-generated using
+                    # <db name>.<collection name>.<index field names> schema
+                    index_fields = dict(index_specs['fields']).keys()
+                    index_field_name = '.'.join(index_fields)
+
+                index_name = '%s.%s.%s' % (db_name, collection_name, index_field_name)
+
+                if len(index_name) > self.LIMIT:
+                    self.fail('Index name "%s" for model "%s" is longer than %s characters. '
+                              'Please manually define name for this index so it\'s shorter than '
+                              'that' % (index_name, model.__name__, self.LIMIT))
+
+
 class DbConnectionTestCase(DbTestCase):
 
     def test_check_connect(self):