기본 콘텐츠로 건너뛰기

PostgreSQL 동시성 제어 문제, 튜닝으로 해결하기

PostgreSQL 동시성 제어 문제, 튜닝으로 해결하기

AI 생성 이미지: PostgreSQL 동시성 제어 문제 해결을 위한 튜닝 가이드
AI 생성 이미지: PostgreSQL 동시성 제어 문제 해결을 위한 튜닝 가이드

PostgreSQL 동시성 제어, 왜 중요한가?

엔터프라이즈 환경에서 PostgreSQL을 안정적으로 운영하려면 동시성 제어가 핵심입니다. 동시성 제어는 여러 트랜잭션이 동시에 데이터에 접근할 때 데이터의 일관성과 무결성을 지키는 중요한 메커니즘입니다. 마치 여러 사람이 동시에 문서를 수정할 때 데이터가 꼬이거나 덮어쓰여지는 상황을 방지하는 것과 같습니다.

PostgreSQL은 MVCC(Multi-Version Concurrency Control)라는 독자적인 모델을 통해 높은 수준의 동시성을 제공합니다. MVCC는 각 트랜잭션이 특정 시점의 데이터 스냅샷을 보도록 하여, 다른 트랜잭션의 실시간 변경 사항에 영향을 받지 않도록 설계되었습니다. 덕분에 읽기 작업이 쓰기 작업을 방해하지 않고, 쓰기 작업 간의 대기 시간도 최소화하여 효율성을 높입니다.

하지만 MVCC 모델에서도 다음과 같은 동시성 제어 관련 문제들이 발생할 수 있으며, 이는 PostgreSQL 동시성 제어 문제 해결을 위한 튜닝 가이드의 시작점이 됩니다:

  • 데드락 (Deadlock): 두 개 이상의 트랜잭션이 서로가 가진 리소스를 무한정 기다리며 멈추는 상황입니다.
  • 락 경합 (Lock Contention): 여러 트랜잭션이 동일한 데이터나 객체에 대한 락을 얻으려고 경쟁하면서 발생하는 성능 저하 현상입니다.
  • VACUUM 관련 문제: MVCC는 오래된 데이터를 정리하기 위해 `VACUUM` 프로세스가 필수적입니다. 이 프로세스가 제대로 작동하지 않거나 누락되면 "vacuum too old" 오류가 발생하거나 불필요한 공간이 늘어나 성능 저하로 이어질 수 있습니다.

이러한 동시성 문제는 서비스 가용성과 사용자 경험에 직접적인 영향을 미칩니다. 따라서 PostgreSQL 동시성 제어 문제 해결을 위한 튜닝 가이드를 숙지하고 사전에 대비하는 것이 매우 중요합니다. 예를 들어, 트랜잭션 처리 순서를 최적화하거나, 락을 획득하는 시간을 제한하는 등의 실질적인 조치를 통해 문제를 예방할 수 있습니다.

락(Lock)과 트랜잭션 격리 수준 이해하기

PostgreSQL 동시성 제어 문제 해결을 위한 튜닝 가이드의 첫걸음은 락(Lock)과 트랜잭션 격리 수준(Transaction Isolation Level)에 대한 명확한 이해입니다. PostgreSQL은 MVCC(Multi-Version Concurrency Control)라는 강력한 메커니즘을 기반으로 동시성을 높입니다. MVCC는 데이터의 여러 버전을 유지함으로써 읽기 작업이 쓰기 작업을 차단하지 않도록 하여, 동시 읽기/쓰기 성능을 크게 향상시킵니다.

하지만 MVCC만으로는 모든 데이터 무결성을 완벽하게 보장하기 어렵기에, 특정 상황에서는 락이 필수적으로 사용됩니다. PostgreSQL은 다양한 락 레벨을 제공하며, 대표적으로 테이블 레벨의 ACCESS SHARE, EXCLUSIVE와 행 레벨의 ROW EXCLUSIVE 등이 있습니다. 이러한 락들은 동시 접근을 정교하게 제어하여 예상치 못한 데이터 변경을 효과적으로 방지합니다.

