1+ import abc
12import typing
23from typing import Any , Iterable
34
5+ import structlog
46from boto3 .dynamodb .conditions import Key
57from django .conf import settings
68from django .core .exceptions import ObjectDoesNotExist
9+ from django .db .models import prefetch_related_objects
710
811from environments .dynamodb .constants import (
912 DYNAMODB_MAX_BATCH_WRITE_ITEM_COUNT ,
1215)
1316from environments .dynamodb .types import IdentityOverridesV2Changeset
1417from environments .dynamodb .utils import (
18+ estimate_document_size ,
1519 get_environments_v2_identity_override_document_key ,
1620)
21+ from environments .metrics import (
22+ flagsmith_dynamo_environment_document_compression_ratio ,
23+ flagsmith_dynamo_environment_document_size_bytes ,
24+ )
25+ from integrations .flagsmith .client import get_client
1726from util .mappers import (
27+ map_environment_to_compressed_environment_document ,
28+ map_environment_to_compressed_environment_v2_document ,
1829 map_environment_to_environment_document ,
1930 map_environment_to_environment_v2_document ,
2031 map_identity_override_to_identity_override_document ,
2738 from mypy_boto3_dynamodb .type_defs import QueryInputRequestTypeDef
2839
2940 from environments .models import Environment
41+ from util .dataclasses import CompressedEnvironmentDocument
42+
43+ logger = structlog .get_logger ("dynamodb" )
3044
3145
32- class BaseDynamoEnvironmentWrapper (BaseDynamoWrapper ):
46+ class BaseDynamoEnvironmentWrapper (BaseDynamoWrapper , abc . ABC ):
3347 def write_environment (self , environment : "Environment" ) -> None :
3448 self .write_environments ([environment ])
3549
3650 def write_environments (self , environments : Iterable ["Environment" ]) -> None :
37- raise NotImplementedError ()
51+ self ._write_environments (environments )
52+
53+ @abc .abstractmethod
54+ def _map_environment_document (
55+ self ,
56+ environment : "Environment" ,
57+ ) -> dict [str , Any ]: ...
58+
59+ @abc .abstractmethod
60+ def _map_compressed_environment_document (
61+ self ,
62+ environment : "Environment" ,
63+ ) -> "CompressedEnvironmentDocument" : ...
64+
65+ def _write_environments (self , environments : Iterable ["Environment" ]) -> None :
66+ flagsmith_client = get_client ("local" , local_eval = True )
67+ prefetch_related_objects (
68+ environments ,
69+ "project__organisation" ,
70+ "project__organisation__subscription" ,
71+ )
72+
73+ assert self .table
74+ with self .table .batch_writer () as writer :
75+ for environment in environments :
76+ organisation = environment .project .organisation
77+ if flagsmith_client .get_identity_flags (
78+ organisation .flagsmith_identifier ,
79+ traits = organisation .flagsmith_on_flagsmith_api_traits ,
80+ ).is_feature_enabled ("compress_dynamo_documents" ):
81+ result = self ._map_compressed_environment_document (environment )
82+ writer .put_item (Item = result .document )
83+
84+ flagsmith_dynamo_environment_document_size_bytes .labels (
85+ table = self .get_table_name (),
86+ compressed = "true" ,
87+ ).observe (result .compressed_size_bytes )
88+ flagsmith_dynamo_environment_document_compression_ratio .labels (
89+ table = self .get_table_name (),
90+ ).observe (result .compression_ratio )
91+ logger .info (
92+ "environment-document-compressed" ,
93+ environment_id = environment .id ,
94+ environment_api_key = environment .api_key ,
95+ )
96+ else :
97+ item = self ._map_environment_document (environment )
98+ writer .put_item (Item = item )
99+
100+ flagsmith_dynamo_environment_document_size_bytes .labels (
101+ table = self .get_table_name (),
102+ compressed = "false" ,
103+ ).observe (estimate_document_size (item ))
38104
39105
40106class DynamoEnvironmentWrapper (BaseDynamoEnvironmentWrapper ):
41107 def get_table_name (self ) -> str | None : # type: ignore[override]
42108 return settings .ENVIRONMENTS_TABLE_NAME_DYNAMO
43109
44- def write_environments (self , environments : Iterable ["Environment" ]): # type: ignore[no-untyped-def]
45- with self .table .batch_writer () as writer : # type: ignore[union-attr]
46- for environment in environments :
47- writer .put_item (
48- Item = map_environment_to_environment_document (environment ),
49- )
110+ def _map_environment_document (self , environment : "Environment" ) -> dict [str , Any ]:
111+ return map_environment_to_environment_document (environment )
112+
113+ def _map_compressed_environment_document (
114+ self , environment : "Environment"
115+ ) -> "CompressedEnvironmentDocument" :
116+ return map_environment_to_compressed_environment_document (environment )
50117
51118 def get_item (self , api_key : str ) -> dict : # type: ignore[type-arg]
52119 try :
@@ -59,7 +126,7 @@ def delete_environment(self, api_key: str) -> None:
59126
60127
61128class DynamoEnvironmentV2Wrapper (BaseDynamoEnvironmentWrapper ):
62- def get_table_name (self ) -> str | None : # type: ignore[override]
129+ def get_table_name (self ) -> str :
63130 return settings .ENVIRONMENTS_V2_TABLE_NAME_DYNAMO
64131
65132 def get_identity_overrides_by_environment_id (
@@ -122,12 +189,14 @@ def update_identity_overrides(
122189 ),
123190 )
124191
125- def write_environments (self , environments : Iterable ["Environment" ]) -> None :
126- with self .table .batch_writer () as writer : # type: ignore[union-attr]
127- for environment in environments :
128- writer .put_item (
129- Item = map_environment_to_environment_v2_document (environment ),
130- )
192+ def _map_environment_document (self , environment : "Environment" ) -> dict [str , Any ]:
193+ return map_environment_to_environment_v2_document (environment )
194+
195+ def _map_compressed_environment_document (
196+ self ,
197+ environment : "Environment" ,
198+ ) -> "CompressedEnvironmentDocument" :
199+ return map_environment_to_compressed_environment_v2_document (environment )
131200
132201 def delete_environment (self , environment_id : int ): # type: ignore[no-untyped-def]
133202 environment_id = str (environment_id ) # type: ignore[assignment]
0 commit comments