veluxer62 / veluxer62.github.io

veluxer's blog
http://veluxer62.github.io
MIT License
2 stars 0 forks source link

7월 개발일지 #548

Closed veluxer62 closed 3 years ago

veluxer62 commented 3 years ago

https://www.coupang.com/vp/products/4945355563?itemId=6522590198&vendorItemId=73817074279&src=1191000&spec=10999999&addtag=400&ctag=4945355563&lptag=CFM91783024&itime=20210702140500&pageType=PRODUCT&pageValue=4945355563&wPcid=16225971019382016078617&wRef=&wTime=20210702140500&redirect=landing&isAddedCart=

키보드 사고싶다

veluxer62 commented 3 years ago

graphene_sqlalchemy 라이브러리는 페이지네이션을 수행할때 불필요한 count 쿼리를 수행합니다. (hasNext를 호출 하지 않을 때도 말이죠) 아래 코드는 graphene_sqlalchemy 라이브러리의 resolve_connection함수 구현코드입니다.

@classmethod
def resolve_connection(cls, connection_type, model, info, args, resolved):
    if resolved is None:
        resolved = cls.get_query(model, info, **args)
    if isinstance(resolved, Query):
        _len = resolved.count() # <---------- 여기서 카운트 쿼리를 무조건 실행함
    else:
        _len = len(resolved)
    connection = connection_from_list_slice(
        resolved,
        args,
        slice_start=0,
        list_length=_len,
        list_slice_length=_len,
        connection_type=connection_type,
        pageinfo_type=PageInfo,
        edge_type=connection_type.Edge,
    )
    connection.iterable = resolved
    connection.length = _len
    return connection

왜 전체 건수가 필요한지 보니 graphene라이브러리가 connection_from_list_slice 함수에서 slice_end를 만들기 위해서는 전체 건수가 필요해서 그런 것입니다.

def connection_from_list_slice(list_slice, args=None, connection_type=None,
                               edge_type=None, pageinfo_type=None,
                               slice_start=0, list_length=0, list_slice_length=None):
    """
    Given a slice (subset) of an array, returns a connection object for use in
    GraphQL.
    This function is similar to `connectionFromArray`, but is intended for use
    cases where you know the cardinality of the connection, consider it too large
    to materialize the entire array, and instead wish pass in a slice of the
    total result large enough to cover the range specified in `args`.
    """
    connection_type = connection_type or Connection
    edge_type = edge_type or Edge
    pageinfo_type = pageinfo_type or PageInfo

    args = args or {}

    before = args.get('before')
    after = args.get('after')
    first = args.get('first')
    last = args.get('last')
    if list_slice_length is None:
        list_slice_length = len(list_slice)
    slice_end = slice_start + list_slice_length # <----------------- 여기
    before_offset = get_offset_with_default(before, list_length)
    after_offset = get_offset_with_default(after, -1)

    start_offset = max(
        slice_start - 1,
        after_offset,
        -1
    ) + 1
    end_offset = min(
        slice_end,
        before_offset,
        list_length
    )
    if isinstance(first, int):
        end_offset = min(
            end_offset,
            start_offset + first
        )
    if isinstance(last, int):
        start_offset = max(
            start_offset,
            end_offset - last
        )

    # If supplied slice is too large, trim it down before mapping over it.
    _slice = list_slice[
        max(start_offset - slice_start, 0):
        list_slice_length - (slice_end - end_offset)
    ]
    edges = [
        edge_type(
            node=node,
            cursor=offset_to_cursor(start_offset + i)
        )
        for i, node in enumerate(_slice)
    ]

    first_edge_cursor = edges[0].cursor if edges else None
    last_edge_cursor = edges[-1].cursor if edges else None
    lower_bound = after_offset + 1 if after else 0
    upper_bound = before_offset if before else list_length

    return connection_type(
        edges=edges,
        page_info=pageinfo_type(
            start_cursor=first_edge_cursor,
            end_cursor=last_edge_cursor,
            has_previous_page=isinstance(last, int) and start_offset > lower_bound,
            has_next_page=isinstance(first, int) and end_offset < upper_bound
        )
    )

앞으로 점차적으로는 해당 라이브러리를 사용하지 않고 Query와 Model을 분리하면서 직접 페이지네이션을 구현하려고 합니다. 그렇다면 위 라이브러리로 인해 불필요한 count 쿼리를 수행하지 않을 수 있구요, 페이지네이션은 아래 코드와 같이 전체 건수가 없이도 슬라이싱을 할 수 있습니다.

session.query(VendorReview).offset(paging.offset).limit(paging.limit).all()

# 또는

session.query(VendorReview)[paging.offset:paging.limit]