트랜잭션 격리 수준은 동시 트랜잭션 간의 상호작용 방식을 정의하는 매우 중요한 요소입니다. PostgreSQL은 다음 네 가지 격리 수준을 지원하며, 각 수준은 동시성 문제의 발생 가능성을 다르게 제어합니다:

  • READ UNCOMMITTED: 가장 낮은 격리 수준으로, 다른 트랜잭션에서 아직 커밋되지 않은 데이터(Dirty Read)를 읽을 수 있습니다.
  • READ COMMITTED: 커밋된 데이터만 읽을 수 있어 Dirty Read는 방지하지만, Non-repeatable Read와 Phantom Read가 발생할 수 있습니다. (PostgreSQL의 기본값입니다.)
  • REPEATABLE READ: 트랜잭션 내에서 동일한 쿼리를 반복 실행할 때 항상 동일한 결과를 보장하며, Non-repeatable Read를 효과적으로 방지합니다. 다만, Phantom Read는 여전히 발생 가능합니다.
  • SERIALIZABLE: 트랜잭션이 마치 순차적으로 실행되는 것처럼 동일한 결과를 보장하여 모든 종류의 동시성 문제를 원천적으로 방지합니다. 하지만 이로 인해 성능 저하가 발생할 수 있다는 점을 유념해야 합니다.

각 격리 수준별 동작 방식을 깊이 있게 이해하는 것이 PostgreSQL 동시성 제어 문제 해결을 위한 튜닝 가이드의 핵심입니다. 예를 들어, `READ COMMITTED` 수준에서 다른 트랜잭션에 의해 데이터가 수정되고 커밋될 때 Non-repeatable Read가 발생할 수 있습니다. 실무에서는 특정 작업에서 `SERIALIZABLE` 수준을 사용하기보다, `REPEATABLE READ`를 적용하고 발생 가능한 Phantom Read에 대한 애플리케이션 레벨의 처리를 고려하는 것이 성능과 안정성 사이의 균형을 맞추는 좋은 전략이 될 수 있습니다. 이러한 락과 격리 수준의 복잡한 상호작용을 정확히 파악하는 것이 성능 최적화를 위한 첫걸음입니다.

동시성 문제 진단: 잠금 경합(Lock Contention) 탐지

PostgreSQL에서 발생하는 성능 저하의 주요 원인 중 하나는 바로 잠금 경합(Lock Contention)입니다. 여러 트랜잭션이 동일한 데이터에 동시에 접근하려 할 때 발생하는 이 문제는 시스템 전반의 응답 속도를 늦출 수 있습니다. 이러한 동시성 문제를 효과적으로 해결하기 위한 첫걸음은 잠금 경합을 정확하게 진단하는 것입니다. 이를 위해 pg_locks, pg_stat_activity, pg_stat_statements와 같은 시스템 뷰를 활용하는 방법을 살펴보겠습니다.

1. pg_lockspg_stat_activity 활용

pg_locks 뷰는 현재 데이터베이스에 걸려 있는 모든 잠금 정보를 담고 있으며, pg_stat_activity 뷰는 현재 실행 중인 세션들에 대한 상세 정보를 제공합니다. 이 두 뷰를 함께 조회하면, 어떤 세션이 특정 객체에 잠금을 보유하고 있고, 다른 세션들이 해당 잠금을 기다리고 있는지 명확하게 파악할 수 있습니다. 특히, pg_locks 뷰에서 granted = false 조건을 사용하거나 pg_stat_activity 뷰에서 wait_event_type = 'Lock' 조건을 필터링하면, 현재 잠금을 기다리고 있는 세션과 그 원인을 신속하게 식별할 수 있습니다. 이를 통해 잠금 경합의 직접적인 원인을 빠르게 찾아내는 것이 중요합니다.

예를 들어, 잠금 대기 중인 세션들을 확인하려면 다음과 같은 쿼리를 사용할 수 있습니다:

 SELECT     pid,     usename,     client_addr,     wait_event_type,     wait_event,     query FROM     pg_stat_activity WHERE     wait_event_type = 'Lock'; 

2. pg_stat_statements를 통한 잠금 대기 시간 분석

pg_stat_statements 확장 기능은 실행된 쿼리들의 성능 통계를 수집하여 제공합니다. 이 중 blk_wait_time (블록 I/O 대기 시간)은 잠금으로 인해 쿼리 실행이 지연된 시간을 나타내므로, 잠금 문제로 인해 성능 저하를 겪는 쿼리를 찾아내는 데 매우 유용합니다. 이 뷰를 분석하여 blk_wait_time이 유독 높은 쿼리들을 식별하고 해당 쿼리의 최적화를 진행하는 것이 PostgreSQL 동시성 제어 문제 해결을 위한 튜닝 가이드의 다음 단계가 될 수 있습니다. 이 확장 기능은 사용 전에 설치 및 활성화가 필요합니다.

