레이블이 computer인 게시물을 표시합니다. 모든 게시물 표시
레이블이 computer인 게시물을 표시합니다. 모든 게시물 표시

[컴] spring에서 test container 사용시간 단축하기

 

test container / testcontainer / 스프링에서 / 스프링부트 / springboot / 스프링

spring에서 test container 사용시간 단축하기

test container 를 이용해서 test 를 하면, container 를 load 하는 시간이 오래걸린다. 개인적으로 ssd 가 아닌 hdd 에서 동작하도록 해서 그런지 더 실행시간이 오래걸렸다.

그래서 test 를 자주 하려면, 최대한 test container 의 실행을 줄이는 것이 좋다. 즉, 덜 자주 unload , load 를 하는 것이 낫다.

container 를 1번만 실행

Testcontainer 를 사용하면 test class에 @Testcontainers를 사용하게 된다. 이것은 매 class마다 container 를 실행하게 된다.

대체로 test container 를 이용해서 test 를 하면, container 를 load 하는 시간이 오래걸린다. 개인적으로 ssd 가 아닌 hdd 에서 동작하도록 해서 그런지 더 실행시간이 오래걸렸다.

때문에, test 를 자주 하려면, 최대한 test container 의 실행을 줄이는 것이 좋다. 즉, 덜 자주 unload , load 를 하는 것이 낫다.

그래서 이것을 우리는 이것을 전체 test 에서 1번만 실행하게 할 것이다.

위 글에도 나와 있지만 static variable 에 할당하는 것으로 container 의 생성을 1번으로 만든다.

datasource 의 설정

이것은 ApplicationContextInitializer 를 이용한다.

특정 profile 에서는 현재 띄워져있는 container 를 사용하도록 한다

testcontainer를 부하를 줄이려 1번만 load 한다고 해도, TDD 등을 할때는 속도가 엄청 느리다. 계속 unload/load 가 이뤄지기 때문에 빠른 개발이 어렵다.

그래서 build 시점에는 testcontainer 를 사용해서 test 를 하지만, 개발을 할때는 현재 띄워져 있는 container에 접속해서 test 할 수 있도록 했다.

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ContextConfiguration(initializers = TestcontainersInitializer.class)
class FishApplicationTests {
}
...


public class TestcontainersInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {

    private static final String MIGRATION_LOCATION = "filesystem:db/migration";
    public static final MariaDBContainer<?> mariaDB = new MariaDBContainer<>("mariadb:10.6")
            .withDatabaseName("myapp")
            .withUsername("myapp-user")
            .withUrlParam("useBulkStmts", "false")
            .withPassword("strong-password")
            .withReuse(true);

    public static final LocalStackContainer localStack = new LocalStackContainer(
            DockerImageName.parse("localstack/localstack"))
            .withServices(LocalStackContainer.Service.S3)
            .withReuse(true);

    public static final KafkaContainer kafka = new KafkaContainer(
            DockerImageName.parse("confluentinc/cp-kafka:7.5.0"))
            .withReuse(true);

    /*
     * Static block runs before Spring Boot starts the application context.
     */

    @Override
    public void initialize(@NotNull ConfigurableApplicationContext ctx) {
        // if system.property active profile is test, do not start containers
        final String EXCLUDE_PROFILE = "testnocontainer";
        var activeProfiles = ctx.getEnvironment().getActiveProfiles();
        if (activeProfiles.length <= 0 || !activeProfiles[0].equals(EXCLUDE_PROFILE)) {
            // if active profile is NOT 'testnocontainer'

            // 만약 여러개의 test class 를 실행하면 이부분은 당연히 여러번 실행된다. 하지만 괜찮다.
            // static 이라서 여러번 실행되지 않았다.

            /**
             * This routine runs multiple times, but due to the use of the static variables,
             * it only works once
             */
            /*
             * deepStart() is used to start containers in parallel.
             */
            Startables.deepStart(mariaDB, localStack, kafka).join();
            
            runMigrations(); // run flyway

            // 여기를 실행하지 않는 profile 은 application.xml 의 datasource 쪽을 바라보게 된다.
            // 그러므로 datasource setting 을 해놔야 한다.
            TestPropertyValues.of("spring.datasource.url=" + mariaDB.getJdbcUrl())
                .and("spring.datasource.username=" + mariaDB.getUsername())
                .and("spring.datasource.password=" + mariaDB.getPassword())
                .and("spring.kafka.bootstrap-servers=" + kafka.getBootstrapServers())
                .applyTo(ctx);
        }
    }

    private static void runMigrations() {
        Flyway.configure()
                .dataSource(mariaDB.getJdbcUrl(), mariaDB.getUsername(),
                        mariaDB.getPassword())
                .locations(MIGRATION_LOCATION)
                .schemas(mariaDB.getDatabaseName())
                .baselineOnMigrate(true)
                .load().migrate();
    }
}

See Also

  1. 쿠…sal: [컴] Spring Test 에서 @Sql 을 code 로

[컴] AI 에서 어떻게 model 을 디자인 하는 것일까?

 

AI 에서 어떻게 model 을 디자인 하는 것일까?

아래 영상을 보고 간단하게 keras 를 이용해서 ai model 을 만들어볼 수 있다.

  • Make Your First AI in 15 Minutes with Python - YouTube
  • 에러
    • ValueError: Input 0 of layer "sequential_1" is incompatible with the layer: expected shape=(None, 455, 30), found shape=(None, 30) 와 같은 에러가 발생하는데 code에서 model.add(tf.keras.layers.Dense(256, input_shape=x_train.shape[1:], activation='sigmoid')) 로 변경하면 된다. x_train.shape 대신 x_train.shape[1:]를 사용하는 것이다.