GraphQL의 표준으로 잠깐 얘기를 돌려보면 GraphQL Pagenation을 보면 페이지네이션을 위한 매개변수로 firstoffset, after를 사용하는 것을 볼 수 있습니다. 하지만 graphene 라이브러리에서 사용하는 매개변수는 before, after, first, last로 보입니다. 표준과 다른 이유는 제가 GraphQL의 히스토리를 다 알지 못해서 파악하지는 못했는데 버전의 문제지 않을까 라는 조심스러운 추측을 해봅니다.

다시 본론으로 돌아와서 현재 서버에서 제공하고 있는 페이지네이션은 모호함과 함께 표준을 지키지 못하고 있다고 보입니다. 모호함은 첫번째 페이지임에도 불구하고 {first: 10, last: 10}와 같은 형태로 호출 하고 있다는 것입니다. 표준 대로라면 첫 페이지는 {first: 10} 또는 {first: 10, offset: 0}으로 표현됨이 맞다고 보입니다. (커서 기반이라면 {first: 10, after: '<마지막 커서>'}가 되겠네요)

그래서 표준에 맞게 스펙을 바꾸는게 어떨까 하는 제안을 해봅니다. 물론 신규로 만드는 기능부터 적용하고 시간이 될때 리펙토링을 통해 기존 페이지네이션도 고쳐가는 방법으로 하는 전략을 채택하면 좋을 것 같네요. 이슈로는 하위 호환과 일관성인데요. 하위 호환은 기존 기능의 변경 시 협의를 해야 할 것으로 보이구요, 일관성은 기존에 표준에 맞지 않는 구현을 제대로 바꾼다는 측면에서 어느정도 희생을 해야하지 않을까라는 생각을 조심스레 해봅니다.

veluxer62 commented 3 years ago

OCR 비교

https://github.com/jbarlow83/OCRmyPDF

vs

https://aws.amazon.com/ko/textract/

veluxer62 commented 3 years ago

형태소 분석기

https://bitbucket.org/eunjeon/mecab-ko-dic

https://konlpy.org/en/latest/

veluxer62 commented 3 years ago

webflux timeout 설정

https://www.amitph.com/spring-webflux-timeouts/

veluxer62 commented 3 years ago

mock webserver

https://www.industriallogic.com/blog/integration-testing-with-mockwebserver/

veluxer62 commented 3 years ago

webclient에서 응답 미디어 타입이 text/plain인 경우 dto로 매핑해주기 위해서 codec을 사용하면 된다. 시간되면 한번 활용방법에 대해 적어봐야겠다.

@Bean
fun korailClient(korailProperties: KorailProperties): WebClient {
    return WebClient.builder()
        .baseUrl(korailProperties.host)
        .clientConnector(
            ReactorClientHttpConnector(
                HttpClient.create().responseTimeout(Duration.ofMillis(korailProperties.timeout))
            )
        )
        .codecs {
            it.defaultCodecs()
                .jackson2JsonDecoder(Jackson2JsonDecoder(jacksonObjectMapper(), MimeType.valueOf("text/plain")))
        }
        .build()
}
veluxer62 commented 3 years ago

jacoco gitaction 뱃지 제너레이터

https://github.com/marketplace/actions/jacoco-badge-generator

veluxer62 commented 3 years ago

Jira 세팅을 이것저것 해보고 요구사항에 대한 계층도 만들어 보고 하고 있는데 참 어느것 하나 만족스럽지 못하네Face with open mouth and cold sweat 너무 복잡하게 생각하는게 아닌가 싶기도 하고

sub task는 말 그대로 하위 작업이다. story의 task 용도로 사용하면 나중에 번다운 차트나 번업 차트 등 리포트를 생성할때 estimate값을 불러오지 못한다

component는 서브 프로젝트 용도로 사용할 수 있는데 epic을 그루핑 해주는 용도로 사용하기에 나쁘지 않다. 하지만 상위 티켓으로 사용되는 것이 아니기에 불편함이 있다.

결국 티켓의 계층은 epic과 story(task, backlog 등) 두 계층으로만 나눌 수 있다. 이슈링크를 통해 계층을 표현할 순 있지만 결국 링크일 뿐이라 유연함은 있지만 가시적이지 않다.

veluxer62 commented 3 years ago

BDD 테스트 도구

https://cucumber.io/

veluxer62 commented 3 years ago

jacoco gradle plugin document

https://docs.gradle.org/current/userguide/jacoco_plugin.html

veluxer62 commented 3 years ago

git case sensitive issue

https://stackoverflow.com/questions/17683458/how-do-i-commit-case-sensitive-only-filename-changes-in-git

git mv 명령어를 사용하면 된다

veluxer62 commented 3 years ago

python에서 배열의 index를 함께 조회할때 사용하기 좋은 함수 enumerate

https://realpython.com/python-enumerate/#using-pythons-enumerate

veluxer62 commented 3 years ago

kotlin 에서 padding 문자열 넣을때 사용하면 좋은 함수

https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.text/pad-start.html

veluxer62 commented 3 years ago

아르고 CD

https://coffeewhale.com/kubernetes/gitops/argocd/2020/02/10/gitops-argocd/