잠금 대기 시간이 긴 상위 5개 쿼리를 확인하는 쿼리는 다음과 같습니다:

 SELECT     query,     calls,     (blk_read_time + blk_write_time) AS total_block_io_time,     (blk_read_time + blk_write_time) / calls AS avg_block_io_time FROM     pg_stat_statements ORDER BY     avg_block_io_time DESC LIMIT 5; 

이러한 시스템 뷰들을 종합적으로 활용하여 잠금 경합의 근본 원인을 파악하는 것은 PostgreSQL 동시성 제어 문제 해결을 위한 튜닝 가이드의 필수적인 첫걸음입니다. 더불어, 애플리케이션 레벨에서는 트랜잭션 격리 수준(Isolation Level)을 적절히 설정하고, 불필요한 잠금을 최소화하는 쿼리 작성 습관을 들이는 것도 중요한 예방책이 될 수 있습니다.

튜닝 포인트 1: 효과적인 인덱싱 전략 수립

PostgreSQL 환경에서 동시성 문제를 해결하는 데 있어 쿼리 성능 최적화는 매우 중요합니다. 복잡한 트랜잭션 처리 시 발생하는 잠금 경합은 성능 저하의 주요 원인으로 작용하는데, 이를 완화하는 가장 효과적인 방법 중 하나가 바로 '적절한 인덱싱 전략'입니다.

잘 설계된 인덱스는 데이터베이스가 쿼리 실행에 필요한 데이터만 빠르고 정확하게 찾아낼 수 있도록 지원합니다. 이 덕분에 전체 테이블을 훑는 비효율적인 작업을 줄일 수 있으며, 결과적으로 잠금 획득 및 해제 빈도를 낮추고 트랜잭션 간 대기 시간을 단축하여 동시성 제어 문제를 해결하는 데 직접적인 도움을 줍니다.

인덱스 설계 및 관리 핵심 사항:

  • 선택성이 높은 컬럼에 집중: 검색 조건으로 자주 사용되며 값이 다양한 컬럼(예: 사용자 ID, 고유 식별자)에 인덱스를 생성하면 효율성을 극대화할 수 있습니다.
  • 쿼리 패턴 분석을 통한 설계: WHERE 절이나 JOIN 조건에 주로 사용되는 컬럼을 중심으로 인덱스를 구성하는 것이 좋습니다. 여러 컬럼을 함께 사용해야 하는 경우에는 복합 인덱스(Composite Indexes)를 고려하되, 컬럼 순서 또한 실제 쿼리 패턴에 맞춰 최적화해야 합니다.
  • Partial Indexes의 전략적 활용: 특정 상태값이나 조건에 해당하는 데이터 조회 빈도가 높다면, 해당 조건으로 구성된 부분 인덱스(Partial Indexes)를 생성하여 인덱스 크기를 줄이고 성능을 향상시킬 수 있습니다. 예를 들어, '활성' 상태의 사용자만 자주 조회한다면 `WHERE status = 'active'` 조건으로 부분 인덱스를 만들 수 있습니다.
  • 정기적인 인덱스 점검 및 최적화: 사용되지 않는 인덱스는 과감히 제거하고, 데이터 변경으로 인해 단편화된 인덱스는 `REINDEX` 명령어를 통해 재구성하는 것이 동시성 문제 해결을 위한 중요한 관리 요소입니다. 더불어, `ANALYZE` 명령어로 최신 통계 정보를 유지하여 쿼리 플래너가 최적의 실행 계획을 수립하도록 지원해야 합니다.

튜닝 포인트 2: 트랜잭션 관리 및 쿼리 최적화

PostgreSQL에서 발생하는 동시성 문제는 종종 비효율적인 트랜잭션 처리 방식이나 최적화되지 않은 쿼리에서 비롯됩니다. 본 섹션에서는 PostgreSQL 동시성 제어 문제 해결을 위한 튜닝 가이드의 핵심이라 할 수 있는 트랜잭션 및 쿼리 최적화 방안을 상세히 다룹니다.

트랜잭션 관리 최적화

