CodeGym/Java Course/All lectures for KO purposes/애플리케이션에서 ACID를 구현하는 방법: 이론

애플리케이션에서 ACID를 구현하는 방법: 이론

사용 가능

7.1 왜 필요한가

우리는 ACID의 모든 속성, 목적 및 사용 사례에 대해 자세히 논의했습니다. 보시다시피 모든 데이터베이스가 ACID 보장을 제공하는 것은 아니며 더 나은 성능을 위해 희생합니다. 따라서 ACID를 제공하지 않는 데이터베이스가 프로젝트에서 선택될 수 있으며 애플리케이션 측에서 필요한 ACID 기능 중 일부를 구현해야 할 수 있습니다. 그리고 시스템이 마이크로서비스 또는 다른 종류의 분산 응용 프로그램으로 설계된 경우 한 서비스의 정상적인 로컬 트랜잭션은 이제 분산 트랜잭션이 되며 물론 ACID 특성을 잃게 됩니다. 각 개별 마이크로서비스는 ACID가 됩니다.

너무 크고 복잡하기 때문에 트랜잭션 관리자를 생성하는 방법에 대한 철저한 가이드를 제공하고 싶지 않으며 몇 가지 기본 기술만 다루고자 합니다. 분산 응용 프로그램에 대해 이야기하는 것이 아니라면 ACID 보장이 필요한 경우 응용 프로그램 측에서 ACID를 완전히 구현하려고 할 이유가 없습니다. 결국 기성 솔루션을 사용하는 것이 모든면에서 더 쉽고 저렴할 것입니다. 즉, ACID가 있는 데이터베이스).

하지만 응용 프로그램 측에서 트랜잭션을 수행하는 데 도움이 되는 몇 가지 기술을 보여 드리고 싶습니다. 결국 이러한 기술을 알면 다양한 시나리오에서 도움이 될 수 있으며 트랜잭션이 반드시 포함되지 않는 경우에도 도움이 될 수 있으며 더 나은 개발자가 될 수 있습니다(그렇기를 바랍니다).

7.2 거래 애호가를 위한 기본 도구

낙관적 및 비관적 차단. 이는 동시에 액세스할 수 있는 일부 데이터에 대한 두 가지 유형의 잠금입니다.

낙천주의자동시 액세스 가능성이 그다지 크지 않다고 가정하므로 다음을 수행합니다. 원하는 라인을 읽고 해당 버전 번호를 기억합니다(또는 타임스탬프 또는 체크섬/해시 - 데이터 구성표를 변경하고 버전에 대한 열을 추가할 수 없는 경우). 또는 타임스탬프), 이 데이터에 대한 변경 사항을 데이터베이스에 쓰기 전에 이 데이터의 버전이 변경되었는지 확인합니다. 버전이 변경된 경우 생성된 충돌을 어떻게든 해결하고 데이터를 업데이트("커밋")하거나 트랜잭션을 롤백("롤백")해야 합니다. 이 방법의 단점은 "time-of-check to time-of-use"라는 긴 이름(TOCTOU로 축약됨)이 있는 버그에 유리한 조건을 생성한다는 것입니다. 상태는 확인과 쓰기 사이의 기간에 변경될 수 있습니다. 낙관적 잠금에 대한 경험이 없습니다.

예를 들어 낙관적 잠금과 같은 것을 사용하는 개발자의 일상 생활에서 HTTP 프로토콜이라는 기술을 발견했습니다. 초기 HTTP GET 요청에 대한 응답에는 클라이언트가 If-Match 헤더에서 사용할 수 있는 클라이언트의 후속 PUT 요청에 대한 ETag 헤더가 포함될 수 있습니다(MAY). GET 및 HEAD 메서드의 경우 서버는 알고 있는 ETag 중 하나와 일치하는 경우에만 요청된 리소스를 다시 보냅니다. PUT 및 기타 안전하지 않은 메서드의 경우 이 경우에도 리소스만 로드합니다. ETag의 작동 방식을 모르는 경우 RSS 및 기타 피드를 구문 분석하는 데 도움이 되는 "feedparser" 라이브러리를 사용하는 좋은 예가 있습니다.


>>> import feedparser 
>>> d = feedparser.parse('http://feedparser.org/docs/examples/atom10.xml') 
>>> d.etag 
'"6c132-941-ad7e3080"' 
>>> d2 = feedparser.parse('http://feedparser.org/docs/examples/atom10.xml', etag=d.etag) 
>>> d2.feed 
{} 
>>> d2.debug_message 
'The feed has not changed since you last checked, so the server sent no data.  This is a feature, not a bug!' 

