nginx 설정시 이정도는 알아야함

 - https://www.nginx.com/blog/avoiding-top-10-nginx-configuration-mistakes/

 - 참고링크: https://news.hada.io/topic?id=6041

 

  1. 워커당 File Descriptor가 충분하지 않음
  2. error_log off 는 없음
    access_log 는 off가 되지만, error_log는 off가 되지 않음. 이러면 off 이름을 가진 에러 로그 파일이 생성
  3. 업스트림 서버 연결에는 Keepalive를 활성화할 것
  4. 지시문 상속이 동작하는 방식 제대로 알기
  5. proxy_buffering off 사용 금지
  6. if 지시문의 잘못된 사용
  7. 과도한 헬스 체크 금지
  8. Metric 접근에 보안 설정하기
  9. 모든 트래픽이 같은 /24 CIDR 블록에서 오는 경우에는 ip_hash 대신 hash $binary_remote_addr consistent 사용
  10. 업스트림 그룹의 장점 활용하기

 

 

 

참고 링크 

 - https://sas-study.tistory.com/410

 - 

 

코드 샘플

import org.springframework.security.core.annotation.AuthenticationPrincipal;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : account")
public @interface CurrentUser {

}
  1. 신규 개발자 입사 또는 성능 문제 발생시 공유하는 java 코딩 방법
  2.  모두 지킬 수는 없지만 알고 있으면 좋은 내용


p.s. 생각날때 개인 의견 추가 및 링크들 추가 예정

AWS에서 Redis(Elastic cache)사용시 모니터링 등에 사용하는 커맨드
 - 예) 리얼 환경에서 사용하면 안되는 커맨드가 가끔 개발자 실수로 반영될 수 있음(예. keys)

-- redis에 keys 커맨드가 실행되고 있는지 모니터링하는 커맨드
 redis-cli -h 도메인혹은IP monitor | grep keys
  - 예) redis-cli -h 블라블라.apn2.cache.amazonaws.com monitor


-- redis에 접속되어있는 유니크한 클라이언 IP 리스트 확인 커맨드
redis-cli -h redis서버호스트 CLIENT LIST | awk '{print $2}' | sed s/:.*//g | sort -u

 


AWS ec2에 redis-cli설치하는 방법

# make 하기 위해서 gcc 다운
sudo yum install -y gcc

# redis-cli 설치 및 make
wget http://download.redis.io/redis-stable.tar.gz && tar xvzf redis-stable.tar.gz && cd redis-stable && make

#src/redis-cli 에서 사용 가능
예) ./redis-cli -h 'redis 호스트 도메인' -p '포트번호'

 

git 링크

 - https://github.com/oshnew/study-modern-java/blob/master/src/main/java/stream/StreamSorted.java

 

import lombok.AllArgsConstructor;
import lombok.Data;

import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 내부 개발자 교육을 위한 간단한 java Stream 샘플 소스
 *  - 정렬
 *
 * @author
 */
public class StreamSorted {

	public static void main(String[] args) {

		//테스트 데이터 셋팅
		List<StatVO> statList = Arrays.asList(new StatVO("a", 3), new StatVO("b_a", 2), new StatVO("b_c", 1), new StatVO("d", 7));

		System.out.println("=== cnt 오름차순 정렬");
		statList.stream().sorted(Comparator.comparing(StatVO::getCnt)).forEach(System.out::println);

		System.out.println("\n\n");
		System.out.println("=== cnt 내림차순 정렬");
		statList.stream().sorted(Comparator.comparing(StatVO::getCnt).reversed()).forEach(System.out::println);

		System.out.println("\n\n");
		System.out.println("=== stat name 오름차순 후에 cnt 내림차순정렬"); //다수 필드 정렬
		statList.stream().sorted(Comparator.comparing(StatVO::getStat).thenComparing(StatVO::getCnt).reversed()).forEach(System.out::println);

		System.out.println("\n\n");
		List<StatVO> descList = statList.stream().sorted(Comparator.comparing(StatVO::getCnt)).collect(Collectors.toList());
		System.out.println("=== 오름차순 정렬하여 collect한 리스트");
		descList.stream().forEach(System.out::println);

	}