너무 길거나 불필요하게 확장된 트랜잭션은 잠금 경합을 심화시켜 동시성 성능 저하의 주요 원인이 됩니다. 따라서 각 트랜잭션의 범위를 가능한 한 좁게 유지하고, 데이터베이스 작업과 애플리케이션 로직을 명확히 분리하는 것이 중요합니다. 대량 데이터 처리 시에는 단일 트랜잭션 대신 `COPY` 명령어나 배치 처리 방식을 도입하여 효율성을 높이는 것을 고려해야 합니다. 또한, PostgreSQL의 MVCC 아키텍처를 효과적으로 활용하기 위해서는 주기적인 `VACUUM` 및 `ANALYZE` 작업이 필수적입니다. 자동 `VACUUM` 기능을 활성화하고, 시스템 환경에 맞춰 관련 파라미터를 조정하여 오래된 행 버전을 정리하고 쿼리 플래너가 최신 통계 정보를 활용하도록 유지해야 합니다. 실무 팁: 개발팀과 협력하여 애플리케이션 레벨에서 트랜잭션 커밋 시점을 최적화하고, 불필요한 잠금을 유발하는 패턴이 없는지 주기적으로 점검하는 것이 좋습니다.

쿼리 최적화 및 분석

최적화되지 않은 쿼리는 시스템 리소스 낭비와 잠금 경합을 가중시킵니다. 쿼리의 실행 계획을 정확히 파악하기 위해 `EXPLAIN` 및 `EXPLAIN ANALYZE` 명령어를 적극적으로 활용해야 합니다. 이를 통해 병목 현상을 정확히 진단하고, 쿼리에서 빈번하게 사용되는 컬럼에 적절한 인덱스를 생성하거나 기존 인덱스를 개선하는 것이 성능 향상의 지름길입니다. `JOIN` 조건의 효율성을 검토하고, `SELECT *` 대신 반드시 필요한 컬럼만 명시적으로 지정하며, `WHERE` 및 `LIMIT` 절을 효과적으로 사용하여 불필요한 데이터 처리량을 줄이는 것도 중요합니다. 이러한 쿼리 최적화 노력은 PostgreSQL 동시성 제어 문제 해결을 위한 튜닝 가이드의 핵심적인 부분입니다. 앞서 제시된 트랜잭션 관리 및 쿼리 최적화 기법들을 체계적으로 적용한다면, PostgreSQL 시스템의 동시성 문제를 효과적으로 해결하고 전반적인 성능을 크게 향상시킬 수 있을 것입니다.

튜닝 포인트 3: PostgreSQL 설정 파라미터 조정

데이터베이스 서버의 성능을 극대화하기 위한 튜닝 작업에서, 시스템 설정 파라미터 최적화는 핵심적인 요소입니다. 특히 PostgreSQL의 동시성 제어 문제를 해결하려면, 서버의 하드웨어 사양과 실제 워크로드 특성을 세밀하게 분석하여 shared_buffers, work_mem, max_connections와 같은 주요 파라미터를 신중하게 조정해야 합니다. 이러한 조정은 동시성 성능 향상에 결정적인 영향을 미칩니다.

주요 파라미터 튜닝 전략

shared_buffers는 디스크 I/O 부담을 줄이는 데 필수적인 캐시 영역의 크기를 결정합니다. 일반적으로 시스템 메모리의 25% 수준을 권장하지만, 실제 워크로드 패턴에 따른 충분한 테스트가 반드시 필요합니다. 복잡한 쿼리 실행 시 각 프로세스가 사용하는 임시 메모리 공간인 work_mem이 부족하면 디스크 스와핑으로 인해 성능이 크게 저하될 수 있습니다. 동시성이 높은 환경에서는 이 설정값이 각 세션별로 할당되므로, 전체 시스템 메모리 사용량을 고려한 섬세한 접근이 중요합니다. max_connections는 동시에 접속 가능한 최대 사용자 수를 제한하는데, 이 값을 과도하게 높게 설정하면 서버 자원이 고갈될 위험이 있습니다. 따라서 애플리케이션의 동시성 요구사항과 서버 자원 현황을 종합적으로 고려하여 적정 수준을 설정하고, pgbouncer와 같은 연결 풀러(connection pooler) 활용을 적극적으로 고려해 보세요.