여기서 궁금중은 그러면 model 을 만들때 사용한 layer를 어떤 기준으로 추가했느냐이다. 그리고 그 layer에 사용한 activation function 은 어떤 기준으로 선택했는가 이다.

model = tf.keras.models.Sequential()
model.add(tf.keras.layers.Dense(256, input_shape=x_train.shape, activation='sigmoid'))
model.add(tf.keras.layers.Dense(256, activation='sigmoid'))
model.add(tf.keras.layers.Dense(1, activation='sigmoid'))

그럼 어떻게 model 을 만들까?

이것에 대한 대답을 다음 글에서 확인할 수 있을 듯 하다.

이글은 deep learning 의 성능을 향상시키는 방법을 이야기하는데, 여러가지 방법이 있다.

  • 데이터양을 증가시키는 방법
  • 데이터를 normalize 하는 법
  • 데이터를 시각화해서 모양을 봐보라, 그러면 이것이 가우스분포같은지, log분포같은지등을 보고, 필요한 변환을 해볼 수 있다.
  • feature selection
  • 문제를 구성하는 방법을 변경해봐라
  • 여러 알고리즘을 통한 향상
    • 선택한 알고리즘이 best가 아닐 수 있다. linear methods, tree methods, instance methods, 다른 neural network methods 들로 평가해봐라.
    • 논문등 다른 사람들이 했던 비슷한 문제들을 찾아서 아이디어를 얻어라(리서치를 해라)
    • 샘플링한 dataset을 다시 잘 sampling 한다. 예를 들어 train/test 로 나눴다면, train 에 해당하는 dataset이 문제의 대표성을 띠는지 등. 또는 작은 dataset을 사용하고, 그에 맞는 model을 선택해서 테스트해보고 큰 dataset으로 확장해 볼 수도 있다.
  • 알고리즘 튜닝을 통한 향상
    • 진단, overfitting인지 underfitting인지
    • 가중치 초기화
    • 학습률을 조정
    • activation function
    • network topology , 여러가지 구성을 테스트해본다. 1개의 많은 newrons를 갖는 hidden layer를 시도하던지, layer당 적은수의 neuron들을 갖는 deep network 를 시도하던지, 이 2개를 전부해보던지등.
    • batches and Epochs, batch size를 변경하는 것은 gradient를 정의하고, weight들을 얼마나 자주 update하는지를 정한다. epoch는 배치마다 ‘network 에 노출되는 전체 dataset’ 이다.
    • 정규화, overfitting을 억제하는 좋은 방법이다. dropout이라는 정규화기술도 있다.
    • 최정화와 손실, 이전에 최적화는 ‘확률적 경사하강법(stochastic gradient descnet)’ 였지만, 이젠 많은 optimizer들이 있다.
    • 빠른 정지(Early Stopping), 성과가 저하되기 시작하면 학습을 중단한다. 그리고 다른 sample을 만들던지 하는등으로 시간을 절약할 수 있다.

[컴] Docker 의 DockerCli.exe -SwitchDaemon

SwitchDaemon 은 무슨 용도 /

Docker 의 DockerCli.exe -SwitchDaemon

ref. 2 를 보면, linux 에서 windows container 를 실행할 수 없다.

windows 에서는 WSL2 를 제공하기 때문에, linux container 를 실행할 수 있고, windows 이기에 windows container도 실행 가능하다. 그래서 ref. 2 에 보면, linux container와 windows container 를 동시에 실행하는 것도 가능하다.

참고로, Docker for windows에는 windows container를 위해선 WindowsEngine을 사용하고, linux container 를 위해선 LinuxEngine을 사용한다.

engine 변경 방법

이 때 engine 을 변경하는 방법이 DockerCli.exe -SwitchDaemon 이다.

C:\Program Files\Docker\Docker
DockerCli.exe -SwitchDaemon

특정 engine 으로 변환하려면 다음처럼 하면 된다.

DockerCli.exe -SwitchWindowsEngine
DockerCli.exe -SwitchLinuxEngine

References

  1. Run Linux and Windows Containers on Windows 10
  2. docker - Can Windows containers be hosted on Linux? - Stack Overflow
  3. Docker ❤️ WSL 2 - The Future of Docker Desktop for Windows | Docker

[컴] Spring Test 에서 @Sql 을 code 로

springboot test / junit / unittest/ /통합테스트 / .sql / query / 쿼리실행 / 초기 데이터 세팅 / setup / @Sql 대신 사용

Spring Test 에서 @Sql 을 code 로

아래와 같은 annotation 을

@JooqTest
@Sql({"classpath:myquery1.sql", "classpath:myquery2.sql"})
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class MyTests {
    ...
}

아래처럼 변경했다. 아래는 특정 profile 이 아닐때만 sql 을 실행하도록 했다.

@JooqTest
// @Sql({"classpath:myquery1.sql", "classpath:myquery2.sql"})
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class MyTests {

    // require: @JooqTest
    @Autowired
    DSLContext ctx;
    @Autowired
    DataSource dataSource;
    @Autowired
    Environment env;

    ProductStatusBgJobProcessor backgroundJobProcessor;

    @BeforeAll
    void beforeAll() {
        runSqlInitData(dataSource);
    }

    @BeforeEach
    void setUp() {

        ...
    }

    @Test
    void myTest1() throws Exception {

        ...

    }

    private void runSqlInitData(DataSource dataSource) {
        final String EXCLUDE_PROFILE = "testnocontainer";
        var activeProfiles = env.getActiveProfiles();
        if (activeProfiles.length <= 0 || !activeProfiles[0].equals(EXCLUDE_PROFILE)) {
            // when not 'testnocontainer' profile
            ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
            populator.addScripts(
                    new ClassPathResource("myquery1.sql"),
                    new ClassPathResource("myquery2.sql"));
            populator.setSeparator(";");
            populator.execute(dataSource);
        }
    }
}