	@Data
	@AllArgsConstructor
	public static class StatVO {
		private String stat;
		private int cnt;
	}

}
  1. 인증
    1. 서비스 계정으로 인증: https://cloud.google.com/docs/authentication/production?hl=ko

1. 목적/배경

 - 가끔 대용량 데이터 처리(예. DB에 100만건 insert values로 bulk insert할때) 부하 분산을 위해 적절한 데이터 사이즈로 분할하여 처리할 필요가 있음

   (간단한 수학 계산인데 필요할때마다 다시 코딩하는게 번거롭고 헷갈려서 메모 목적으로 작성해둡니다.

 

2. 처리 방법

 - 적절한 데이터 사이즈만큼 잘라서 loop처리

 

 

샘플 소스

/**
	 * N개의 데이터 리스트를 자르는 간단한 소스
	 *   - 대용량 DB 저장 등의 기능 구현때 부하 분산 목적
	 *
	 * @param args
	 */
	public static void main(String[] args) {

		//테스트 데이터 셋팅
		int dataSize = 103;
		ArrayList<Integer> dataList = new ArrayList<>(dataSize);
		for (int i = 1; i <= dataSize; i++) {
			dataList.add(i);
		}

		//처리 시작
		final int dataCnt = dataList.size();
		final int PER_CNT = 10; //1회 처리 건수

		int fromIndex = 0;
		int toIndex = 0;

		boolean isNeedNextLoop = true;
		int loopCompleteCnt = 0; //loop처리 완료된 횟수
		do {

			fromIndex = loopCompleteCnt * PER_CNT;  //최초 시작은 0, 이후 PER_SAVE_CNT개 만큼씩 fromIndex는 증가
			toIndex = fromIndex + PER_CNT; //from index에서 1회 최대 건수만큼까지 toIndex는 증가
			if (toIndex >= dataCnt) { //증가한 toIndex가 데이터 전체 갯수보다 클때는 toIndex 값을 변경(마지막 loop에서는 데이터 element갯수가 1회 처리량보다 적거나 같음)
				toIndex = dataCnt;
				isNeedNextLoop = false;
			}		

			//비즈니스 로직 작성
			System.out.println(String.format("%s회 data subList: %s", loopCompleteCnt+1, dataList.subList(fromIndex, toIndex)));
            loopCompleteCnt++;

		} while (isNeedNextLoop);

	}

 

샘플소스 결과 

1회 data subList: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
2회 data subList: [11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
3회 data subList: [21, 22, 23, 24, 25, 26, 27, 28, 29, 30]
4회 data subList: [31, 32, 33, 34, 35, 36, 37, 38, 39, 40]
5회 data subList: [41, 42, 43, 44, 45, 46, 47, 48, 49, 50]
6회 data subList: [51, 52, 53, 54, 55, 56, 57, 58, 59, 60]
7회 data subList: [61, 62, 63, 64, 65, 66, 67, 68, 69, 70]
8회 data subList: [71, 72, 73, 74, 75, 76, 77, 78, 79, 80]
9회 data subList: [81, 82, 83, 84, 85, 86, 87, 88, 89, 90]
10회 data subList: [91, 92, 93, 94, 95, 96, 97, 98, 99, 100]
11회 data subList: [101, 102, 103]


필요한 일이 생겨서 간단히 프로토타이핑한 구글 수익보고서 다운로드하는 java프로그램
 - 2024-07-22 간단히 업데이트 진행

 

필요 라이브러리(기타 추가 import가 필요할 수도 있음)

<!-- https://mvnrepository.com/artifact/com.google.apis/google-api-services-storage -->
<dependency>
    <groupId>com.google.apis</groupId>
    <artifactId>google-api-services-storage</artifactId>
    <version>v1-rev20240706-2.0.0</version>
</dependency>

<!-- https://mvnrepository.com/artifact/com.google.api-client/google-api-client -->
<dependency>
    <groupId>com.google.api-client</groupId>
    <artifactId>google-api-client</artifactId>
    <version>2.6.0</version>
</dependency>

<!-- https://mvnrepository.com/artifact/com.google.http-client/google-http-client-jackson2 -->
<dependency>
    <groupId>com.google.http-client</groupId>
    <artifactId>google-http-client-jackson2</artifactId>
    <version>1.44.2</version>
</dependency>
import com.google.api.client.auth.oauth2.Credential;
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.services.storage.Storage;
import com.google.api.services.storage.StorageScopes;
import com.google.api.services.storage.model.Objects;
import com.google.api.services.storage.model.StorageObject;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;

/**
 * 구글 재무보고서 중에서 '수익'보고서를 다운로드하는 샘플 프로그램
 *  - 참고: https://support.google.com/googleplay/android-developer/answer/6135870#zippy=%2C%EC%88%98%EC%9D%B5
 *
 *  - 추가 참고사항
 *  1) 수익 보고서를 사용하여 판매 대금과 거래 내역를 파악할 수 있습니다.
 *     보고서의 각 행은 거래 유형(예: 고객에게 대금을 청구하거나 Google에 수수료를 지급하는 시기)과 함께 원래 금액 및 변환된 금액을 나타냅니다.
 *  2) 수익 보고서에는 이전 달에 발생한 인보이스가 포함됩니다. 최소 지급액에 도달하면 인보이스를 받게 됩니다. 수익 보고서를 사용할 수 있게 되면 몇 주 후에판매 대금을 수령할 수 있습니다.
 *  3) 수익 보고서는 한 달에 한 번 생성되며 일반적으로 다음 달 5일에 제공됩니다.
 *  	경우에 따라 Google에서 계산 오류를 바로잡기 위해 수익을 조정할 수 있습니다.
 *  	이 경우 Google에서 문제에 관해 알려 드리며 개발자 기록용으로 조정된 거래만 포함되어 있는 추가 수입 파일을 생성합니다.
 *	4) Google은 유럽 경제 지역(EEA)의 사용자에게 판매되는 상품의 등록된 판매자이므로, 영향을 받는 국가에서 이루어진 판매가 주문당 한 줄씩 표시됩니다.
 *		또한 거래 유형은 '청구'로 표시됩니다. 다른 국가에서 이루어진 판매에는 'Google 수수료' 거래 유형도 포함됩니다.
 * 	5) 수익 보고서에는 지불 거절이 포함되지 않습니다
 *
 * @author
 */
@Slf4j
public class GoogleEarningsReportDownloadTest {

  //다운로드 저장 디렉토리 경로
  final static String saveDirStr = ".temp_google_earnings";

  //Google Cloud Platform에서 생성한 Service Account의 키 파일. 해당 키 파일의 유저(이메일주소) Google Play Console에 초대 후 권한 지급이 되어 있어야 함
  //일괄 보고서에 액세스하려면 '앱 정보 보기' 권한이 '전체'로 설정되어 있어야 합니다.
  //재무 보고서를 다운로드하려면 '재무 데이터 보기' 권한이 '전체'로 설정되어 있어야 합니다.
  //참고: https://support.google.com/googleplay/android-developer/answer/6135870#zippy=%2C%EC%88%98%EC%9D%B5%2C%ED%81%B4%EB%9D%BC%EC%9D%B4%EC%96%B8%ED%8A%B8-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-%EB%B0%8F-%EC%84%9C%EB%B9%84%EC%8A%A4-%EA%B3%84%EC%A0%95%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%98%EC%97%AC-%EB%B3%B4%EA%B3%A0%EC%84%9C-%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%ED%95%98%EA%B8%B0
  final static String SERVICE_ACCOUNT_KEY_FILE_PATH = "구글 Service Account 인증 파일 경로.json";

  //버킷
  final static String BUCKET_NAME = "pubsite_prod_블라블라"; //버킷명(Google Play Console -> 보고서 다운로드 -> 재무 -> Cloud Storage URI복사를 통해서 확인 가능)

  //다운로드할 수익보고서 object name
  final static String DOWNLOAD_OBJECT_NAME = "earnings/earnings_202406_블라블라.zip"; //object name 샘플. earnings/earnings_202110_블라블라숫자-숫자.zip
  final static String applicationName = "test-app-0.0.1";

  //인증 스콥
  final static String GoogleCredential_SCOPE = StorageScopes.CLOUD_PLATFORM_READ_ONLY;


  public static void main(String[] args) throws Exception {

    LocalDateTime start = LocalDateTime.now();
    log.info("\n\n==== Start: {} ====", start);

    HttpTransport httpTransport = GoogleNetHttpTransport.newTrustedTransport();
    JsonFactory jsonFactory = JacksonFactory.getDefaultInstance();

    Credential credential = authorizeWithServiceAccount(httpTransport, jsonFactory);
    Storage storage = new Storage.Builder(httpTransport, jsonFactory, credential).setApplicationName(applicationName).build();

    //테스트로 prefixName 기반으로 리스트 조회
    final String namePreifx = "earnings/earnings_2024"; //2024년 prefix로 전체 조회
    listObject(storage, BUCKET_NAME, namePreifx).forEach(storageObject -> {
      log.info("버킷의 {} prefix storageObject:{}", namePreifx, storageObject);
    });

    //버킷의 모든 object를 조회하는 경우: 오버헤드 주희
//    storage.objects().list(BUCKET_NAME).execute().getItems().forEach(storageObject -> {
//      log.info("storageObject:{}", storageObject);
//    });

    final Storage.Objects.Get downloartTargetObject = storage.objects().get(BUCKET_NAME, DOWNLOAD_OBJECT_NAME);

    final StorageObject storageObject = downloartTargetObject.execute();
    log.info("storageObject 정보 ==>\n\t{}", storageObject.toPrettyString());

    FileUtils.forceMkdir(new File(saveDirStr)); //디렉토리 생성
    log.info("저장 디렉토리:{}", saveDirStr);

    String objectNameSplitArray[] = StringUtils.split(DOWNLOAD_OBJECT_NAME, "/");
    String saveFileFullPathStr = saveDirStr + File.separator + objectNameSplitArray[1];

    log.info("저장 파일 full path:{}", saveFileFullPathStr);

    final File saveFile = new File(saveFileFullPathStr);
    FileUtils.deleteQuietly(saveFile); //기존 다운로드된 파일이 존재할 수 있어서 선 삭제 진행

    FileOutputStream out = new FileOutputStream(saveFile);
    downloartTargetObject.getMediaHttpDownloader().setDirectDownloadEnabled(true); //true 설정 필요
    downloartTargetObject.executeMediaAndDownloadTo(out);

    LocalDateTime end = LocalDateTime.now();
    log.info("\n\n==== End: {} ==== 소요시간: {}(second)", end, Duration.between(start, end).getSeconds());
  }

  /**
   * 서비스 account 인증
   *
   * @param httpTransport
   * @param jsonFactory
   * @return
   * @throws IOException
   */
  private static Credential authorizeWithServiceAccount(HttpTransport httpTransport, JsonFactory jsonFactory) throws IOException {

    final String privateKey = FileUtils.readFileToString(new File(SERVICE_ACCOUNT_KEY_FILE_PATH), "UTF-8");

    InputStream inputStream = new ByteArrayInputStream(privateKey.getBytes(StandardCharsets.UTF_8));

    GoogleCredential credential = GoogleCredential.fromStream(inputStream, httpTransport, jsonFactory);
    credential = credential.createScoped(Collections.singleton(GoogleCredential_SCOPE));

    return credential;
  }

  /**
   * Storage의 object들 중에서, name prefix로 필터링하여 가져옴
   *
   * @param storage
   * @param bucketName
   * @param namePrefix
   * @return
   * @throws IOException
   */
  public static Iterable<StorageObject> listObject(Storage storage, String bucketName, String namePrefix) throws IOException {

    List<List<StorageObject>> pagedList = Lists.newArrayList();
    Storage.Objects.List listObjects = storage.objects().list(bucketName).setPrefix(namePrefix); //name prefix로 필터링해서 가져오도록 설정 -> 속도 개선됨
    Objects objects;
    do {
      objects = listObjects.execute();
      List<StorageObject> items = objects.getItems();
      if (items != null) {
        pagedList.add(objects.getItems());
      }
      listObjects.setPageToken(objects.getNextPageToken());
    } while (objects.getNextPageToken() != null);

    return Iterables.concat(pagedList);
  }

}

+ Recent posts