이 외에도 maintenance_work_mem (백업, 복구 등 유지보수 작업 시 사용)이나 effective_cache_size (쿼리 플래너가 최적의 실행 계획을 세우는 데 참고하는 값) 등 다양한 파라미터가 동시성 제어 및 전반적인 데이터베이스 성능에 영향을 미칩니다. 이러한 파라미터들은 서로 복합적인 영향을 주고받기 때문에, 개별적인 튜닝보다는 시스템의 전체적인 부하와 워크로드 특성을 종합적으로 이해하고 접근하는 것이 중요합니다. 예를 들어, 대규모 데이터 처리량이 많은 워크로드에서는 shared_buffers를 늘리고, 복잡한 조인 연산이 빈번한 경우 work_mem을 적절히 증설하는 식입니다. 이러한 PostgreSQL 동시성 제어 문제 해결을 위한 튜닝 가이드에 따른 파라미터 조정 후에는 반드시 충분한 테스트와 지속적인 모니터링을 통해 실제 성능 변화를 면밀히 검증해야 합니다.

경험에서 배운 점

엔터프라이즈 환경에서 PostgreSQL 동시성 제어 문제는 종종 발생하는 까다로운 문제입니다. 특히 트랜잭션 격리 수준 설정이 부적절하거나 VACUUM 작업이 제대로 수행되지 않으면, deadlock이나 lock timeout 같은 현상이 빈번하게 나타날 수 있습니다. 저희 팀도 처음에는 단순히 쿼리 최적화만으로 이 문제를 해결하려다 어려움을 겪었습니다. 하지만 실제로는 데이터베이스 설정, 특히 max_connections, shared_buffers, work_mem과 같은 메모리 관련 파라미터와 autovacuum 설정의 균형이 중요하다는 것을 깨달았습니다. 워크로드에 맞춰 VACUUM 관련 파라미터(autovacuum_vacuum_threshold, autovacuum_vacuum_scale_factor)를 조정하고, 필요에 따라 수동 VACUUM을 예약하는 것이 필수적이었습니다.

가장 큰 실수는 VACUUM의 중요성을 간과하고 애플리케이션 레벨에서의 락 경합에만 집중했던 것입니다. pg_stat_activitypg_locks 뷰를 통해 어떤 쿼리가 락을 차지하고 있는지 파악하는 것도 중요하지만, 근본적인 원인은 종종 오래된 트랜잭션이나 UPDATE/DELETE 작업으로 인해 쌓인 dead tuplesVACUUM으로 처리되지 않는 데 있었습니다. dead tuples가 증가하면 인덱스 스캔 효율이 저하되고, 결국 락 경합을 심화시키는 악순환으로 이어집니다. 따라서 pg_stat_user_tables 뷰를 주기적으로 확인하여 n_dead_tup 값이 비정상적으로 높은 테이블을 식별하고, 해당 테이블의 autovacuum 설정을 집중적으로 튜닝하는 것이 효과적이었습니다.

이러한 문제가 재발하는 것을 방지하기 위해 저희는 다음과 같은 체크리스트를 마련했습니다. 첫째, 모든 PostgreSQL 인스턴스에서 autovacuum이 활성화되어 있는지, 그리고 주요 테이블의 autovacuum 관련 파라미터가 적절하게 설정되어 있는지 정기적으로 점검합니다. 둘째, pg_stat_activity에서 idle in transaction 상태로 장시간 유지되는 트랜잭션이 있는지 모니터링하고, 애플리케이션 로직 개선을 통해 이를 최소화합니다. 셋째, pg_stat_user_tables에서 n_live_tup 대비 n_dead_tup 비율이 특정 임계값을 초과하는 테이블을 식별하여 선제적으로 튜닝합니다. 마지막으로, 격리 수준 설정은 꼭 필요한 경우가 아니라면 READ COMMITTED를 기본값으로 유지하고, SERIALIZABLE과 같이 더 높은 격리 수준을 사용할 경우 발생할 수 있는 성능 저하 및 동시성 이슈를 충분히 인지하고 철저히 테스트해야 합니다.

AI 생성 이미지: PostgreSQL 동시성 제어 문제 해결을 위한 튜닝 가이드
AI 생성 이미지: PostgreSQL 동시성 제어 문제 해결을 위한 튜닝 가이드

댓글

이 블로그의 인기 게시물

Java Servlet Request Parameter 완전 정복 — GET/POST 모든 파라미터 확인 & 디버깅 예제 (Request Parameter 전체보기)

