Upgrade to Pro — share decks privately, control downloads, hide ads and more …

AWS SDKのClientは Factory経由で作ろう

TomoyaIwata
September 26, 2023

AWS SDKのClientは Factory経由で作ろう

「緊急開催!サーバーレス座談会 in JAWS-UG 大阪」にてLTさせて頂いた際の資料です

https://jawsugosaka.doorkeeper.jp/events/162714

TomoyaIwata

September 26, 2023
Tweet

More Decks by TomoyaIwata

Other Decks in Programming

Transcript

  1. AWS SDKのClientは
    Factory経由で作ろう
    クラスメソッド株式会社
    岩⽥ 智哉
    1

    View Slide

  2. 2
    ⾃⼰紹介
    l
    クラスメソッド株式会社 サーバーサイドエンジニア
    l
    2023 Japan AWS Top Engineer
    l
    2023 Japan AWS All Certifications Engineer
    l
    前⼗字靭帯再建⼿術リハビリ中
    岩⽥ 智哉

    View Slide

  3. 3
    ⾔いたいこと
    AWS SDKのClientは
    Factory経由で作ろう

    View Slide

  4. 4
    LambdaとEC2/ECSの違い
    リクエストとコンピューティング環境がN:1
    リクエストとコンピューティング環境が1:1

    View Slide

  5. 5
    リクエストとコンピューティング環境が1:1だと…
    ソケット ソケット
    ソケット
    ソケット
    DynamoDB等のAWSサービス
    ソケット
    ソケット
    Lambda実⾏環境で⽣成するソケットは1つで⼗分
    (なことが多い)

    View Slide

  6. 6
    これらを意識すると
    AWS SDKのClientの扱いが
    最適化できる

    View Slide

  7. 7
    良くない例1
    import boto3
    class TableA:
    def __init__(self):
    self._client = boto3.client('dynamodb')
    def put_item(self, item):
    self._client.put_item(TableName='tableA', Item=item)
    class TableB:
    def __init__(self, client):
    self._client = boto3.client('dynamodb')
    def put_item(self, item):
    self._client.put_item(TableName='tableB', Item=item)
    def handler(event, context):
    table_a = TableA()
    table_a.put_item({'foo': 'bar’})
    table_b = TableB()
    table_b.put_item({'hoge': 'fuga'})

    View Slide

  8. 8
    何が良くないのか︖

    View Slide

  9. 9
    よくある解決策
    class TableA:
    def __init__(self, client):
    self._client = client
    def put_item(self, item):
    self._client.put_item(TableName='tableA', Item=item)
    class TableB:
    def __init__(self, client):
    self._client = client
    def put_item(self, item):
    self._client.put_item(TableName='tableB', Item=item)
    import boto3
    client = boto3.client('dynamodb’)
    def handler(event, context):
    table_a = TableA(client)
    table_a.put_item({'foo': 'bar'})
    table_b = TableB(client)
    table_b.put_item({'hoge': 'fuga'})

    View Slide

  10. 10
    そうはいっても
    • 現実世界のアプリはもっと複雑。呼び出し階層も深くなる
    • 呼び出し先の呼び出し先の呼び出し先…にclientを伝搬するのは⾯倒
    • clientの処理化処理はもっと⾊々やることがある
    import boto3
    client = boto3.client('dynamodb’)
    def handler(event, context):
    table_a = TableA(client)
    table_a.put_item({'foo': 'bar'})
    table_b = TableB(client)
    table_b.put_item({'hoge': 'fuga'})

    View Slide

  11. 11
    提案
    Factoryクラスを使おう︕

    View Slide

  12. 12
    実装例(簡易版)
    import boto3
    class Boto3ClientFactory:
    # ⽣成したclientクラスのインスタンスをクラス変数に保持しておく
    _clients = {}
    @classmethod
    def get_singleton_client(cls, service_name, **kwargs):
    # 対象サービスのclientクラスを⽣成済みならクラス変数のキャッシュから返却
    # 複数リージョンを扱う場合はキャッシュキーにリージョンを含めるなど追加の考慮が必要
    if service_name in cls._clients:
    return cls._clients[service_name]
    client = boto3.client(service_name, **kwargs)
    cls._clients[service_name] = client
    return client

    View Slide

  13. 13
    Factoryクラスの追加実装例
    • タイムアウト値の調整
    • デフォルト値はLambda実⾏環境の設定値としては不適切
    • connect_timeout:60, read_timeout:60
    • Event Systemを利⽤したフックの登録
    • API呼び出し前にパラメータをクラス変数に保存
    • 例外キャッチ時にクラス変数に保存したパラメータをログ出⼒
    • ⾮シングルトンなclientクラス⽣成処理
    • たまにはPromise.All的な実装が必要になることもある

    View Slide

  14. 14
    Factoryクラスの利⽤例
    client = Boto3ClientFactory.get_singleton_client('dynamodb')
    def handler(event, context):
    table_a = TableA(client)
    table_a.put_item({'foo': 'bar'})
    table_b = TableB(client)
    table_b.put_item({'hoge': 'fuga'})
    def foo():
    bar()
    def bar():
    baz()
    def baz():
    client = Boto3ClientFactory.get_singleton_client('dynamodb')
    table_a = TableA(client)
    table_a.put_item({'foo': 'bar'})
    def handler(event, context):
    foo()
    タイムアウト値など適切に設定された
    clientクラスが1発で取得可能
    呼び出し階層の深いところまでclientクラ
    スを引き回さなくて良くなる

    View Slide

  15. 15
    Provisioned Concurrency利⽤時の注意
    Boto3ClientFactory.get_singleton_client('dynamodb’)
    def handler(event, context):

    Init処理の中でclientクラスの⽣成を「空打ち」しておく

    View Slide

  16. 16
    初回のClientクラス⽣成処理は重い
    https://github.com/boto/botocore/blob/40d6219947f4d047088cbeb80f8f222f599f9c7c/botocore/loaders.py
    • 初回のclientクラス⽣成時はJSONファイルを読み込んで動的にクラスを⽣成するので「重い」
    • 2回⽬以後はキャッシュを使う
    • Init処理の中でclientクラス向けのキャッシュを「暖気」することでProvisioned Concurrencyに最適化
    https://github.com/boto/botocore/blob/40d6219947f4d047088cbeb80f8f222f599f9c7c/botocore/data/dynamo
    db/2012-08-10/service-2.json

    View Slide

  17. 17
    以上
    ありがとうございました

    View Slide

  18. 18

    View Slide