Spring, Integration Test 에서 sql file을 실행하기

sql script 를 실행하기 위해 다음 4개의 함수를 사용할 수 있다.

  • org.springframework.jdbc.datasource.init.ScriptUtils: SQL 스크립트가 parse되고 실행되는 방법을 완전히 제어할 때 사용
  • org.springframework.jdbc.datasource.init.ResourceDatabasePopulator
    • 내부적으로 ScriptUtils 를 사용
  • org.springframework.test.context.junit4.[AbstractTransactionalJUnit4SpringContextTests](https://docs.spring.io/spring-framework/reference/testing/testcontext-framework/support-classes.html#testcontext-support-classes-junit4)
    • 내부적으로 ResourceDatabasePopulator를 사용
  • org.springframework.test.context.testng.[AbstractTransactionalTestNGSpringContextTests](https://docs.spring.io/spring-framework/reference/testing/testcontext-framework/support-classes.html#testcontext-support-classes-testng)
    • 내부적으로 ResourceDatabasePopulator를 사용

Reference

  1. Executing SQL Scripts :: Spring Framework

[컴] SpringBoot v3 에서 openapi v2 사용

javadoc / 자동 문서 / swagger / api 문서 작성 /

SpringBoot v3 에서 openapi v2 사용

gradle 사용시 spring boot 에서 openapi spec의 문서를 만드려면 아래 build.gradle 처럼 springdoc-openapi-starter-webmvc-ui 를 추가하면 된다.

  • implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.0'

springboot v3 을 사용한다면 springdoc-openapi 2.x 를 사용해야 한다. springdoc-openapi-starter-webmvc-uispringdoc-openapi-ui의 openapi-v2 이다.

// build.gradle
plugins {
    id 'java'
    id 'org.springframework.boot' version '3.2.1'
    id 'io.spring.dependency-management' version '1.1.4'
}

group = 'com.nsm'
version = '0.0.1-SNAPSHOT'

java {
    sourceCompatibility = '17'
}

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'

    implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.0'
}

tasks.named('test') {
    useJUnitPlatform()
}

test

내 springBoot server가 localhost:8080 에 떠있는 경우, 다음 경로로 가면 api 문서 화면이 보인다.

json format은

disable

다음 옵션 2개를 꺼주면 된다.

springdoc.api-docs.enabled=false
springdoc.swagger-ui.enabled=false

문서작성 annotation

일부는 자동으로 만들어주지만, 정확하지 않은 값으로 만들어준다. 명시적으로 api 문서에 값을 넣을때는 annotation 을 활용할 수 있다.

아래를 참고하면 된다.

다만, @RequestBody의 경우 spring 의 annotation 과 겹쳐서 @io.swagger.v3.oas.annotations.parameters.RequestBody 등으로 사용해야 할 수 있다.

@PostMapping
@Operation(summary = "Create a User", description = "This can only be done by the logged in user.")
ResponseEntity<MyResponse> createUser(
        @io.swagger.v3.oas.annotations.parameters.RequestBody(description = "Created User object", 
          required = true, content = @Content(schema = @Schema(implementation = MyPostBody.class))) 
        @Valid @RequestBody MyPostBody user) {
    return ResponseEntity.status(HttpStatus.CREATED)
            .body(userService.createOne(user));
}

Reference

  1. OpenAPI 3 Library for spring-boot

[컴] 구글 워크스페이스 특정계정의 2단계인증 중지 방법

구글 계정/ 구글 어드민 / google workspace / google gsuite /

구글 워크스페이스 특정계정의 2단계인증 중지 방법

2024-01-01 화면 기준

  1. https://admin.google.com/
  2. 메뉴 –> 디렉토리 –> 사용자
  3. 수정하기를 원하는 사용자의 이름을 클릭 –> 상세화면이 보인다.
  4. ‘보안’ 부분으로 간다. 여기서 확장(expand) icon 클릭
  5. ‘본인 확인 요청’ 클릭
  6. 사용 중지

References

  1. 본인 확인 요청, 2단계 인증, 로그인 관련 문제 해결하기 - Google Workspace 관리자 고객센터
  2. 사용자의 본인 확인 요청 일시적으로 사용 중지하기 - Google Workspace 관리자 고객센터

[컴] 구글 클라우드에 만들어 놓은 project 의 owner 계정이 사라지면 project 는 같이 지워질까?

구글 클라우드에 만들어 놓은 project 의 owner 계정이 사라지면 project 는 같이 지워질까?

google cloud(https://console.cloud.google.com/) 에 만들어 놓은 project 의 owner 계정이 사라지면 project 는 같이 지워질까?

개인 계정인 경우는 그렇다.

그러나 google workspace 같은 경우라면 다르다.

  • 계정(account) 가 organization(조직) 에 속해있으면, project 는 organization 에 속하게 됨.
  • 그래서 organization 의 admin 이 접속할 수 있게 됨.
  • IAM page 가서 새로운 owner 를 할당할 수 있다.

Reference

  1. What happens when we delete the google account of a project owner? - Stack Overflow

[컴] spring boot project 를 vscode 에서 실행하기

springboot / vscode 에서 java 사용 / --debug-jvm / unittest system.out / stdout 출력

spring boot project 를 vscode 에서 실행하기

잠깐 써본 소감은 'Language Support for Java' 의 memory 를 max 2G까지 올렸는데, 그래서인지 메모리를 많이 잡아먹었다. 그러나, IntelliJ를 사용할때와 비슷한 수준이었다.

설치한 vscode extension

‘Language Support for Java’ 에 대한 java home 설정

여기선 workspace setting 을 변경했다.

// myproj.code-workspace
{
    "folders": [
        {
            "path": ".."
        }
    ],
    "settings": {
        "java.jdt.ls.java.home": "d:\\a\\appss\\jdk-17.0.6.10-hotspot"
    }
}

gradle extension 에서 실행

아래 그림처럼 gradle 의 task 를 실행할 수 있다. 여기서 debugger 를 run 하거나, 그냥 run 하거나 할 수 있다.

vscode 화면

개별 class test 시 debug mode

아직은 gradle extension 에서 개별 class의 test 에 대한 debug mode run 은 지원이 안된다.

그래서 다음과 같은 방법으로 gradle test 에 debugger 를 붙일 수 있다.

  1. vscode 내부 터미널에서 다음처럼 gradle test 를 실행하고,
gradlew test --tests co.my.pro.MyTests --debug-jvm
  1. 아래처럼 launch.json을 설정해서 debugger를 실행(f5)시킨다.
// launch.json
{
    "version": "0.2.0",
    "configurations": [
        
        {
            "type": "java",
            "request": "attach",
            "name": "Attach by Process ID",
            "processId": "${command:PickJavaProcess}"
        }
    ]
}
  1. 그러면 process 를 선택하면 된다.

--debug-jvm : Testing in Java & JVM projects

console log

testLogging.showStandardStreams = true 을 해주면, log 가 stdout 으로 찍힌다.

events=["passed", "failed", "skipped", "standard_out", "standard_error"]

로 설정하는 것은

events=["passed", "failed", "skipped"]
showStandardStreams = true

과 같다. showStandardStreamsevents=["standard_out", "standard_error"] 과 같다. 위와 같이 설정하면 2개가 동시에 적용된다.


test {
    useJUnitPlatform()

    // set systemProperties with default value "spring.profiles.active=test"
    systemProperties = System.properties
    if (!systemProperties.containsKey('spring.profiles.active')) {
        systemProperty("spring.profiles.active", "test")
    }

    testLogging { 
        events=["passed", "failed", "skipped", "standard_out", "standard_error"]
        // events=["passed", "failed", "skipped"]
        // showStandardStreams = true
    }
}

See Also

  1. Run and Debug Java in Visual Studio Code
  2. 쿠…sal: [컴][자바] vscode 에서 java 사용하기, 2017-02
  3. Debugging doesn't work · Issue #876 · microsoft/vscode-gradle · GitHub

[컴][웹] 2개의 다른 domain 에서 서버가 같은 상위 domain 을 사용하는 cookie 를 생성하도록 경우, 브라우저의 credential: 'include' 동작

만약 a.domain.net, b.domain.net 이 모두 Domain을 자신들의 상위 도메인인 .domain.net 으로 해서 Set-Cookie 를 날리는 상황을 가정해보자.

  • a.domain.net : Set-Cookie: session=11111111111; path=/; domain=.domain.net; HttpOnly

  • b.domain.net : Set-Cookie: session=22222222222; path=/; domain=.domain.net; HttpOnly

  • A라는 사이트에서 login 은 해서 cookie 가 만들어졌다. 이때 credential 을 include 하면 이 cookie 가 request 시점에 날라간다.

  • 이 때 B라는 사이트에서 login을 한다. 그런데 같은 domain 에 대해서 만들기 때문에, A 라는 사이트에서 사용하던 cookie 가 browser에서 지워지고, 새롭게 B site 에서 사용할 cookie 가 만들어진다.

  • cookie 는 도메인 별로 unique 하게 존재하지만, 이것을 해당 domain 외의 domain 에서 access 하는 것 또한 same-origin policy 를 따라야 해서, browser에서 다른 site A로 request를 보낼때 credential(cookie)이 없는 채로 날라간다.

  • 즉, domain 설정을 잘 해줘야 2 site 에서 동시에 cookie 를 사용할 때 문제가 없다.

See Also

  1. http - Share cookies between subdomain and domain - Stack Overflow
  2. https://setcookie.net/ : 여기서 Set-Cookie 의 domain설정을 해서 테스트해 볼 수 있다.

[컴] backbone network 란?

백본망 / 백본은 뭔가?

backbone network 란?

일반적으로 ref.1 에 나와있듯이 ’주요 노드(primary nodes)를 연결하는 고용량 통신 시설(high capacity communication facilities)’을 가리키는 용어이다.

기본적으로 backbone network 는 개념적인 이야기다. 그러므로 일반적인 뜻보다는 좀 더 작은 용량의 시설도 sub-network들을 연결한다면 백본망이라 부를 수 있다.

만약 내가 ISP 로 부터 인터넷 선을 하나 할당받고, 이것을 허브를 이용해서 사무실 2개에 대해 각각 케이블을 뽑고, 그 케이블마다 공유기에 연결해서 각각 network 를 구성했다고 하자. 그렇다면 나의 백본망은 이 NAT 와 각 사무실로 뻗어나가는 2개의 유선일 것이다.

통신사업자(ISP) 의 백본

ISP 의 백본은 우리가 일반적으로 생각하는 백본이라고 생각하면 될 것 같다. 다음 기사를 통해 대략적으로 어떻게 구성되어 있는지 확인해 볼 수 있다.

See Also

  1. Backbone network - Wikipedia
  2. 백본망 코어망 라우터 게이트웨이 개념

[컴] Java Spring JobRunr

 

스프링 / 스케쥴러 / scheduler / celery

Java Spring JobRunr

  • job 을 다른 background thread에서 수행한다. 그래서 web request 가 block 되지 않는다.
  • java 8 lambda 를 분석해서 json 으로 serialize한다. 이 것을 db 에 저장한다.
  • 많은 job 들이 만들어져서, server 가 그 load를 처리할 수 없게 됐을때, app instance 를 늘리면, JobRunr이 자동으로 그 job 을 분산시킨다.(scale horizontally)
  • retry 는 자동으로 이뤄지는데, 이때 exponential back-off policy 를 사용한다.
  • 모든 job들을 monitor 하는 built-in dashboard 가 있다.
  • 성공한 job 들을 일정 시간이 지나면 자동으로 지워지게 할 수 있다. 이 일정시간을 설정할 수 있다.
  • Real time trigger processing · jobrunr/jobrunr · Discussion #442 · GitHub : 5초 단위의 pollingTimeInterval 을 갖는다. 그래서 그보다 낮은 시간간격으로 수행해야 하는 job 들의 경우 문제가 있다. 그리고 정확히 간격이 맞지 않는다면, 특정 scheduled job 이 정해진 시간보다 먼저 실행될 수도 있다? 
  • Background Job Poll Interval time | github : pollingTimeInterval 의 기본값은 15초
  • Important remarks | Recurring jobs · JobRunr

port 설정

기본은 8000 이다. localhost:8000/dashboard 로 가면 JobRunr 의 dashboard를 확인할 수 있다. 다음처럼 config file 에서 변경할 수 있다.

org.jobrunr.dashboard.enabled=true 
org.jobrunr.dashboard.port=8000

매분단위 job 을 실행한 개인적인 경험, jobrunr 6.2.3

매분마다 도는 job 을 설정해놨다. 그러고 나서, 보니 같은 시점에 2개가 실행됐다. 그래서 db 의 jobrunr_jobs 에서 확인을 해보니, 같은 시간에 2번이 실행됐다.

created_at 을 보니, 매 분마다 실행시간이 조금씩 밀렸다. 대략 0.08s~0.09s 씩 늦어졌다. 그래서 결국 같은 시점에 2번이 실행되는 상황이 왔다.

위의 5초단위의 pollingTimeInterval 과 어느정도 상관관계가 있는 듯도 싶다. 만약 항상 40~50초 시점에 trigger 가 발생한다면, job 내에서 시간을 보정해서 사용하는 것도 방법일 듯 하다.

같은 ‘분’(minute)에 실행된 job 들을 확인하기 위한 query

-- 같은 '분'(minute)에 실행된 job 들
SELECT * FROM (
    SELECT count(his) cnt, t1.* FROM (
        SELECT DATE_FORMAT(createdAt, '%j %H:%i') AS his, DATE_FORMAT(createdAt, '%H:%i:%S.%f'), createdAt, updatedAt  
        FROM jobrunr_jobs 
        WHERE recurringJobId 
          IN('myjob.MyJobBackgroundJobProcessor.runEveryMinuteExample()')
        ORDER BY createdAt DESC
    ) AS t1 
GROUP BY t1.his
) AS tt1 WHERE cnt > 1
;

이슈가 있던 때의 createdAt 과 updatedAt 값들

createdAt updatedAt
2024-02-13 04:21:59.615 2024-02-13 04:21:59.696
2024-02-13 04:22:59.715 2024-02-13 04:22:59.793
2024-02-13 04:23:59.812 2024-02-13 04:23:59.880
2024-02-13 04:24:59.904 2024-02-13 04:24:59.988
2024-02-13 04:26:00.016 2024-02-13 04:26:00.393
2024-02-13 04:26:45.418 2024-02-13 04:26:45.494


2024-02-13 06:39:59.635 2024-02-13 06:39:59.706
2024-02-13 06:40:59.726 2024-02-13 06:40:59.793
2024-02-13 06:41:59.812 2024-02-13 06:41:59.885
2024-02-13 06:42:59.910 2024-02-13 06:42:59.976
2024-02-13 06:44:00.001 2024-02-13 06:44:00.327
2024-02-13 06:44:45.314 2024-02-13 06:44:45.358


2024-02-13 08:54:59.536 2024-02-13 08:54:59.603
2024-02-13 08:55:59.625 2024-02-13 08:55:59.695
2024-02-13 08:56:59.723 2024-02-13 08:56:59.804
2024-02-13 08:57:59.820 2024-02-13 08:57:59.887
2024-02-13 08:58:59.913 2024-02-13 08:58:59.980
2024-02-13 09:00:00.020 2024-02-13 09:00:03.025
2024-02-13 09:00:46.289 2024-02-13 09:00:46.723

cron time 설정시 주의

만약 다음처럼 정의를 하면,

@Recurring(cron = "*/10 * * * *", zoneId = "Asia/Seoul")

job 은 다음처럼 10분 근처에서 실행한다. 30분에 대한 trigger 가 29:54 쯤에 발생한다.

createdAt updatedAt
2024-02-17 07:29:54.207 2024-02-17 07:29:54.239
2024-02-17 07:19:53.956 2024-02-17 07:19:53.983
2024-02-17 07:09:53.689 2024-02-17 07:09:53.798

Reference

  1. Background Jobs in Spring with JobRunr | Baeldung

Start recurring jobs multiple times? · jobrunr/jobrunr · Discussion #755 · GitHub

[컴] client-side web app 을 위한 OAuth 2.0

google cloud credential / api credential / access token

client-side web app 을 위한 OAuth 2.0

OAuth 2.0 은 유저들이 application 과 특정data 를 공유할 수 있게 해준다. 공유하면서도 그들의 user명과 비밀번호와 다른 개인정보를 노출하지 않는다.

예를 들어 OAuth 2.0 은 user의 Google Drive에 file 을 저장하기 위해 그 해당 user로 부터 permission(허락)을 획득하게 된다.

OAuth 2.0 flow 는 implicit grant flow (암묵적 허락 흐름)라고 부른다. 이것은 ’user 가 application 에 있을때만 API들에 접근’하는 application들을 위해 만들어졌다. 이 application들은 기밀정보(confidential information)를 저장해 놓을 수 없다.

me: 그래서 access token 만 처음에 받아와서 사용하게 된다. app 등은 Authorization Code 를 받아온 후에 다시 access token 을 받는다. 다음 글들을 읽어보면 된다. Using OAuth 2.0 for Web Server Applications 를 봐도 알 수 있는데, 이것은 confidential information 을 저장해 놓고 사용하는 app을 위해 design 됐다고 나와 있다.

동작

이 flow에서, app 은 query parameter를 이용하는 Google URL 을 연다. 이 query parameter 는 app 을 구별하고, app 이 필요한 API 접근의 종류를 구별하는 값을 갖고 있다.

현재 브라우저 window 에서 이 URL 을 열거나 popup 으로 열 수 있다.

user 는 Google 에서 인증하고, app이 요청하는 permission 들을 grant 해주게 된다. Google 은 그리고 나서 user 를 app 으로 다시 redirect 시킨다. 이 때 access token 와 함께 redirect 시킨다. app 은 이 access token 을 확인(verify)하고, API 요청을 만들때 사용된다.

Goolge OAuth 2.0 server

Google API들에 access 하기 위해서 OAuth 2.0 을 사용하는 모든 application은 authorization credential들을 가져야만 한다. 이 authorization credential 은 Google 의 OAuth 2.0 server 가 application 을 판별할 수 있게 해준다.

이 credential id 는 다음 link 에서 만들 수 있다.

여기서 authorized JavaSCript origins 를 설정하게 된다. 이 설정된 origin 에서 보내는 request 만 accept 하는 것이다.

auth code flow 를 사용하자.

위의 글의 내용은 요즘 브라우저에서 3rd party cookie 를 없애려는 움직임이 있다. 그런 이유로 implicit grant flow 는 더이상 auth 를 하는 방법으로 적절치 않다는 이야기다.

See Also

  1. 쿠…sal: [컴][웹] OAuth 2.0

[컴] AWS snapshot archive 정보

 

스냅샷

AWS snapshot archive 정보

금액

금액 정보 : Archive Amazon EBS snapshots - Amazon Elastic Compute Cloud

  • snapshot 금액 : 월 GB당 $0.05
  • archived snapshot
    • 월 GB당 $0.0125
    • 100 GB, 월 $1.25(100GiB * $0.0125)가 청구
  • 복원비용 : 데이터 GB당 0.03 USD
    • 100GiB 스냅샷을 복원하면 3달러 청구(100GiB * $0.03).
    • 1회성 비용
  • snapshot 이 standard tier 로 복원된 뒤로는 snapshot 요금인 월 GB당 $0.05

[컴] terraform 에서 for_each에서 map 에 list 를 감싸는 이유

 테라폼 코딩 / 꼼수 / 트릭 /

terraform 에서 for_each에서 map 에 list 를 감싸는 이유

다음 module code를 보다가 궁금한게 생겼다.

code from : https://github.com/terraform-aws-modules/terraform-aws-alb/blob/a1a54c5b0a26919eda7bdd50da6b9eed5fedcc1c/main.tf#L343

resource "aws_lb_listener_rule" "redirect_to_https" {
  # rules = { <each.key> = each.value }
  for_each = local.rules

  ...

  dynamic "condition" {
    for_each = try(each.value.conditions, [])

    content {
      dynamic "host_header" {
        for_each = try([condition.value.host_header], [])

        content {
          values = host_header.value.values
        }
      }
    }
  }

  ...
}

위코드의 input은 다음과 같다.

...
  conditions = [{
    host_header = {
      values = ["/onboarding", "/docs"]
    }
  }]
...

여기서 왜 아래처럼 condition.value.host_header를 iterate 할 때, condition.value.host_header로 감쌌는지 궁금했다.

...
      dynamic "host_header" {
        for_each = try([condition.value.host_header], [])
        # 여기서 host header 는 아래와 같다.
        # host_header = { values = ["/onboarding", "/docs"] },

        content {
          values = host_header.value.values
        }
      }
...

아래처럼 바로 map 을 이용할 수도 있는데 말이다.

...
      dynamic "host_header" {
        for_each = try(condition.value.host_header, {})
        # 여기서 host header 는 아래와 같다.
        # host_header.key = values, host_header.value = ["/onboarding", "/docs"]

        content {
          values = host_header.value
        }
      }
...

input 에서 map 의 특정key 를 사용하게 하도록 하기 위해

이것은 host_header.values 를 명시적으로 하기 위함인 듯 하다. 아래 input 값을 가지고 test 를 해보면 이해가 쉽다.

...
  conditions = [{
    path_pattern = {
      vals = ["/onboarding", "/docs"]
    }
  }]
...

만약 map을 그대로 이용한 경우에는 values를 사용하지 않아도, 첫번째 item 이 가지고 있는 값을 사용하게 된다. 하지만 list 로 감싸면, iterate 에서 map 을 그대로 사용하게 되기때문에, 명시적으로 values 를 사용해줘야만 한다.

그로인해 input 은 ’values’라는 key 를 사용해야 만 한다.

[컴] 무한스크롤 vs pagination

비교

무한스크롤 vs pagination

ref.1 은 infinte scroll 과 pagination 의 비교한 글이다.

일부를 정리하면 다음과 같다.

  • 좀 더 목적이 있는 글에는 pagination 이 낫다.

  • 유저가 빠르게 소비하는 content 에는 infinite 이 낫다.

  • 구글 search 의 결과는 pagination 이지만, google image 의 결과는 infinite 인 이유가 image 를 보는 속도가 빠르기 때문이다.

예외 상황

모든 페이지의 모습이 ref.1 의 글안에 들어맞는 것은 아니다.

몇몇 무한스크롤은 명시적으로 콘텐츠를 더 불러오기 위해 클릭을 필요로 한다.

  • 덕덕고의 검색 결과는 infinite 이다.
  • 덕덕고에서는 페이지 이후에 명시적으로 다음 페이지 로드를 하도록 한다.
    • 이것은 footer를 보여줄 수 있다.
    • 다만 여전히 클릭을 해야 한다.

개인적인 생각

개인적으로 무한스크롤의 가장 큰 단점은 명시적으로 그 곳을 찾아가기가 어렵다는 것이라고 생각한다.

그것을 극복하는 방법중에 각 글에 대한 url을 제공하는 것이 의미가 있다고 본다. 이 경우 viewer에 대한 구현은 2가지가 있을 수 있다.

  • 하나는 url의 내용만 보여주는것,
  • 다른 하나는 스크롤등의 흐름안에서 그것을 보여주는 법.
    • 이것은 '텔레그램 채널'이 가장 적절한 구현의 예를 보여준다거 생각한다.

Reference

  1. UX: Infinite Scrolling vs. Pagination

[컴] MariaDB에서 binaray logging 이 되어 있다면, trigger 를 만들때 super 권한이 필요하다.

aws mysql / aws mariadb

MariaDB에서 binaray logging 이 되어 있다면, trigger 를 만들때 super 권한이 필요하다.

"You do not have the SUPER privilege and binary logging is enabled" 

자동 백업 기능(automated backups)은 MySQL에 대해 바이너리 로깅(binary logging)을 켤지, 끌지를 결정한다.

  • 바이너리 로깅 on: 백업 보존 기간(backup retention period)을 0이 아닌 양수 값으로 설정한다.
  • 바이너리 로깅 off: 백업 보존 기간을 0으로 설정한다.

자동백업을 활성화하는 법: Working with backups - Amazon Relational Database Service

  • Creating triggers with binary logging enabled requires SUPER privilege | Troubleshooting for Amazon RDS - Amazon Relational Database Service

    • 바이너리 로깅(binary loggin)이 활성화된 경우 trigger를 사용하려면 SUPER 권한이 필요하다.
    • 이 권한은 RDS for MySQL 및 RDS for MariaDB DB instance에서는 제한이 되어있다.(참고로, 자동으로 만들어지는 user 계정도 SUPER 를 가지고 있지 않다.)
    • SUPER 권한이 없어도 log_bin_trust_function_creators 매개 변수를 true로 설정하여 binary logging이 활성화된 상황에서 trigger를 만들 수 있다.
    • log_bin_trust_function_creators를 true로 설정하려면 새 DB parameter group을 생성하거나 기존 DB parameter group을 수정하면 된다.
      (참고로, 이미 RDS 가 run 하고 있을때 수정했다면, reboot 이 필요할 수 있다.)

현재 RDS 의 binray logging 이 활성화됐는지 확인

현재 RDS 의 binray logging 이 활성화됐는지는 다음 query 로 알 수 있다.

show global variables like 'log_bin'; 

version 확인

참고로 binary log 가 활성됐을 때 db version 을 확인하면 아래처럼 ‘-log’ 가 붙어서 보인다.

-- binary loggin on
> select version();
'10.6.10-MariaDB-log'

-- binary loggin off
> select version();
'10.6.10-MariaDB'

[컴] terraform 에서 ebs block 을 추가하고, user_data 사용하는 예시

디버그 요령 / 테라폼 요령 /

terraform 에서 ebs block 을 추가하고, user_data 사용하는 예시

data "template_file" "my_user_data" {
  # template = file("../../scripts/init-fish.yml")
  template = templatefile(
    "../../scripts/init-fish.tftpl",
    {
      bashrc  = "${indent(6, file("../../scripts/.bashrc"))}"
    }
  )
}


output "debug_user_data_output" {
  value = data.template_file.my_user_data.rendered
}

resource "aws_instance" "stage_a" {
  ...

  user_data = data.template_file.my_user_data.rendered
}

resource "aws_ebs_volume" "stage_a" {
  availability_zone = "ap-northeast-2a"
  size              = 10 # GB
  iops              = 3000
  type              = "gp3"
}

resource "aws_volume_attachment" "ebs_att" {
  device_name = "/dev/sdf"
  volume_id   = aws_ebs_volume.stage_a.id
  instance_id = aws_instance.stage_a.id
}
#cloud-config

packages:
  - curl

# .bashrc file
write_files:
  - path: /home/admin/.bashrc
    content: |
      "${bashrc}"
  - path: /home/admin/.profile
    content: |
      if [ -n "$BASH_VERSION" ]; then
          # include .bashrc if it exists
          if [ -f "$HOME/.bashrc" ]; then
              . "$HOME/.bashrc"
          fi
      fi

bootcmd:
  # format 하고
  - test -z "$(blkid /dev/nvme1n1)" && mkfs -t ext4 -L home_admin /dev/nvme1n1
  - mkdir -p /home/myuser

mounts:
  # mount
  - [ "/dev/nvme1n1", "/home/myuser", "ext4", "defaults,nofail", "0", "2" ]

runcmd:
  # set .bashrc
  - cp -f /home/bashrc_template_by_cloud_init /home/myuser/.bashrc
  - chown admin:admin /home/admin/.bashrc
# .bashrc, ${}를 사용하려면 $${} 를 사용해야 한다. terraform 에서도 ${} 를 사용하기 때문에. 나머지 $ 들은 괜찮다.
if [ -n "$force_color_prompt" ]; then
  if [ -x /usr/bin/tput ] && tput setaf 1 >&/dev/null; then
      # We have color support; assume it's compliant with Ecma-48
      # (ISO/IEC-6429). (Lack of such support is extremely rare, and such
      # a case would tend to support setf rather than setaf.)
      color_prompt=yes
  else
      color_prompt=
  fi
fi

if [ "$color_prompt" = yes ]; then
    PS1='$${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$$ '
else
    PS1='$${debian_chroot:+($debian_chroot)}\u@\h:\w\$ '
fi

data template 의 output 을 확인

다음 처럼 output 을 추가하고, terraform plan 을 하면 output 이 template_file이 어떤 모습으로 생성되는지 확인할 수 있다.

...
output "debug_user_data_output" {
  value = data.template_file.my_user_data.rendered
}

aws cloud init debug

  1. aws management console 에서 stop instance 를 한 후 Actions -> Instance settings -> Edit user data 를 하자.
  2. 수정후 start instance
  3. cat /var/log/cloud-init-output.log

/var/log/cloud-init-output.log

aws 에서 cloud init 에 대한 log는 /var/log/cloud-init-output.log 에서 확인할 수 있다.

See Also

  1. 쿠…sal: [컴] aws 에서 cloud-init 을 이용해서 ebs attach

Reference

  1. grep - How to escape special characters in Terraform local-exec command line - Stack Overflow

[컴] aws 에서 cloud-init 을 이용해서 ebs attach

 

aws 에서 cloud-init 을 이용해서 ebs attach

aws 에서 ebs block 을 attach 하는 경우, instance 가 생성된 후 로그인을 하고, mkfs 를 해줘야 한다.

이것을 cloud-init 을 통해 해보려 했다.

ref. 2 에서 이야기처럼, 문제는 instance 가 run 되고 나서 ebs 가 인식되는 듯 하다. 그래서 cloud-init 시점에 mount 나 mkfs 명령어가 제대로 동작하지 않았다.

그래서 결론은 runcmd 에 sleep 을 주는 것으로 해결했다.

첫번째 시도, disk_setup, fs_setup

fs_setup을 통한 방법은 ref.1 을 보면, 이제 amazon linux 에서도 지원한다고 한다. 그래서 일단 시도해봤다. 아래 cloud-config 에서 ‘2nd Attempt’ 대신 1st Attempt 를 사용했다.

이 경우에는 처음 실행후, 다시 cloud-init 을 하는 경우에 mount 가 됐다.

  • instance running
  • login
  • sudo cloud-init clean
  • reboot
#cloud-config

packages:
  - curl

bootcmd:
 - mkdir -p /services

# ----------------
#
# 1st Attempt
#
# ----------------
# disk_setup:
#   /dev/nvme1n1:
#     table_type: gpt
#     layout: true
#     overwrite: false
#
# fs_setup:
#   - label: fs1
#     filesystem: ext4
#     device: /dev/nvme1n1
#     partition: auto
#     cmd: mkfs -t %(filesystem)s -L %(label)s %(device)s
#
# mounts:
#  - [ "/dev/nvme1n1", "/services"]

# ----------------
#
# 2nd Attempt
#
# ----------------
runcmd:
  - chown -R admin:admin /home/admin/
  - sleep 1m
  - test -z "$(blkid /dev/nvme1n1)" && mkfs -t ext4 -L fds1 /dev/nvme1n1
  - mount /dev/nvme1n1 /services
  - echo '/dev/nvme1n1  /services auto defaults,nofail,comment=cloudconfig 0 2' >> /etc/fstab

2번째 시도, sleep 1m

약 1분 정도 sleep 을 줬다. 참고로 sleep 중에도 ssh login 은 가능했다.

이 경우 정상적으로 mount 를 완료했다.

See Also

  1. cloud init debugging 방법 : Debugging cloud-init — cloud-init 22.2.2 documentation

Reference

  1. amazon linux 2023 에서 fs_setup이 가능한 듯 보인다.
  1. amazon ec2 - cloud-init: delay disk_setup and fs_setup - Stack Overflow : volume attachment 가 instance 가 완전히 실행이 된 이후에 되는 경우가 있다. 그 경우에 대한 fs_setup을 위해서 reboot 을 하게 하는 cloud-config 를 보여준다. sleep 에 대한 이야기도 여기에 있다.

[컴] terraform 에서 공통 변수 사용법

 

테라폼 / 사용법 / common / global variable /

terraform 에서 공통 변수 사용법

terraform 에서 환경변수 예시

export TF_VAR_test_var="testvar"

terraform에서 다음처럼 변수를 선언하고 사용하면 된다.

variable "test_var" {
    type        = string
    description = "This is an example input variable using env variables."
    default = "testvar"
}

resource "aws_instance" "tf-dev-auth-c" {
  tag = {
    test = var.test_var
  }
  ...
}

See Also

  1. 쿠…sal: [컴] Terraform 사용법

[컴] Airflow 에서 일정기간 후에 log 를 지우는 법

 

log retention / 보존기간 설정 /

Airflow 에서 일정기간 후에 log 를 지우는 법

기본적으로 airflow 에서 지원하는 retention 설정은 없는 듯 하다. 다음 글들에서는 airflow 를 이용해서 주기적으로 airflow 의 log 를 지우는 방법을 보여준다.

  1. A Simple DAG to Quickly Purge Old Airflow Logs | by Christine Betadam | EatCodePlay
  2. python - Configure logging retention policy for Apache airflow - Stack Overflow

airflow.cfg 를 변경후 server restart

설정에 따라 다르다는 듯, 어떤 값들은 reload 하려면 airflow 를 restart 해야 한다.

airflow restart 방법

kill -HUP <scheduler_process_id>
kill -HUP <webserver_process_id>

nohup airflow webserver --port 8080 1> /dev/null 2>&1 &
nohup airflow scheduler 1> /dev/null 2>&1 &
  • airflow scheduler -- DagFileProcessorManager 이 죽지 않는 경우가 있다. 이 경우는 수동으로 kill 해주자. 참고로 scheduler 가 떠 있으면, ‘DagFileProcessorManager’ 를 kill 해도 다시 실행된다.
  • webserver 는 ‘airflow webserver’ 가 아닌 ’gunicorn’을 kill 한다.