Validation rull을 수정해야 하는데

아래와 같이 작성했는데 정상적으로 작동하지 않아서 의아했는데

IBoolean__c && FieldName ≠ null

이전에 작성한 Validation rull을 확인하니 원인을 파악할 수 있었다.

 

&&는 AND, ||는 OR 그리고 is not null을 나타내는 것은 !ISNULL을 사용해야 했는데

검색 결과 ISNULL보다 ISBLANK가 더 좋다는 내용이 있었는데

생각해보면 null값을 싫어해서 ‘’형태로 값을 보내 공백이지만 값은 있는 경우가 있기 떄문에

진짜 null이 아닌 값이 없는 것을 체크하려면 ISBLANK가 더 맞긴 할 것 같았다.

AND(!IBoolean__c, !ISBLANK(FieldName))

 

세번째 프로젝트 진행 중

개발오그가 지나치게 느린 것에 오늘도 고통받고 있었는데

저번에도 한참 원인을 파악하며 네트워크등을 건드리다가

통으로 복사되어 없는 데이터를 가져오려는 메인 로고 부분을

운영서버로 가서 가져온 다음 넣어줬는데 그래도 해결되지 않았었다.

 

문득 어제 한참 시도하다가 포기했던 캐시 부분에서

“캐시를 org 단위로 없앨 수는 있지만 심각한 성능 저하가 발생할 수 있다”고 해서 포기했던게 떠올랐고

설정 → 보안 → 세션 설정 → 안전하고 지속적인 브라우저 캐싱을 통해 성능 개선을 보니

예상대로 해제되어 있는 것을 볼 수 있었다.

 

설정을 다시 켜고 재접속을 해도 큰 차이는 느낄 수 없었는데

원인을 다시 파악해보니 시작 부분에 무슨 대시보드만 수십개가 깔려있었기 때문에

해당 부분들 로딩 문제로 느려지는 것 같았다.

 

그래도 다행히 내용물이 별로 없는 탭으로 빠르게 이동하면 딜레이가 거의 없었는데

이전에는 내용이 하나도 없는 탭도 5~10초가 걸리던 것에 비하면 상당히 만족스러웠다.

 

다음 작업을 진행하다가 스케줄을 걸어줘야 했는데

이전까지는 스케줄이 아닌 배치라고 착각하고 배치를 만들다가

뭔가 이상함을 느끼고 다시 스케줄로 방향을 선회했다.

Global with sharing class IF_EveryDay_BatchJob implements Schedulable {
    //오전 1시 예정으로 테스트를 위해 주석처리
    // Global static String cron          = '0 0 1 * * ?';
    //원하는 시간대 테스트용
    Global static String cron          = '0 45 16 * * ?';
    
    Global static void runSchedule() {
        System.schedule('MethodName EveryDay 01:00:00', cron, new IF_EveryDay_BatchJob());
    }

    Global void execute(SchedulableContext sc) {
        IF_EveryDay_BatchJob.executeBatch(sc.getTriggerId());
    }

    Global static void executeBatch(Id jId){
        ClassName.MethodName();
    }
}

스케줄로 작동시키려고 시도하는데 자꾸 callout 문제가 발생했고

해당 문제를 해결하기 위해 배치 내부가 아닌 외부에 하려고 아래처럼 excuteBatch도 만들었지만

의미없이 넘어가버렸다.

 

결국 원인을 찾아본 결과 스케줄의 특성상 동기화로 작동되지 않는다는 문제였는데

이걸 해결하기 위해서는 비동기로 진행한다는 future를 사용해야 했고

@future를 사용했음에도 불구하고 callout 에러가 발생했는데

에러코드에 callout = true를 사용하라고 안내 메세지가 같이 있었기 때문에

아래와 같이 메서드 위에 future를 붙여 실행할 수 있었다.

public with sharing class ClassName{
    
    @Future(callout=true)
    public static void MethodName(){}

}

 

하지만 많은 데이터가 생길 경우 배치를 사용해야 하는데

지금 하루 평균 한두건의 데이터라고 해도 몇건이 될지 모르기 때문에