반면에 비관주의자는 거래가 종종 동일한 데이터에서 "만난다"는 사실에서 시작하여 그의 삶을 단순화하고 불필요한 경쟁 조건을 피하기 위해 필요한 데이터를 차단합니다 . 잠금 메커니즘을 구현하려면 세션에 대한 데이터베이스 연결을 유지하거나(풀에서 연결을 가져오는 대신 - 이 경우 낙관적 잠금으로 작업해야 할 가능성이 높음) 트랜잭션에 ID를 사용해야 합니다. , 연결에 관계없이 사용할 수 있습니다. 비관적 잠금의 단점은 이를 사용하면 일반적으로 트랜잭션 처리 속도가 느려지지만 데이터에 대해 침착하고 진정한 격리를 얻을 수 있다는 것입니다.

그러나 추가적인 위험은 여러 프로세스가 서로 잠긴 리소스를 기다리는 가능한 교착 상태에 도사리고 있습니다. 예를 들어 트랜잭션에는 리소스 A와 B가 필요합니다. 프로세스 1은 리소스 A를 점유했고 프로세스 2는 리소스 B를 점유했습니다. 두 프로세스 중 어느 것도 실행을 계속할 수 없습니다. 이 문제를 해결하는 방법에는 여러 가지가 있습니다. 지금 자세히 설명하고 싶지 않으므로 Wikipedia를 먼저 읽으십시오. 간단히 말해서 잠금 계층 구조를 만들 가능성이 있습니다. 이 개념을 더 자세히 알고 싶다면 "Dinning Philosophers Problem"("식사하는 철학자 문제")에 머리를 숙이도록 초대합니다.

다음은 동일한 시나리오에서 두 잠금이 어떻게 작동하는지에 대한 좋은 예입니다.

잠금 구현에 대해. 자세히 설명하고 싶지는 않지만 분산 시스템용 잠금 관리자가 있습니다(예: ZooKeeper, Redis, etcd, Consul).

7.3 연산의 멱등성

Idempotent 코드는 일반적으로 좋은 방법이며 트랜잭션을 사용하는지 여부에 관계없이 개발자가 이 작업을 수행할 수 있는 것이 좋은 경우입니다. Idempotency는 해당 작업을 개체에 다시 적용할 때 동일한 결과를 생성하는 작업의 속성입니다. 함수가 호출되었습니다 - 결과를 제공했습니다. 1~5초 후에 다시 호출하면 동일한 결과가 나타납니다. 물론 데이터베이스의 데이터가 변경된 경우 결과가 달라집니다. 세 번째 시스템의 데이터는 함수에 의존하지 않을 수 있지만 함수에 의존하는 모든 것은 예측 가능해야 합니다.

멱등성은 여러 가지로 나타날 수 있습니다. 그중 하나는 코드 작성 방법에 대한 권장 사항일 뿐입니다. 최고의 기능은 한 가지 일을 하는 기능이라는 것을 기억하십니까? 그리고 이 함수에 대한 단위 테스트를 작성하는 것이 좋은 점은 무엇입니까? 이 두 가지 규칙을 준수하면 함수가 멱등성이 될 가능성이 이미 높아집니다. 혼동을 피하기 위해 멱등 함수가 반드시 "순수"("함수 순수성"이라는 의미에서)는 아님을 분명히 하겠습니다. 순수 함수는 어떤 식으로든 변경하거나 처리된 결과를 반환하지 않고 입력에서 받은 데이터에서만 작동하는 함수입니다. 함수형 프로그래밍 기술을 사용하여 애플리케이션을 확장할 수 있는 함수입니다. 일반적인 데이터와 데이터베이스에 대해 이야기하고 있기 때문에 기능이 순수할 가능성이 낮습니다.

이것은 순수한 함수입니다.


def square(num: int) -> int: 
	return num * num 

그러나 이 함수는 순수하지 않고 멱등적입니다(이 부분에서 코드를 작성하는 방법에 대해 결론을 내리지 마십시오).


def insert_data(insert_query: str, db_connection: DbConnectionType) -> int: 
  db_connection.execute(insert_query) 
  return True 