Java Servlet Request Parameter 완전 정복 — GET/POST 모든 파라미터 확인 & 디버깅 예제 Java Servlet Request Parameter 완전 정복 웹 애플리케이션에서 클라이언트로부터 전달되는 Request Parameter 를 확인하는 것은 필수입니다. 이 글에서는 Java Servlet 과 JSP 에서 GET/POST 요청 파라미터를 전체 출력하고 디버깅하는 방법을 다양한 예제와 함께 소개합니다. 1. 기본 예제: getParameterNames() 사용 Enumeration<String> params = request.getParameterNames(); System.out.println("----------------------------"); while (params.hasMoreElements()){ String name = params.nextElement(); System.out.println(name + " : " + request.getParameter(name)); } System.out.println("----------------------------"); 위 코드는 요청에 포함된 모든 파라미터 이름과 값을 출력하는 기본 방법입니다. 2. HTML Form과 연동 예제 <form action="CheckParamsServlet" method="post"> 이름: <input type="text" name="username"><br> 이메일: <input type="email" name="email"><b...

PostgreSQL 달력(일별,월별)

SQL 팁: GENERATE_SERIES로 일별, 월별 날짜 목록 만들기 SQL 팁: GENERATE_SERIES 로 일별, 월별 날짜 목록 만들기 데이터베이스에서 통계 리포트를 작성하거나 비어있는 날짜 데이터를 채워야 할 때, 특정 기간의 날짜 목록이 필요할 수 있습니다. PostgreSQL과 같은 데이터베이스에서는 GENERATE_SERIES 함수를 사용하여 이 작업을 매우 간단하게 처리할 수 있습니다. 1. 🗓️ 일별 날짜 목록 생성하기 2020년 1월 1일부터 12월 31일까지의 모든 날짜를 '1 day' 간격으로 생성하는 쿼리입니다. WITH date_series AS ( SELECT DATE(GENERATE_SERIES( TO_DATE('2020-01-01', 'YYYY-MM-DD'), TO_DATE('2020-12-31', 'YYYY-MM-DD'), '1 day' )) AS DATE ) SELECT DATE FROM date_series 이 쿼리는 WITH 절(CTE)을 사용하여 date_series 라는 임시 테이블을 만들고, GENERATE_SERIES 함수로 날짜를 채웁니다. 결과 (일별 출력) 2. 📅 월별 날짜 목록 생성하기 동일한 원리로, 간격을 '1 MONTH' 로 변경하면 월별 목록을 생성할 수 있습니다. TO...

CSS로 레이어 팝업 화면 가운데 정렬하는 방법 (top·left·transform 완전 정리)

레이어 팝업 센터 정렬, 이 코드만 알면 끝 (CSS 예제 포함) 이벤트 배너나 공지사항을 띄울 때 레이어 팝업(center 정렬) 을 깔끔하게 잡는 게 생각보다 어렵습니다. 화면 크기가 변해도 가운데에 고정되고, 모바일에서도 자연스럽게 보이게 하려면 position , top , left , transform 을 정확하게 이해해야 합니다. 이 글에서는 아래 내용을 예제로 정리합니다. 레이어 팝업(center 정렬)의 기본 개념 자주 사용하는 position: absolute / fixed 정렬 방식 질문에서 주신 스타일 top: 3.25%; left: 50%; transform: translateX(-50%) 의 의미 실무에서 바로 쓰는 반응형 레이어 팝업 HTML/CSS 예제 1. 레이어 팝업(center 정렬)이란? 레이어 팝업(레이어 팝업창) 은 새 창을 띄우는 것이 아니라, 현재 페이지 위에 div 레이어를 띄워서 공지사항, 광고, 이벤트 등을 보여주는 방식을 말합니다. 검색엔진(SEO) 입장에서도 같은 페이지 안에 HTML이 존재 하기 때문에 팝업 안의 텍스트도 정상적으로 인덱싱될 수 있습니다. 즉, “레이어 팝업 센터 정렬”, “레이어 팝업 만드는 방법”과 같이 관련 키워드를 적절히 넣어주면 검색 노출에 도움이 됩니다. 2. 질문에서 주신 레이어 팝업 스타일 분석 질문에서 주신 스타일은 다음과 같습니다. <div class="layer-popup" style="width:1210px; z-index:9001; position:absolute; top:3.25%; left:50%; transform:translateX(-50%);"> 레이어 팝업 내용 <...