유지보수를 위해서는 배치로 해야 한다는 말씀이 있으셔서 다시 배치를 작성하기로 했다.

 

배치는 또 개체 관련 문제로 골치였는데

저번에 해결했던 경험을 살려서 확인해보니

리턴값과 받는 값의 타입은 List<Object>로 해버리고

for문 내부에서 다시 형변환을 해야 해결할 수 있었다.

Global without sharing class ClassName implements Database.Batchable<Object>,Database.AllowsCallouts {

    Public List<Object> start(Database.BatchableContext BC) {
        List<ObjectName> acList = [SELECT Id
                                   , FieldName1
                                   , FieldName2
                                   , FieldName3
                                   , FieldName4
                                   , FieldName5
                                   , FieldName6
                                FROM ObjectName
                               ];

        return acList;
    }

    Public void execute(Database.BatchableContext BC, List<Object> scope) {
        for(Object asObj : scope){
            ObjectName asset = (ObjectName)asObj;
            System.debug(asset);
        }
    }

 

 

(1).백준 21146번 Rating Problems는 심사위원들의 평점을 구해야 하는 문제였다.

 

-3~3점까지의 점수를 줄 수 있고

n명 중 k명의 점수가 공개되었을 때

최고 및 최저 평점을 구해야 했기 때문에

점수가 밝혀진 위원들의 점수 합을 구한 다음

max에는 나머지 위원들의 점수를 3점으로 더하고

min에는 나머지 위원들의 점수를 3점씩 모두 제외했다.

 

점수는 평점이기 때문에 최종적으로 총 인원인 n으로 나눠 문제를 해결할 수 있었다.

const input = `5 2
1
2`.split('\n')

const [n, k] = input[0].split(' ').map(Number)

let point = 0

for(let i = 1 ; i < input.length ; i++){
    point += Number(input[i])
}

const max = (point + 3 * (n - k)) / n
const min = (point - 3 * (n - k)) / n

console.log(min, max)

'회고' 카테고리의 다른 글

[개발일지] - 114(주말)  (0) 2023.10.22
[개발일지] - 113(주말)  (0) 2023.10.21
[개발일지] - 111  (1) 2023.10.19
[개발일지] - 110  (0) 2023.10.18
[개발일지] - 109  (2) 2023.10.17

여태까지는 배열을 넘겨줄 경우 [a,b,c,d]형태로 담아서 넘겨준다고 생각했기 때문에

용량을 많이 차지한다고 생각해서 굳이 문자열 형태로 바꾸거나 map 형태로 담아서 키를 전송했었는데

알고보니 배열을 넘길 떄는 담긴 전체 내용이 넘어가는 것이 아니라 주소값만 넘어가기 때문에

아래와 같은 작업을 진행했을 때 arr의 처리 시간은 160ms

str의 처리시간은 1500ms로 오히려 배열로 처리하는 것이 더 안정적인 것을 볼 수 있었다.

 

사실 더 안정적인지는 정확하게 모르겠지만

str의 길이가 길어질수록 시간이 지연되는 문제는 확인할 수 있지만

arr의 경우 천만개의 데이터가 들어있어도 속도 지연이 현저하게 적다는 것을 알 수 있기 때문에

배열의 값이 아닌 주소를 넘긴다는 것을 확인하는 시간이 되었다.

let startTime = performance.now();

const arr = []
const arrChanger = (arr,num) => {
    arr.push(num)
}

for(let i = 0 ; i < 10000000 ; i++){
    arrChanger(arr,i)
}
console.log(arr.length)

// let str = ''
// const strChanger = (st, num) => {
//     str += num
// }
// for(let i = 0 ; i < 10000000 ; i++){
//     strChanger(str,i)
// }
let endTime = performance.now();
console.log(`걸린 작업 시간은 총 ${endTime - startTime} 밀리초입니다.`);

배치를 작성하던 중 외부 아이디를 확정지어야 했는데

이전에 두개를 외부아이디로 설정했었지만

인스펙터로 확인해보니 첫번째 키는 전부 존재하지만

두번째 키는 3개정도 빈 곳을 볼 수 있었다.

 

결론적으로 저 테이블에서는 첫번째 키를 키로 사용한다는 것을 확신했고

데이터정의서를 보니 그제서야 첫번째 키에 강조 표시가 있다는 것을 볼 수 있었는데

이게 키를 표시해주는 것임을 뒤늦게 인지할 수 있었다.

 

기존에 설정했던 외부아이디 2개 중 하나인 두번째 키를 원래대로 되돌리고

다시 배치를 작성했다.

(보안 문제로 이름을 첫번째 키, 두번째 키로 변경해 어색함이 있는 상태)

 

외부 키를 사용해 upsert를 사용할 경우

upsert upsertDataList externalId형태로 기본 upsert 뒤에 외부 아이디를 추가하면 된다.

 

배치를 작성한 후 스케쥴러를 통해 작업을 진행하려고 했으나

기존에 작성된 스케쥴러가 있었기 때문에 해당 스케쥴러에 추가하려고 했다.

 

하지만 스케쥴러 내부의 작동 방식은 getTriggerId 같은 뭔가로 jobId를 특정하면서 진행하는데

excute 내부나 excute에서 실행한 함수 내부나 동일한 아이디를 가지고 있어서 

두개의 작업을 넣으면 안될 것 같았다.

 

알고보니 Batch를 연달아 배치(?)하는게 아니고

배치가 끝난 후 final 부분에서 실행시키는 것이었다.

 

그리고 배치가 기존 실행과 schedule로 실행되는 것을 구분하는 방법이 있었는데

해당 방식을 내 코드에 적용하고 보니 뭔가 이상했고

final에서 다음 작업을 작동시킬지를 확인하는 것이었기 때문에

이전 스케쥴의 마지막 Batch 작업이었던 Batch 내부에서 설정하고

내 코드는 그냥 호출만 당하는 것이었다.

 

오늘도 좋은 내용을 많이 배울 수 있었고

남는 시간에는 메일을 전부 읽었는데

주어진 일만 하는 것이 아니라 감독하는 입장에서 확인해보니 

사용자의 입장에서 필요한 수정이 생각보다 많았고

그 부분을 정리한 다음 금요일에 있을 회의 전에 개선을 시도하거나

개선 방법에 대해 생각해보기로 했다.

 

 

(1).백준 10807번 개수 세기는 새싹 문제였는데

실수로 클릭한 새싹 문제들을 풀지 않아서 아직 새싹단계인걸로 착각하고 문제를 풀어버렸다.

 

자세히 읽어보니 새싹 문제를 다 풀더라도

이쪽은 새싹 태그의 문제가 모여있어서 새싹등급이 아닌 새싹 태그일 뿐이었지만

혹시 새싹 태그를 다 풀면 다음 단계가 있어서 더 체계적으로 문제를 풀 수 있지 않나 기대를 해본다.

 

갯수 새기는 해당 글자와 일치하는 것의 갯수를 찾는 문제인데

지금 생각해보면 filter와 length를 사용해도 괜찮았을 것 같다.

const input = `11
1 4 1 2 4 2 4 2 3 4 4
2`.split('\n')
const arr = input[1].split(' ')
let count = 0
for(let i = 0 ; i < arr.length ; i++){
    if(arr[i] === input[2]) count++
}

console.log(count)

'회고' 카테고리의 다른 글

[개발일지] - 49  (0) 2023.08.18
[개발일지] - 48  (0) 2023.08.17
[개발일지] - 46(광복절)  (0) 2023.08.15
[개발일지] - 45  (0) 2023.08.14
[개발일지] - 44(주말)  (0) 2023.08.13

보안적인 문제로 인해 그냥 일지 자체를 중단했었는데

지나간 코드를 블로그에 검색할 수 없어서 예전에 해결한 문제와 비슷한 경우 다시 시간이 걸리는 단점이 생겨버렸고

이로 인해 보안적으로 문제가 되는 부분들은 지워버리고 세일즈포스적인 부분만 남겨서 참고하기로 했다.

(변수명, 변수값 정도만 지운다는 이야기)

 

하지만 프로젝트를 들어가면서 조금 더 세밀하게 일정과 기록이 관리될 필요성을 느끼는데

보안적인 문제가 있어서 옵시디언을 사용해야 할 수도 있을 것 같고

만약 옵시디언으로 정리하게 된다면 일기는 몰라도 회고의 필요성이 점점 줄어든다는 생각이 든다.

 

1.배치 200 -> 1로 줄여서 여러개 테스트해보기

2.배치 넘길 때 Database.Query 말고 다른 방식 찾아보기(Iterable<String>)

3.스케쥴러를 익명함수가 아닌 클래스로 추가해서 배치 작동시키기

 

1.배치 생성 완료 

public class LeadProcessor implements Database.Batchable<sObject>, Database.Stateful {
    public Integer recordsProcessed = 0;
    public Database.QueryLocator start(Database.BatchableContext bc) {
        return Database.getQueryLocator(
            'SELECT ID, Company FROM Lead'
        );
    }
    public void execute(Database.BatchableContext bc, List<Lead> scope) {
    // process each batch of records
    List < Lead > leads = new List < Lead > ();
    for (Lead lead : scope) {
        lead.Company = lead.Company + '1';
        leads.add(lead);
        // increment the instance member counter
        recordsProcessed = recordsProcessed + 1;
    }
update leads;
}
public void finish(Database.BatchableContext bc){
    System.debug(recordsProcessed + ' records processed. Shazam!');
AsyncApexJob job = [SELECT Id, Status, NumberOfErrors,
                           JobItemsProcessed,
                           TotalJobItems, CreatedBy.Email
                      FROM AsyncApexJob
                     WHERE Id = : bc.getJobId()];
    // call some utility to send email
    EmailUtils.sendMessage(job, recordsProcessed);
}
}

 

2.배치 생성 후 원하는 값 start -> execute 전달은 Database.Query가 되지 않음 Iterable<String>으로 해결

 

3.배치 생성 후 api 가져오기 Too many callouts 문제 Database.AllowsCallouts으로 해결

 

4.배치 200 -> 1로 줄여서 실행해보기 ApiBatchProcessor batchClass = new ApiBatchProcessor(); Database.executeBatch(batchClass,1); 형태로 , num 형태일 경우 해당 수치만큼 쪼개진다.

 

5.배치 endpoint 미등록 setting -> Security -> Remote Site Settings -> New Remote Site 등록 후 진행 성공

배치

public class ApiBatchProcessor implements Database.Batchable<String>, Database.AllowsCallouts {
		public Iterable<String> start(Database.BatchableContext bc) {
		// 여기서는 외부 API를 호출하여 데이터를 가져옵니다.
		String requestBody = //보안상 입력 형태 삭제
		HttpRequest request = new HttpRequest();
    request.setEndpoint('<https://url>' //보안상 입력 형태 삭제);
    request.setMethod('POST');
    request.setHeader('Content-Type', 'application/json');
    request.setBody(requestBody);

    Http http = new Http();
    HttpResponse response = http.send(request);
	List<String> cardNames;
    if (response.getStatusCode() == 200) {
        // API에서 가져온 데이터를 가공하여 List<Object> 형태로 보관합니다.
        Map<String, Object> jsonResponse = (Map<String, Object>) JSON.deserializeUntyped(response.getBody());
        List<Object> apiData = (List<Object>) jsonResponse.get('result');
        XXXXs = new List<String>();

        // API에서 가져온 데이터를 가공하여 XXXX 값을 추출합니다.
        for (Object data : apiData) {
            Map<String, Object> dataMap = (Map<String, Object>) data;
            String xxxx = (String) dataMap.get('XXXX');
            XXXXs.add(xxxx);
        }
        // 가공된 데이터를 반환합니다.
        return XXXXs;
    } else {
        return null; // API 호출에 실패한 경우, 처리할 데이터가 없으므로 null을 반환합니다.
    }
}

public void execute(Database.BatchableContext bc, List<String> scope) {
    // execute 메서드에서는 가공된 데이터를 사용하여 처리 작업을 수행합니다.
    List<Lead> leads = new List<Lead>();
	System.debug(scope);
    for (String xxxx : scope) {
		System.debug(xxxx);
        leads.add(new Lead(LastName = 'test', Company = xxxx));
    }
	System.debug(leads);
    insert leads;
}

public void finish(Database.BatchableContext bc) {
    // 배치 작업이 완료된 후에 추가적인 마무리 작업을 수행합니다.
}

}

6.스케쥴러로 배치를 작동하지 않고 implements Schedulable를 배치에 추가해 진행 => 실패 스케쥴러 생성 후 스케쥴러 내부 배치 실행 방식으로 변경 후 성공 

public class ApiBatchScheduler implements Schedulable {
    public void execute(SchedulableContext sc) {
    ApiBatchProcessor batchClass = new ApiBatchProcessor();
        Database.executeBatch(batchClass, 1);
    }
}
    String jobName = '배치 실행 스케줄러';
    String cronExpression = '0 0 * * * ?';
    ApiBatchScheduler batchScheduler = new ApiBatchScheduler();
System.schedule(jobName, cronExpression, batchScheduler);

 

7.클래스 추가 후 클래스 내부에서 작동시키기

public class ApiBatchScheduler implements Schedulable {
	public void execute(SchedulableContext sc) {
	ApiBatchProcessor batchClass = new ApiBatchProcessor();
	Database.executeBatch(batchClass,1);
	}
	public static void registerScheduler() {
	    String jobName = '배치 실행 스케줄러';
	    String cronExpression = '0 30 * * * ?';
	    ApiBatchScheduler batchScheduler = new ApiBatchScheduler();
	    System.schedule(jobName, cronExpression, batchScheduler);
	}
}

익명함수에서 ApiBatchScheduler.registerScheduler();로 작동

로그쌓기 진행하기(하단 미공개)

 

내일 모레가 당장 시험이라 노션에서 복사해서 옮기는게 조금엉망진창으로 정리된 느낌이지만

이것도 많이 무리한 것 같은데 아마 내일 회고록은 문제만 올라오고 수정되거나

그냥 기존처럼 수정없이 문제만 있을 수 있을 것 같다.

 

 

(1).백준 17944번 퐁당퐁당 1은 사람들이 모였을 때 주어진 사람 숫자의 2배만큼의 숫자까지 오르락 내리락하는 게임이었다.

 

처음 생각으로는 간단하게 사람 수를 기준으로 게임을 %처리해 쉽게 해결할 수 있을 것 같았는데

2n이라는 것을 가볍게 읽고 넘어가버려서 n 기준으로 작성하고 코드가 꼬여서 수정하는데 조금 헷갈렸다.

 

결과적으로는 1~2n까지의 증가 후 다시 1까지 감소해야 하는데

1은 새로운 시작이라고 보면 1~2n -> 2까지로 2n으로 4n번이 아닌 4n-2번 이동한다는 것을 확인해야 했고

그걸 다시 n이 아닌 2n을 기준으로(people * 2) 처리해야 하는 것을 깜빡하지만 않는다면 쉽게 해결할 수 있는 문제였다.

 

n 기준으로는 금방 풀 수 있을 것 같은데

2n으로 설정되고 -2처리까지 붙었다고 10분 가까이 걸려서 조금 당황했다.

const [people, game] = `5 20`.split(' ').map(Number)
const oneCycle = 4 * people - 2
console.log(game % oneCycle > 2 * people ? 2 * people - (game % oneCycle) % (2 * people) : game % oneCycle)

'회고' 카테고리의 다른 글

[개발일지] - 22(주말)  (0) 2023.07.22
[개발일지] - 21  (1) 2023.07.21
[개발일지] - 19  (0) 2023.07.19
[개발일지] - 18  (0) 2023.07.18
[개발일지] - 17  (0) 2023.07.17

+ Recent posts