많은 단어 대신 멱등 프로그램을 작성하는 방법을 배우도록 강요받은 방법에 대해 이야기할 수 있습니다. 보시다시피 저는 AWS로 많은 작업을 하고 있으며 AWS Lambda라는 서비스가 있습니다. Lambda를 사용하면 서버를 관리하지 않고 일정에 따라 또는 일부 이벤트에 대한 응답으로 실행할 코드를 로드하기만 하면 됩니다. 이벤트는 메시지 브로커가 전달하는 메시지일 수 있습니다. AWS에서 이 브로커는 AWS SNS입니다. AWS와 함께 일하지 않는 사람들에게도 이 점은 분명해야 한다고 생각합니다. 채널("주제")을 통해 메시지를 보내는 브로커가 있고 이러한 채널을 구독하는 마이크로 서비스가 메시지를 수신하고 어떻게든 반응합니다.

문제는 SNS가 "적어도 한 번은" 메시지를 전달한다는 점이다("최소 1회 전달"). 무슨 뜻이에요? 조만간 Lambda 코드가 두 번 호출될 것입니다. 그리고 그것은 정말로 일어납니다. 함수가 멱등적이어야 하는 여러 가지 시나리오가 있습니다. 예를 들어 계정에서 돈이 인출될 때 누군가가 같은 금액을 두 번 인출할 것으로 예상할 수 있지만 이것이 실제로 2번의 독립적인 시간인지 확인해야 합니다. 즉, 이들은 하나의 반복이 아니라 2개의 다른 트랜잭션입니다.

변경을 위해 API에 대한 요청 빈도를 제한("속도 제한")하는 또 다른 예를 들겠습니다. Lambda는 특정 user_id가 있는 이벤트를 수신하며 해당 ID를 가진 사용자가 일부 API에 대한 가능한 요청 수를 소진했는지 확인해야 합니다. 호출 값을 AWS에서 DynamoDB에 저장하고 함수를 호출할 때마다 값을 1씩 늘릴 수 있습니다.

하지만 이 Lambda 함수가 동일한 이벤트에 의해 두 번 호출되면 어떻게 될까요? 그런데 lambda_handler() 함수의 인수에 주의를 기울였습니까? 두 번째 인수인 AWS Lambda의 컨텍스트는 기본적으로 제공되며 각 고유 호출에 대해 생성되는 request_id를 비롯한 다양한 메타데이터를 포함합니다. 즉, 이제 테이블에 수행된 호출 수를 저장하는 대신 request_id 목록을 저장할 수 있으며 호출할 때마다 Lambda가 주어진 요청이 이미 처리되었는지 확인합니다.

import json
import os
from typing import Any, Dict

from aws_lambda_powertools.utilities.typing import LambdaContext  # needed only for argument type annotation
import boto3

limit = os.getenv('LIMIT')

def handler_name(event: Dict[str: Any], context: LambdaContext):

	request_id = context.aws_request_id

	# We find user_id in incoming event
	user_id = event["user_id"]

	# Our table for DynamoDB
	table = boto3.resource('dynamodb').Table('my_table')

	# Doing update
	table.update_item(
    	Key={'pkey': user_id},
    	UpdateExpression='ADD requests :request_id',
    	ConditionExpression='attribute_not_exists (requests) OR (size(requests) < :limit AND NOT contains(requests, :request_id))',
    	ExpressionAttributeValues={
        	':request_id': {'S': request_id},
        	':requests': {'SS': [request_id]},
        	':limit': {'N': limit}
    	}
	)

	# TODO: write further logic

	return {
    	"statusCode": 200,
    	"headers": {
        	"Content-Type": "application/json"
    	},
    	"body": json.dumps({
        	"status ": "success"
    	})
	}

내 예는 실제로 인터넷에서 가져온 것이기 때문에 특히 조금 더 많은 정보를 제공하므로 원본 소스에 대한 링크를 남겨 둘 것입니다.

고유한 트랜잭션 ID와 같은 것을 공유 데이터를 잠그는 데 사용할 수 있다고 앞서 언급한 것을 기억하십니까? 이제 작업을 멱등성으로 만드는 데에도 사용할 수 있음을 배웠습니다. 이러한 ID를 직접 생성할 수 있는 방법은 무엇인지 알아보겠습니다.

코멘트
  • 인기
  • 신규
  • 이전
코멘트를 남기려면 로그인 해야 합니다
이 페이지에는 아직 코멘트가 없습니다