초기커밋

This commit is contained in:
2025-05-01 07:20:41 +09:00
commit 98bb2e3c5c
2747 changed files with 646947 additions and 0 deletions

610
ServerBase/DB/QueryBatch.cs Normal file
View File

@@ -0,0 +1,610 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;
using NLog;
using Amazon.Runtime;
using Amazon.DynamoDBv2;
using Amazon.DynamoDBv2.Model;
using Amazon.DynamoDBv2.DataModel;
using Amazon.DynamoDBv2.DocumentModel;
using Amazon;
using ServerCore; using ServerBase;
using LOG_ACTION_TYPE = System.String;
namespace ServerBase;
//==============================================================================================
// 배치 쿼리를 전담 처리해 주는 추상 클래스 이다.
//==============================================================================================
public abstract partial class QueryBatchBase
{
public enum QueryResultType
{
Success = 0, // 쿼리를 성공 했다.
NotCalledQueryFunc = 1, // 쿼리 함수가 호출되지 않았다.
CalledQueryFunc = 2, // 쿼리 함수를 호출 했다.
QueryFailed = 3, // 쿼리를 실패 했다.
}
private DynamoDbClient m_dynamo_db_connector;
private IQueryRunner m_query_runner;
private bool m_is_use_transact = false;
private readonly string m_trans_id = string.Empty;
private UInt16 m_retry_delay_msec = 0;
private UInt16 m_retry_count = 0;
private readonly List<QueryExecutorBase> m_querys = new();
private IWithLogActor m_log_actor;
private readonly LogAction m_log_action;
private readonly List<ILogInvoker> m_business_logs = new();
public QueryBatchBase( IWithLogActor logActor, LOG_ACTION_TYPE logActionType
, DynamoDbClient dbConnector
, IQueryRunner queryRunner
, bool isUseTransact = true, string transId = ""
, UInt16 retryCount = 0, UInt16 retryDelayMSec = 0)
{
m_log_actor = logActor;
m_dynamo_db_connector = dbConnector;
m_query_runner = queryRunner;
m_is_use_transact = isUseTransact;
m_trans_id = string.IsNullOrEmpty(transId) ? Guid.NewGuid().ToString("N") : transId;
m_log_action = new LogAction(logActionType);
m_retry_count = retryCount;
m_retry_delay_msec = retryDelayMSec;
}
public QueryBatchBase( IWithLogActor logActor, LogAction logAction
, DynamoDbClient dbConnector
, IQueryRunner queryRunner
, bool isUseTransact = true, string transId = ""
, UInt16 retryCount = 0, UInt16 retryDelayMSec = 0)
{
m_log_actor = logActor;
m_dynamo_db_connector = dbConnector;
m_query_runner = queryRunner;
m_is_use_transact = isUseTransact;
m_trans_id = string.IsNullOrEmpty(transId) ? Guid.NewGuid().ToString("N") : transId;
m_log_action = logAction;
m_retry_count = retryCount;
m_retry_delay_msec = retryDelayMSec;
}
public bool addQuery( QueryExecutorBase queryContext
, QueryExecutorBase.FnCommit? fnCommit = null
, QueryExecutorBase.FnRollback? fnRollback = null )
{
queryContext.bindCallback(fnCommit, fnRollback);
queryContext.setQueryBatch(this);
if(false == queryContext.onAddQueryRequests())
{
return false;
}
m_querys.Add(queryContext);
return true;
}
//=========================================================================================
// DB 쿼리 주요 실행 함수들...
//=========================================================================================
public async Task<Result> prepareQueryWithStopwatch()
{
Stopwatch? stopwatch = null;
var event_tid = string.Empty;
var server_logic = ServerLogicApp.getServerLogicApp();
ArgumentNullException.ThrowIfNull(server_logic);
var server_config = server_logic.getServerConfig();
ArgumentNullException.ThrowIfNull(server_config);
if (true == server_config.PerformanceCheckEnable)
{
event_tid = string.IsNullOrEmpty(getTransId()) ? System.Guid.NewGuid().ToString("N") : getTransId();
stopwatch = Stopwatch.StartNew();
}
var result = await prepareQuery();
if (null != stopwatch)
{
var elapsed_msec = stopwatch.ElapsedMilliseconds;
stopwatch.Stop();
if (elapsed_msec > Constant.STOPWATCH_LOG_LIMIT_MSEC)
{
Log.getLogger().debug($"QueryBatch - prepareQuery Stopwatch Stop : ETID:{event_tid}, ElapsedMSec:{elapsed_msec}");
}
}
return result;
}
private async Task<Result> prepareQuery()
{
var result = new Result();
foreach(var query in m_querys)
{
result = await query.onPrepareQuery();
if (result.isFail())
{
return result;
}
}
return result;
}
public async Task<Result> doQueryWithStopwatch()
{
Stopwatch? stopwatch = null;
var event_tid = string.Empty;
var server_logic = ServerLogicApp.getServerLogicApp();
ArgumentNullException.ThrowIfNull(server_logic);
var server_config = server_logic.getServerConfig();
ArgumentNullException.ThrowIfNull(server_config);
if (true == server_config.PerformanceCheckEnable)
{
event_tid = string.IsNullOrEmpty(getTransId()) ? System.Guid.NewGuid().ToString("N") : getTransId();
stopwatch = Stopwatch.StartNew();
}
var result = await doQuery();
if (null != stopwatch)
{
var elapsed_msec = stopwatch.ElapsedMilliseconds;
stopwatch.Stop();
if (elapsed_msec > Constant.STOPWATCH_LOG_LIMIT_MSEC)
{
Log.getLogger().debug($"QueryBatch - doQuery Stopwatch Stop : ETID:{event_tid}, ElapsedMSec:{elapsed_msec}");
}
}
return result;
}
private async Task<Result> doQuery()
{
var result = new Result();
var err_msg = string.Empty;
try
{
foreach (var query in m_querys)
{
result = await query.onQuery();
if (result.isFail())
{
Log.getLogger().error(result.toBasicString());
break;
}
}
}
catch(System.Exception e)
{
err_msg = $"Exception !!!, doQuery(), Exception:{e} - {toBasicString()}";
result.setFail(ServerErrorCode.DynamoDbQueryException, err_msg);
Log.getLogger().error(result.toBasicString());
}
finally
{
if (result.isSuccess())
{
result = await commitQueryAllWithStopWatch();
}
else
{
await rollbackQueryAll(result);
}
onSendMsgByHandler(result);
}
return result;
}
//=====================================================================================
// 1. 등록된 쿼리들을 등록 순서대로 처리해 준다.
// 2. Non-Transaction 쿼리에 오류가 발생할 경우 이후 처리할 쿼리는 모두 실행하지 않는다.
// 3. TransactionGetItem 쿼리에 오류가 발생할 경우 TransactionGetItemRequest에
// 등록된 모든 쿼리는 처리되지 않는다.
// 4. TransactionWriteItem 쿼리에 오류가 발생할 경우 TransactionWriteItemRequest에
// 등록된 모든 쿼리는 처리되지 않는다.
//=====================================================================================
private async Task<Result> commitQueryAllWithStopWatch()
{
Stopwatch? stopwatch = null;
var event_tid = string.Empty;
var server_logic = ServerLogicApp.getServerLogicApp();
ArgumentNullException.ThrowIfNull(server_logic);
var server_config = server_logic.getServerConfig();
ArgumentNullException.ThrowIfNull(server_config);
if (true == server_config.PerformanceCheckEnable)
{
event_tid = string.IsNullOrEmpty(getTransId()) ? System.Guid.NewGuid().ToString("N") : getTransId();
stopwatch = Stopwatch.StartNew();
}
var result = await commitQueryAll();
if (null != stopwatch)
{
var elapsed_msec = stopwatch.ElapsedMilliseconds;
stopwatch.Stop();
if (elapsed_msec > 100)
{
Log.getLogger().debug($"QueryBatch - commitQueryAll Stopwatch Stop : ETID:{event_tid}, ElapsedMSec:{elapsed_msec}");
}
}
return result;
}
private async Task<Result> commitQueryAll()
{
var result = new Result();
var try_count = 0;
while(true)
{
(result, bool is_retry_query) = await commitQueryRunner();
if (true == is_retry_query)
{
try_count++;
if (m_retry_count <= try_count)
{
break;
}
await Task.Delay(m_retry_delay_msec);
}
else
{
break;
}
}
if (result.isFail())
{
await rollbackQueryAll(result);
}
else
{
foreach (var query in m_querys)
{
if (QueryBatchBase.QueryResultType.NotCalledQueryFunc == await query.doFnCommit())
{
await query.onQueryResponseCommit();
}
}
}
return result;
}
private async Task<(Result, bool)> commitQueryRunner()
{
var result = new Result();
var err_msg = string.Empty;
try
{
result = await onCommitQueryRunner();
}
catch (TransactionCanceledException e)
{
var error_code = ServerErrorCode.DynamoDbTransactionCanceledException;
err_msg = $"Failed to commitQueryRunner() !!!, Transaction Canceled, implies a client issue, fix before retrying !!! : errorCode:{error_code}, {e.toExceptionString()} - {toBasicString()}";
result.setFail(error_code, err_msg);
Log.getLogger().error(result.toBasicString());
(var dispatch_result, var exception_custom_result) = await onDispatchTransactionCancelException(e);
if (dispatch_result.isFail())
{
err_msg = $"Failed to onDispatchTransactionCancelException() !!! : {dispatch_result.toBasicString()}, {e.toExceptionString()} - {toBasicString()}";
Log.getLogger().error(err_msg);
}
else
{
// 재정의된 오류 코드가 설정된 경우
if (exception_custom_result.isFail())
{
// 재정의한 예외 결과를 dispatch 했다면 오류 로그로 설정해 준다. !!!
err_msg = $"QueryBatch.onDispatchTransactionCancelException() !!! : exceptionResult:{exception_custom_result.getResultString()} - {toBasicString()}";
result.setFail(exception_custom_result.getErrorCode(), exception_custom_result.getResultString());
Log.getLogger().error(result.toBasicString());
}
}
}
catch (TransactionConflictException e)
{
var error_code = ServerErrorCode.DynamoDbTransactionConflictException;
err_msg = $"TransactionConflictException !!!, Failed to perform in commitQueryRunner() !!!, Transaction conflict occurred !!! : errorCode:{error_code}, {e.toExceptionString()} - {toBasicString()}";
result.setFail(error_code, err_msg);
Log.getLogger().error(result.toBasicString());
}
catch (AmazonDynamoDBException e)
{
var error_code = ServerErrorCode.DynamoDbAmazonDynamoDbException;
err_msg = $"AmazonDynamoDBException !!!, Failed to perform in commitQueryRunner() !!! : errorCode:{error_code}, {e.toExceptionString()} - {toBasicString()}";
result.setFail(error_code, err_msg);
Log.getLogger().error(err_msg);
}
catch (AmazonServiceException e)
{
var error_code = ServerErrorCode.DynamoDbAmazonServiceException;
err_msg = $"AmazonServiceException !!!, Failed to perform in commitQueryRunner() !!! : errorCode:{error_code}, {e.toExceptionString()} - {toBasicString()}";
result.setFail(error_code, err_msg);
Log.getLogger().error(result.toBasicString());
return (result, true);
}
catch (System.Exception e)
{
var error_code = ServerErrorCode.TryCatchException;
err_msg = $"Exception !!!, Failed to perform in commitQueryRunner() !!! : errorCode:{error_code}, exception:{e} - {toBasicString()}";
result.setFail(error_code, err_msg);
Log.getLogger().error(result.toBasicString());
}
return (result, false);
}
private async Task rollbackQueryAll(Result errorResult)
{
await onRollbackQueryRunnner();
foreach (var query in m_querys)
{
if (QueryBatchBase.QueryResultType.NotCalledQueryFunc == await query.doFnRollback(errorResult))
{
await query.onQueryResponseRollback(errorResult);
}
}
}
protected abstract Task<Result> onCommitQueryRunner();
protected abstract Task onRollbackQueryRunnner();
public virtual void onSendMsgByHandler(Result errorResult)
{
return;
}
//==============================================================================================
// 배치쿼리중에서 특정 쿼리를 찾는다.
//==============================================================================================
public TQuery? getQuery<TQuery>()
where TQuery : QueryExecutorBase
{
return m_querys.Find(x => x is TQuery == true) as TQuery;
}
public List<TQuery> getQuerys<TQuery>()
where TQuery : QueryExecutorBase
{
var querys = new List<TQuery>();
foreach (var each in m_querys)
{
if (each is TQuery query)
{
querys.Add(query);
}
}
return querys;
}
public void appendBusinessLogs(List<ILogInvoker> logs)
{
foreach(var log in logs)
{
m_business_logs.Add(log);
}
}
public void appendBusinessLog(ILogInvoker log)
{
m_business_logs.Add(log);
}
private bool hasBusinessLog()
{
var log_actor = getLogActor();
if (null != log_actor && 0 < m_business_logs.Count)
{
return true;
}
return false;
}
public void sendBusinessLog()
{
if (false == hasBusinessLog())
{
return;
}
BusinessLogger.collectLogs(m_log_action, m_log_actor, m_business_logs);
}
public string toBasicString()
{
return $"{this.getTypeName()}, LogActionType:{m_log_action.getLogActionType()}, TransId:{m_trans_id}";
}
protected abstract Task<(Result, Result)> onDispatchTransactionCancelException(TransactionCanceledException exception);
}//QueryBatchBase
//==============================================================================================
// 배치 쿼리를 전담 처리해 주는 제네릭 클래스 이다.
//==============================================================================================
public partial class QueryBatch<TQueryRunner> : QueryBatchBase
where TQueryRunner : IQueryRunner, new()
{
public QueryBatch( IWithLogActor logActor
, LOG_ACTION_TYPE logActionType
, DynamoDbClient dbClient
, bool isUseTransact = true, string transId = ""
, UInt16 retryCount = 3, UInt16 retryDelayMSec = 1000, string eventTid = "" )
: base( logActor, logActionType, dbClient, new TQueryRunner()
, isUseTransact, transId, retryCount, retryDelayMSec)
{
getQueryRunner().setQueryBatch(this);
}
public QueryBatch( IWithLogActor logActor
, LogAction logAction
, DynamoDbClient dbClient
, bool isUseTransact = true, string transId = ""
, UInt16 retryCount = 3, UInt16 retryDelayMSec = 1000, string eventTid = "" )
: base( logActor, logAction.getLogActionType(), dbClient, new TQueryRunner()
, isUseTransact, transId, retryCount, retryDelayMSec)
{
getQueryRunner().setQueryBatch(this);
}
protected override async Task<Result> onCommitQueryRunner()
{
var query_runner = getQueryRunner();
var result = await query_runner.onTryCommitQueryRunner();
if (result.isFail())
{
return result;
}
return await getQueryRunner().onCommitQueryRunner();
}
protected override async Task onRollbackQueryRunnner()
{
await getQueryRunner().onRollbackQueryRunner();
}
protected override async Task<(Result, Result)> onDispatchTransactionCancelException(TransactionCanceledException exception)
{
await Task.CompletedTask;
var log_actor = getLogActor();
ArgumentNullException.ThrowIfNull(log_actor, $"log_actor is null !!!");
ArgumentNullException.ThrowIfNull(exception, $"exception is null !!! - {log_actor.getTypeName()}");
return await getQueryRunner().onHandleTransactionCancelException(exception);
}
}//QueryBatch<TQueryRunner>
//==============================================================================================
// 배치 쿼리 + 모든 쿼리 처리후 TMsg 를 결과로 전송해 주는 제네릭 클래스 이다.
//==============================================================================================
public class QueryBatch<TQueryRunner, TMsg> : QueryBatch<TQueryRunner>
where TQueryRunner : IQueryRunner, new()
where TMsg : class, new()
{
protected readonly TMsg m_reserved_msg ; // 전송하고자 하는 패킷
protected readonly object m_sender; // 전송객체 : setError(ErrorCode error), sendPacketAsync(TMsg msg) 맴버함수가 있어야 한다. !!!
public QueryBatch( IWithLogActor logActor
, LOG_ACTION_TYPE logActionType
, DynamoDbClient dbClient
, object sender
, TMsg? msg = null
, bool isUseTransact = true )
: base(logActor, logActionType, dbClient, isUseTransact )
{
if (msg == null)
{
m_reserved_msg = new TMsg();
}
else
{
m_reserved_msg = msg;
}
m_sender = sender;
}
public QueryBatch( IWithLogActor logActor
, LogAction logAction
, DynamoDbClient dbClient
, object sender
, TMsg? msg = null
, bool isUseTransact = true )
: base(logActor, logAction.getLogActionType(), dbClient, isUseTransact )
{
if (msg == null)
{
m_reserved_msg = new TMsg();
}
else
{
m_reserved_msg = msg;
}
m_sender = sender;
}
public TMsg Msg
{
get { return m_reserved_msg; }
}
private void setErrorToMsg(Result errorResult)
{
}
public override void onSendMsgByHandler(Result errorResult)
{
if (m_reserved_msg != null && m_sender != null)
{
setErrorToMsg(errorResult);
}
}
}//QueryBatch<TQueryRunner, TMsg>