728x90

Error description

 

728x90
728x90

Environment

  • Red Hat Enterprise Linux release 8.7 (Ootpa)
  • 4.18.0-425.10.1.el8_7.x86_64
  • EXT4 filesystem without a journal.

Issue

  • IBM Informix Dynamic Server oninit process hanging, resulting in unresponsive/hung database connections due to deadlock on ext4 filesystem mounted without a journal.
  • IBM Informix Dynamic Server oninit process blocked with the following call traces.
[428617.156430] INFO: task oninit:4327 blocked for more than 120 seconds.
[428617.156431]       Not tainted 4.18.0-425.10.1.el8_7.x86_64 #1
[428617.156433] "echo 0 > /proc/sys/kernel/hung_task_timeout_secs" disables this message.
[428617.156433] task:oninit          state:D stack:    0 pid: 4327 ppid:  4300 flags:0x00004084
[428617.156436] Call Trace:
[428617.156437]  __schedule+0x2d1/0x860
[428617.156439]  ? blk_flush_plug_list+0xd7/0x100
[428617.156441]  schedule+0x35/0xa0
[428617.156443]  ext4_unwritten_wait+0x87/0xc0 [ext4]
[428617.156460]  ? finish_wait+0x80/0x80
[428617.156462]  ext4_file_write_iter+0x396/0x3e0 [ext4]
[428617.156478]  aio_write+0xf6/0x1c0
[428617.156480]  ? blk_mq_sched_insert_requests+0x6c/0xf0
[428617.156483]  ? io_submit_one+0x7e/0x3c0
[428617.156484]  ? kmem_cache_alloc+0x13f/0x280
[428617.156486]  io_submit_one+0x131/0x3c0
[428617.156488]  __x64_sys_io_submit+0xa2/0x180
[428617.156489]  ? syscall_trace_enter+0x1ff/0x2d0
[428617.156492]  do_syscall_64+0x5b/0x1b0
[428617.156494]  entry_SYSCALL_64_after_hwframe+0x61/0xc6
[428617.156497] RIP: 0033:0x7fd1d50409bd
[428617.156499] Code: Unable to access opcode bytes at RIP 0x7fd1d5040993.
[428617.156500] RSP: 002b:00000000531c4da8 EFLAGS: 00000246 ORIG_RAX: 00000000000000d1
[428617.156501] RAX: ffffffffffffffda RBX: 00007fd1d698ab78 RCX: 00007fd1d50409bd
[428617.156502] RDX: 00000000531c4de8 RSI: 0000000000000001 RDI: 00007fd1c4593000
[428617.156503] RBP: 00007fd1c4593000 R08: 00000001358df800 R09: 000000005667b8d8
[428617.156504] R10: ffffffffffffffe0 R11: 0000000000000246 R12: 0000000000000001
[428617.156505] R13: 0000000000000000 R14: 00000000531c4de8 R15: 000000005667b850
[..]
[428617.158050] Kernel panic - not syncing: hung_task: blocked tasks
[428617.158052] CPU: 7 PID: 126 Comm: khungtaskd Kdump: loaded Not tainted 4.18.0-425.10.1.el8_7.x86_64 #1
[428617.158053] Hardware name: VMware, Inc. VMware7,1/440BX Desktop Reference Platform, BIOS VMW71.00V.18227214.B64.2106252220 06/25/2021
[428617.158054] Call Trace:
[428617.158055]  dump_stack+0x41/0x60
[428617.158058]  panic+0xe7/0x2ac
[428617.158062]  watchdog+0x242/0x2f0
[428617.158065]  ? hungtask_pm_notify+0x50/0x50
[428617.158068]  kthread+0x10b/0x130
[428617.158070]  ? set_kthread_struct+0x50/0x50
[428617.158073]  ret_from_fork+0x1f/0x40

Resolution

  • This issue is currently being tracked in Red Hat private Bug ID #2179063
    [Private] Bug 2179063 - Database process hanging due to deadlock on ext4 filesystem mounted without a journal.

Root Cause

  • The symptoms are indicative of a known issue that has been addressed by upstream commit: 3eaf9cc62f447
$ git show 3eaf9cc62f447
commit 3eaf9cc62f447a742b26fa601993e94406aa1ea1
Author: Matthew Bobrowski <mbobrowski@mbobrowski.org>
Date:   Tue Nov 5 23:02:23 2019 +1100

    ext4: update ext4_sync_file() to not use __generic_file_fsync()

    When the filesystem is created without a journal, we eventually call
    into __generic_file_fsync() in order to write out all the modified
    in-core data to the permanent storage device. This function happens to
    try and obtain an inode_lock() while synchronizing the files buffer
    and it's associated metadata.

    Generally, this is fine, however it becomes a problem when there is
    higher level code that has already obtained an inode_lock() as this
    leads to a recursive lock situation. This case is especially true when
    porting across direct I/O to iomap infrastructure as we obtain an
    inode_lock() early on in the I/O within ext4_dio_write_iter() and hold
    it until the I/O has been completed. Consequently, to not run into
    this specific issue, we move away from calling into
    __generic_file_fsync() and perform the necessary synchronization tasks
    within ext4_sync_file().

    Signed-off-by: Matthew Bobrowski <mbobrowski@mbobrowski.org>
    Reviewed-by: Ritesh Harjani <riteshh@linux.ibm.com>
    Reviewed-by: Jan Kara <jack@suse.cz>
    Link: https://lore.kernel.org/r/3495f35ef67f2021b567e28e6f59222e583689b8.1572949325.git.mbobrowski@mbobrowski.org
    Signed-off-by: Theodore Ts'o <tytso@mit.edu>

diff --git a/fs/ext4/fsync.c b/fs/ext4/fsync.c
index 5508baa11bb6..e10206e7f4bb 100644
--- a/fs/ext4/fsync.c
+++ b/fs/ext4/fsync.c
@@ -80,6 +80,43 @@ static int ext4_sync_parent(struct inode *inode)
        return ret;
 }

+static int ext4_fsync_nojournal(struct inode *inode, bool datasync,
+                               bool *needs_barrier)
+{
+       int ret, err;
+
+       ret = sync_mapping_buffers(inode->i_mapping);
+       if (!(inode->i_state & I_DIRTY_ALL))
+               return ret;
+       if (datasync && !(inode->i_state & I_DIRTY_DATASYNC))
+               return ret;
+
+       err = sync_inode_metadata(inode, 1);
+       if (!ret)
+               ret = err;
+
+       if (!ret)
+               ret = ext4_sync_parent(inode);
+       if (test_opt(inode->i_sb, BARRIER))
+               *needs_barrier = true;
+
+       return ret;
+}
+
+static int ext4_fsync_journal(struct inode *inode, bool datasync,
+                            bool *needs_barrier)
+{
+       struct ext4_inode_info *ei = EXT4_I(inode);
+       journal_t *journal = EXT4_SB(inode->i_sb)->s_journal;
+       tid_t commit_tid = datasync ? ei->i_datasync_tid : ei->i_sync_tid;
+
+       if (journal->j_flags & JBD2_BARRIER &&
+           !jbd2_trans_will_send_data_barrier(journal, commit_tid))
+               *needs_barrier = true;
+
+       return jbd2_complete_transaction(journal, commit_tid);
+}
+
 /*
  * akpm: A new design for ext4_sync_file().
  *
@@ -91,17 +128,14 @@ static int ext4_sync_parent(struct inode *inode)
  * What we do is just kick off a commit and wait on it.  This will snapshot the
  * inode to disk.
  */
-
 int ext4_sync_file(struct file *file, loff_t start, loff_t end, int datasync)
 {
-       struct inode *inode = file->f_mapping->host;
-       struct ext4_inode_info *ei = EXT4_I(inode);
-       journal_t *journal = EXT4_SB(inode->i_sb)->s_journal;
        int ret = 0, err;
-       tid_t commit_tid;
        bool needs_barrier = false;
+       struct inode *inode = file->f_mapping->host;
+       struct ext4_sb_info *sbi = EXT4_SB(inode->i_sb);

-       if (unlikely(ext4_forced_shutdown(EXT4_SB(inode->i_sb))))
+       if (unlikely(ext4_forced_shutdown(sbi)))
                return -EIO;

        J_ASSERT(ext4_journal_current_handle() == NULL);
@@ -111,23 +145,15 @@ int ext4_sync_file(struct file *file, loff_t start, loff_t end, int datasync)
        if (sb_rdonly(inode->i_sb)) {
                /* Make sure that we read updated s_mount_flags value */
                smp_rmb();
-               if (EXT4_SB(inode->i_sb)->s_mount_flags & EXT4_MF_FS_ABORTED)
+               if (sbi->s_mount_flags & EXT4_MF_FS_ABORTED)
                        ret = -EROFS;
                goto out;
        }

-       if (!journal) {
-               ret = __generic_file_fsync(file, start, end, datasync);
-               if (!ret)
-                       ret = ext4_sync_parent(inode);
-               if (test_opt(inode->i_sb, BARRIER))
-                       goto issue_flush;
-               goto out;
-       }
-
        ret = file_write_and_wait_range(file, start, end);
        if (ret)
                return ret;
+
        /*
         * data=writeback,ordered:
         *  The caller's filemap_fdatawrite()/wait will sync the data.
@@ -142,18 +168,14 @@ int ext4_sync_file(struct file *file, loff_t start, loff_t end, int datasync)
         *  (they were dirtied by commit).  But that's OK - the blocks are
         *  safe in-journal, which is all fsync() needs to ensure.
         */
-       if (ext4_should_journal_data(inode)) {
+       if (!sbi->s_journal)
+               ret = ext4_fsync_nojournal(inode, datasync, &needs_barrier);
+       else if (ext4_should_journal_data(inode))
                ret = ext4_force_commit(inode->i_sb);
-               goto out;
-       }
+       else
+               ret = ext4_fsync_journal(inode, datasync, &needs_barrier);

-       commit_tid = datasync ? ei->i_datasync_tid : ei->i_sync_tid;
-       if (journal->j_flags & JBD2_BARRIER &&
-           !jbd2_trans_will_send_data_barrier(journal, commit_tid))
-               needs_barrier = true;
-       ret = jbd2_complete_transaction(journal, commit_tid);
        if (needs_barrier) {
-       issue_flush:
                err = blkdev_issue_flush(inode->i_sb->s_bdev, GFP_KERNEL, NULL);
                if (!ret)
                        ret = err;
</tytso@mit.edu></jack@suse.cz></riteshh@linux.ibm.com></mbobrowski@mbobrowski.org></mbobrowski@mbobrowski.org>

Diagnostic Steps

  • A crash dump (VMcore) captured by enabling sysctl hung_task_panic indicates that there is a deadlock between the oninit task PID 4327 waiting for ext4_inode_info->i_unwritten to reach 0 and a rescuer worker thread dio/dm-8 PID 4585 waiting for the rw_semaphore lock held by the "oninit" task PID 4327 waiting for "ext4_inode_info->i_unwritten" to reach 0.

Kernel Crash Dump Analysis:

  • System Information:
crash> sys | grep -e RELEASE -e PANIC
     RELEASE: 4.18.0-425.10.1.el8_7.x86_64
       PANIC: "Kernel panic - not syncing: hung_task: blocked tasks"

crash> sys -i | grep DMI_SYS_VENDOR: -A 1
         DMI_SYS_VENDOR: VMware, Inc.
       DMI_PRODUCT_NAME: VMware7,1
  • Kernel Ring Buffer:
crash> log
[..]
[428617.156430] INFO: task oninit:4327 blocked for more than 120 seconds.
[428617.156431]       Not tainted 4.18.0-425.10.1.el8_7.x86_64 #1
[428617.156433] "echo 0 > /proc/sys/kernel/hung_task_timeout_secs" disables this message.
[428617.156433] task:oninit          state:D stack:    0 pid: 4327 ppid:  4300 flags:0x00004084
[428617.156436] Call Trace:
[428617.156437]  __schedule+0x2d1/0x860
[428617.156439]  ? blk_flush_plug_list+0xd7/0x100
[428617.156441]  schedule+0x35/0xa0
[428617.156443]  ext4_unwritten_wait+0x87/0xc0 [ext4]
[428617.156460]  ? finish_wait+0x80/0x80
[428617.156462]  ext4_file_write_iter+0x396/0x3e0 [ext4]
[428617.156478]  aio_write+0xf6/0x1c0
[428617.156480]  ? blk_mq_sched_insert_requests+0x6c/0xf0
[428617.156483]  ? io_submit_one+0x7e/0x3c0
[428617.156484]  ? kmem_cache_alloc+0x13f/0x280
[428617.156486]  io_submit_one+0x131/0x3c0
[428617.156488]  __x64_sys_io_submit+0xa2/0x180
[428617.156489]  ? syscall_trace_enter+0x1ff/0x2d0
[428617.156492]  do_syscall_64+0x5b/0x1b0
[428617.156494]  entry_SYSCALL_64_after_hwframe+0x61/0xc6
[428617.156497] RIP: 0033:0x7fd1d50409bd
[428617.156499] Code: Unable to access opcode bytes at RIP 0x7fd1d5040993.
[428617.156500] RSP: 002b:00000000531c4da8 EFLAGS: 00000246 ORIG_RAX: 00000000000000d1
[428617.156501] RAX: ffffffffffffffda RBX: 00007fd1d698ab78 RCX: 00007fd1d50409bd
[428617.156502] RDX: 00000000531c4de8 RSI: 0000000000000001 RDI: 00007fd1c4593000
[428617.156503] RBP: 00007fd1c4593000 R08: 00000001358df800 R09: 000000005667b8d8
[428617.156504] R10: ffffffffffffffe0 R11: 0000000000000246 R12: 0000000000000001
[428617.156505] R13: 0000000000000000 R14: 00000000531c4de8 R15: 000000005667b850
[..]
[428617.158050] Kernel panic - not syncing: hung_task: blocked tasks
[428617.158052] CPU: 7 PID: 126 Comm: khungtaskd Kdump: loaded Not tainted 4.18.0-425.10.1.el8_7.x86_64 #1
[428617.158053] Hardware name: VMware, Inc. VMware7,1/440BX Desktop Reference Platform, BIOS VMW71.00V.18227214.B64.2106252220 06/25/2021
[428617.158054] Call Trace:
[428617.158055]  dump_stack+0x41/0x60
[428617.158058]  panic+0xe7/0x2ac
[428617.158062]  watchdog+0x242/0x2f0
[428617.158065]  ? hungtask_pm_notify+0x50/0x50
[428617.158068]  kthread+0x10b/0x130
[428617.158070]  ? set_kthread_struct+0x50/0x50
[428617.158073]  ret_from_fork+0x1f/0x40
[..]
  • The oninit task PID: 4327 has been in the uninterruptible (UN) state for 2 minutes and 35 seconds.
crash> set 4327
    PID: 4327
COMMAND: "oninit"
   TASK: ffff9fc84c518000  [THREAD_INFO: ffff9fc84c518000]
    CPU: 9
  STATE: TASK_UNINTERRUPTIBLE 

crash> px ((struct file *)((struct task_struct *)0xffff9fc84c518000)->mm->exe_file)->f_path.dentry
$4 = (struct dentry *) 0xffff9fc8fc257e00

crash> files -d 0xffff9fc8fc257e00
     DENTRY           INODE           SUPERBLK     TYPE PATH
ffff9fc8fc257e00 ffff9fc8fc178120 ffff9fc85014f000 REG  /users4/ifxdb_14.10.FC8_WE/bin/oninit

crash> ps -m 4327
[0 00:02:35.486] [UN]  PID: 4327     TASK: ffff9fc84c518000  CPU: 9    COMMAND: "oninit"
  • Backtrace of the oninit task PID: 4327
crash> bt
PID: 4327     TASK: ffff9fc84c518000  CPU: 9    COMMAND: "oninit"
 #0 [ffffb2fd13a77bf0] __schedule at ffffffffbb9eb121
 #1 [ffffb2fd13a77c80] schedule at ffffffffbb9eb6e5
 #2 [ffffb2fd13a77c90] ext4_unwritten_wait at ffffffffc08b7317 [ext4]
 #3 [ffffb2fd13a77cd8] ext4_file_write_iter at ffffffffc08b7b46 [ext4]
 #4 [ffffb2fd13a77d38] aio_write at ffffffffbb3b9e16
 #5 [ffffb2fd13a77e40] io_submit_one at ffffffffbb3ba1a1
 #6 [ffffb2fd13a77eb8] __x64_sys_io_submit at ffffffffbb3ba7b2
 #7 [ffffb2fd13a77f38] do_syscall_64 at ffffffffbb0043ab
 #8 [ffffb2fd13a77f50] entry_SYSCALL_64_after_hwframe at ffffffffbba000a9
    RIP: 00007fd1d50409bd  RSP: 00000000531c4da8  RFLAGS: 00000246
    RAX: ffffffffffffffda  RBX: 00007fd1d698ab78  RCX: 00007fd1d50409bd
    RDX: 00000000531c4de8  RSI: 0000000000000001  RDI: 00007fd1c4593000
    RBP: 00007fd1c4593000   R8: 00000001358df800   R9: 000000005667b8d8
    R10: ffffffffffffffe0  R11: 0000000000000246  R12: 0000000000000001
    R13: 0000000000000000  R14: 00000000531c4de8  R15: 000000005667b850
    ORIG_RAX: 00000000000000d1  CS: 0033  SS: 002b
  • The oninit task PID: 4327 was waiting for an ext4_inode_info->i_unwritten to reach 0 for an ext4 inode:
crash> dis -rl ffffffffc08b7317 | tail -n 5
/usr/src/debug/kernel-4.18.0-425.10.1.el8_7/linux-4.18.0-425.10.1.el8_7.x86_64/fs/ext4/file.c: 111
0xffffffffc08b730b <ext4_unwritten_wait+123>:   call   0xffffffffbb1447b0 <init_wait_entry>
0xffffffffc08b7310 <ext4_unwritten_wait+128>:   jmp    0xffffffffc08b7317 <ext4_unwritten_wait+135>
0xffffffffc08b7312 <ext4_unwritten_wait+130>:   call   0xffffffffbb9eb6b0 <schedule>
0xffffffffc08b7317 <ext4_unwritten_wait+135>:   mov    $0x2,%edx

Kernel Source: fs/ext4/file.c
*****************************
218 static ssize_t
219 ext4_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
220 {
221         struct inode *inode = file_inode(iocb->ki_filp);
222         int o_direct = iocb->ki_flags & IOCB_DIRECT;
223         int unaligned_aio = 0;
224         int overwrite = 0;
225         ssize_t ret;
...
273         /*
274          * Unaligned direct AIO must be the only IO in flight. Otherwise
275          * overlapping aligned IO after unaligned might result in data
276          * corruption.
277          */
278         if (ret == -EIOCBQUEUED && unaligned_aio)
279                 ext4_unwritten_wait(inode);
...

Kernel Source: fs/ext4/file.c
*****************************
107 static void ext4_unwritten_wait(struct inode *inode)
108 {
109         wait_queue_head_t *wq = ext4_ioend_wq(inode);
110 
111         wait_event(*wq, (atomic_read(&EXT4_I(inode)->i_unwritten) == 0));

crash> px ext4_unwritten_wait
ext4_unwritten_wait = $2 = 
 {void (struct inode *)} 0xffffffffc08b7290 <ext4_unwritten_wait>

crash> dis -r ffffffffc08b7b46 | tail -n 3
0xffffffffc08b7b3e <ext4_file_write_iter+910>:  mov    %rbp,%rdi
0xffffffffc08b7b41 <ext4_file_write_iter+913>:  call   0xffffffffc08b7290 <ext4_unwritten_wait>
0xffffffffc08b7b46 <ext4_file_write_iter+918>:  mov    %r14,%rdi

crash> bt -f | grep ext4_file_write_iter
 #3 [ffffb2fd13a77cd8] ext4_file_write_iter at ffffffffc08b7b46 [ext4]

crash> px (0xffffb2fd13a77cd8-0x8)
$5 = 0xffffb2fd13a77cd0

crash> rd 0xffffb2fd13a77cd0
ffffb2fd13a77cd0:  ffff9fca42897788                    .w.B....

o struct inode * == 0xffff9fca42897788

crash> struct ext4_inode_info.vfs_inode  -ox
struct ext4_inode_info {
   [0xe8] struct inode vfs_inode;
}

crash> px (0xffff9fca42897788-0xe8)
$6 = 0xffff9fca428976a0

o struct ext4_inode_info * == 0xffff9fca428976a0

crash> struct ext4_inode_info.i_unwritten 0xffff9fca428976a0
  i_unwritten = {
    counter = 1
  },

crash> struct inode.i_dentry 0xffff9fca42897788
    i_dentry = {
      first = 0xffff9fc85a8cc8f0
    },

crash> px (0xffff9fc85a8cc8f0-0xb0)
$7 = 0xffff9fc85a8cc840

o struct dentry * == 0xffff9fc85a8cc840

crash> files -d 0xffff9fc85a8cc840
     DENTRY           INODE           SUPERBLK     TYPE PATH
ffff9fc85a8cc840 ffff9fca42897788 ffff9fc84bf55800 REG  /informix/data/data3dbs_1
  • The ext4 file system mounted without journalling:
crash> mount | grep -e NAME -e ffff9fc84bf55800
     MOUNT           SUPERBLK     TYPE   DEVNAME   DIRNAME
ffff9fc87136d080 ffff9fc84bf55800 ext4   /dev/mapper/rhel-informix /informix  <<< - - -

crash> px ((struct ext4_sb_info *)((struct super_block *)0xffff9fc84bf55800)->s_fs_info)->s_journal
$8 = (struct journal_s *) 0x0

crash> struct inode.i_sb 0xffff9fca42897788
  i_sb = 0xffff9fc84bf55800,

crash> struct super_block.s_bdev 0xffff9fc84bf55800
  s_bdev = 0xffff9fc880c32d00,

crash> struct block_device.bd_disk 0xffff9fc880c32d00
  bd_disk = 0xffff9fc8706a9000,

crash> struct gendisk.disk_name 0xffff9fc8706a9000
  disk_name = "dm-8\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000",

crash> dmshow --lvs | grep -e NAME -e dm-8
LV DM-X DEV   LV NAME                                   VG NAME                                  OPEN COUNT        LV SIZE (MiB)     PV NAME
dm-8          informix                                  rhel                                              1            512000.00     sda3

crash> dev -d | grep -e NAME -e dm-8
MAJOR GENDISK            NAME       REQUEST_QUEUE      TOTAL ASYNC  SYNC 
  253 ffff9fc8706a9000   dm-8       ffff9fc848c21fb0       0     0     0 
  • Let's verify the rescuer worker thread dio/dm-8 PID: 4585
crash> set 4585
    PID: 4585
COMMAND: "dio/dm-8"
   TASK: ffff9fca0d9ea800  [THREAD_INFO: ffff9fca0d9ea800]
    CPU: 4
  STATE: TASK_UNINTERRUPTIBLE 
  • It has been in the uninterruptible (UN) state for 2 minutes and 35 seconds.
crash> ps -m 4585
[0 00:02:35.436] [UN]  PID: 4585     TASK: ffff9fca0d9ea800  CPU: 4    COMMAND: "dio/dm-8"
  • Backtrace of the dio/dm-8 thread PID: 4585
crash> bt
PID: 4585     TASK: ffff9fca0d9ea800  CPU: 4    COMMAND: "dio/dm-8"
 #0 [ffffb2fd13bd7c98] __schedule at ffffffffbb9eb121
 #1 [ffffb2fd13bd7d28] schedule at ffffffffbb9eb6e5
 #2 [ffffb2fd13bd7d38] rwsem_down_write_slowpath at ffffffffbb15503c
 #3 [ffffb2fd13bd7dc0] __generic_file_fsync at ffffffffbb389f92
 #4 [ffffb2fd13bd7df0] ext4_sync_file at ffffffffc08b9310 [ext4]
 #5 [ffffb2fd13bd7e30] dio_complete at ffffffffbb3a5353
 #6 [ffffb2fd13bd7e68] process_one_work at ffffffffbb110767
 #7 [ffffb2fd13bd7ea8] rescuer_thread at ffffffffbb110b1b
 #8 [ffffb2fd13bd7f10] kthread at ffffffffbb117f8b
 #9 [ffffb2fd13bd7f50] ret_from_fork at ffffffffbba0024f
  • It was waiting for the rw_semaphore (inode->i_rwsem) lock.
crash> dis -rl ffffffffbb389f92 | tail -n 6
/usr/src/debug/kernel-4.18.0-425.10.1.el8_7/linux-4.18.0-425.10.1.el8_7.x86_64/./include/linux/fs.h: 787
0xffffffffbb389f83 <__generic_file_fsync+51>:   lea    0xa8(%rbp),%r14
0xffffffffbb389f8a <__generic_file_fsync+58>:   mov    %r14,%rdi
0xffffffffbb389f8d <__generic_file_fsync+61>:   call   0xffffffffbb9edd50 <down_write>
/usr/src/debug/kernel-4.18.0-425.10.1.el8_7/linux-4.18.0-425.10.1.el8_7.x86_64/fs/libfs.c: 1004
0xffffffffbb389f92 <__generic_file_fsync+66>:   mov    0x30(%rbp),%rdi

Kernel Source: fs/libfs.c
*************************
 992 int __generic_file_fsync(struct file *file, loff_t start, loff_t end,
 993                                  int datasync)
 994 {
 995         struct inode *inode = file->f_mapping->host;
 996         int err;
 997         int ret;
 998 
 999         err = file_write_and_wait_range(file, start, end);
1000         if (err)
1001                 return err;
1002 
1003         inode_lock(inode);

Kernel Source: include/linux/fs.h
*********************************
785 static inline void inode_lock(struct inode *inode)
786 {
787         down_write(&inode->i_rwsem);

crash> dis -r ffffffffc08b9310 | tail -n 3
0xffffffffc08b9308 <ext4_sync_file+456>:    mov    %rbx,%rdi
0xffffffffc08b930b <ext4_sync_file+459>:    call   0xffffffffbb389f50 <__generic_file_fsync>
0xffffffffc08b9310 <ext4_sync_file+464>:    mov    %eax,%r13d

crash> dis -r ffffffffbb389f92 | head -n 7
0xffffffffbb389f50 <__generic_file_fsync>:  nopl   0x0(%rax,%rax,1) [FTRACE NOP]
0xffffffffbb389f55 <__generic_file_fsync+5>:    push   %r14
0xffffffffbb389f57 <__generic_file_fsync+7>:    push   %r13
0xffffffffbb389f59 <__generic_file_fsync+9>:    push   %r12
0xffffffffbb389f5b <__generic_file_fsync+11>:   mov    %ecx,%r12d
0xffffffffbb389f5e <__generic_file_fsync+14>:   push   %rbp
0xffffffffbb389f5f <__generic_file_fsync+15>:   push   %rbx

crash> bt -f | grep __generic_file_fsync -A 4
 #3 [ffffb2fd13bd7dc0] __generic_file_fsync at ffffffffbb389f92
    ffffb2fd13bd7dc8: ffff9fc90b9dde00 ffff9fca42897788 
    ffffb2fd13bd7dd8: 0000000000000001 0000000000000800 
    ffffb2fd13bd7de8: 000000008b6517ff ffffffffc08b9310 
 #4 [ffffb2fd13bd7df0] ext4_sync_file at ffffffffc08b9310 [ext4]

o struct file * == 0xffff9fc90b9dde00

crash> px ((struct file *)0xffff9fc90b9dde00)->f_mapping.host
$9 = (struct inode *) 0xffff9fca42897788

o struct inode * == 0xffff9fca42897788

crash> struct inode.i_dentry 0xffff9fca42897788
    i_dentry = {
      first = 0xffff9fc85a8cc8f0
    },

crash> px (0xffff9fc85a8cc8f0-0xb0)
$10 = 0xffff9fc85a8cc840

o struct dentry * == 0xffff9fc85a8cc840

crash> files -d 0xffff9fc85a8cc840
     DENTRY           INODE           SUPERBLK     TYPE PATH
ffff9fc85a8cc840 ffff9fca42897788 ffff9fc84bf55800 REG  /informix/data/data3dbs_1

crash> struct inode.i_rwsem 0xffff9fca42897788 -ox
struct inode {
  [ffff9fca42897830] struct rw_semaphore i_rwsem;
}

o struct rw_semaphore * == 0xffff9fca42897830

crash> struct rw_semaphore.wait_list 0xffff9fca42897830 -ox
struct rw_semaphore {
  [ffff9fca42897838] struct list_head wait_list;
}
  • The waitlist of the rw_semaphore has 6 tasks.
crash> list -H 0xffff9fca42897838
ffffb2fd066abd90
ffffb2fd065dfd90
ffffb2fd194efd90
ffffb2fd17a47d90
ffffb2fd1b2dfd90
ffffb2fd13bd7d60
  • List of the task waiting for the rw_semaphore:
crash> list -H 0xffff9fca42897838 -s rwsem_waiter.task | grep task | tr -d , | awk '{print $3}' > rw_task

crash> ps -m < rw_task
[0 00:02:35.467] [UN]  PID: 3613198  TASK: ffff9fdee0a50000  CPU: 4    COMMAND: "kworker/4:5"
[0 00:02:35.467] [UN]  PID: 3613196  TASK: ffff9fe291838000  CPU: 4    COMMAND: "kworker/4:3"
[0 00:02:35.467] [UN]  PID: 3606261  TASK: ffff9fd99a8aa800  CPU: 4    COMMAND: "kworker/4:7"
[0 00:02:35.467] [UN]  PID: 3609395  TASK: ffff9fe68de85000  CPU: 4    COMMAND: "kworker/4:1"
[0 00:02:35.467] [UN]  PID: 3612070  TASK: ffff9fd2ae145000  CPU: 4    COMMAND: "kworker/4:0"
[0 00:02:35.436] [UN]  PID: 4585     TASK: ffff9fca0d9ea800  CPU: 4    COMMAND: "dio/dm-8"

crash> list -H 0xffff9fca42897838 -o rwsem_waiter.list -s rwsem_waiter.type | grep type | awk '{print $3}' | sort | uniq -c
      6 RWSEM_WAITING_FOR_WRITE,
  • The rw_semaphore (inode->i_rwsem) was held by the oninit task PID: 4327
crash> px ((struct rw_semaphore *)0xffff9fca42897830)->owner
$11 = {
  counter = 0xffff9fc84c518000
}

o struct task_struct * == 0xffff9fc84c518000

crash> task_struct.pid 0xffff9fc84c518000
  pid = 4327,

crash> ps 4327
      PID    PPID  CPU       TASK        ST  %MEM      VSZ      RSS  COMM
     4327    4300   9  ffff9fc84c518000  UN   0.0 29594148    25704  oninit

crash> set 4327
    PID: 4327
COMMAND: "oninit"
   TASK: ffff9fc84c518000  [THREAD_INFO: ffff9fc84c518000]
    CPU: 9
  STATE: TASK_UNINTERRUPTIBLE 
  • It has acquired the rw_semaphore in the ext4_file_write_iter() function.
crash> bt
PID: 4327     TASK: ffff9fc84c518000  CPU: 9    COMMAND: "oninit"
 #0 [ffffb2fd13a77bf0] __schedule at ffffffffbb9eb121
 #1 [ffffb2fd13a77c80] schedule at ffffffffbb9eb6e5
 #2 [ffffb2fd13a77c90] ext4_unwritten_wait at ffffffffc08b7317 [ext4]
 #3 [ffffb2fd13a77cd8] ext4_file_write_iter at ffffffffc08b7b46 [ext4]
 #4 [ffffb2fd13a77d38] aio_write at ffffffffbb3b9e16
 #5 [ffffb2fd13a77e40] io_submit_one at ffffffffbb3ba1a1
 #6 [ffffb2fd13a77eb8] __x64_sys_io_submit at ffffffffbb3ba7b2
 #7 [ffffb2fd13a77f38] do_syscall_64 at ffffffffbb0043ab
 #8 [ffffb2fd13a77f50] entry_SYSCALL_64_after_hwframe at ffffffffbba000a9
    RIP: 00007fd1d50409bd  RSP: 00000000531c4da8  RFLAGS: 00000246
    RAX: ffffffffffffffda  RBX: 00007fd1d698ab78  RCX: 00007fd1d50409bd
    RDX: 00000000531c4de8  RSI: 0000000000000001  RDI: 00007fd1c4593000
    RBP: 00007fd1c4593000   R8: 00000001358df800   R9: 000000005667b8d8
    R10: ffffffffffffffe0  R11: 0000000000000246  R12: 0000000000000001
    R13: 0000000000000000  R14: 00000000531c4de8  R15: 000000005667b850
    ORIG_RAX: 00000000000000d1  CS: 0033  SS: 002b
  • The oninit task PID: 4327 and dio/dm-8 thread PID: 4585 were deadlocked.
 

https://access.redhat.com/solutions/7006119

 

 

728x90
728x90

APAR status

  • OPEN

Error description

  • KAIO not supported with sector_sizes greater than 1kB
    

 

https://www.ibm.com/support/pages/apar/IT43456

728x90
728x90

APAR status

  • Closed as program error.

Error description

  • The listener thread may receive an OS error when trying to bind
    the port with an IPv6 address. This results in an AF similar to:
    
    Assert Warning: Unable to bind to the port (18105) on the host
    () for the server (server1).
    
    Stack for thread: 11 soctcplst
    
    (oninit) afstack
    (oninit) afhandler
    (oninit) afwarn_interface
    (oninit) listen_af_alarm
    (oninit) lisnsocket
    (oninit) tlListen
    (oninit) slSQIlisten
    (oninit) pfListen
    (oninit) cmListen
    (oninit) alListen
    (oninit) ASF_Call
    (oninit) sql_listener
    
    This may occur when using a specific IPv6 address other than the
    loopback address ::1.
    
    This problem may occur on releases of Linux where the option
    "myhostname" is permitted for the hosts entry in
    /etc/nsswitch.conf. The presence of this entry avoids the need
    to enter the host's specific IP address into /etc/hosts and will
    result in an IPv6 address being used by the listener thread if
    IPv6 has been configured for at least one network interface. Red
    Hat 7 is one such OS version where this option is available.
    

Local fix

  • The workaround is to allow the bind operation to use any of the
    available IPv6 addresses on the server by prefixing the hostname
    or address in column 3 of the sqlhosts file with an asterix "*".
    Alternatively if IPv6 is not required it may be disabled by
    setting the environment variable IFX_DISABLE_IPV6 or creating
    the empty file $INFORMIXDIR/etc/IFX_DISABLE_IPV6.
    

Problem summary

  • ****************************************************************
    * USERS AFFECTED:                                              *
    * informix user with IPv6 enabled                              *
    ****************************************************************
    * PROBLEM DESCRIPTION:                                         *
    * See Error Description                                        *
    ****************************************************************
    * RECOMMENDATION:                                              *
    * Update to IBM Informix Server 12.10.xC6                      *
    ****************************************************************
    

Problem conclusion

  • Problem Fixed In IBM Informix Server 12.10.xC6
    

Temporary fix

  •  

Comments

  •  

APAR Information

  • APAR number
  • IT11530
  • Reported component name
  • INFORMIX SERVER
  • Reported component ID
  • 5725A3900
  • Reported release
  • C10
  • Status
  • CLOSED PER
  • PE
  • NoPE
  • HIPER
  • NoHIPER
  • Special Attention
  • NoSpecatt
  • Submitted date
  • 2015-10-01
  • Closed date
  • 2015-12-30
  • Last modified date
  • 2015-12-30
728x90
728x90

Question

Your read cache rate (onstat -p) rises durning server use to ninety-nine percent. You suspect that you have overallocated buffers to the extent that you are wasting memory. How can you determine how much of your buffer cache memory is used?

Answer

Count the number of buffers in use at regular intervals and compare with the total allocated. You can use the following two methods to count the number of buffer pages in use at a given point in time:

  • Execute the onstat -B command. The number of data lines in the output is the number of buffers in use. The onstat -B output includes all the configured buffer pools, grouped by page size. You will have to count the lines in the individual groups if you have more than one buffer pool.


  • Open the sysmaster database and execute the following query:

    SELECT COUNT(*) AS pages_used
    FROM sysbufhdr
    WHERE bufsize = <buffer_page_size>
    AND bflags != 4;

    The buffer_page_size is the page size (in bytes) for the buffer pool you are examining. The buffer page size is listed in the onstat -b/B output. If you only have one buffer pool, you can leave out the buffer page size filter and the query becomes:

    SELECT COUNT(*) AS pages_used
    FROM sysbufhdr
    WHERE bflags != 4;

 

https://www.ibm.com/support/pages/how-do-i-know-if-my-buffers-are-overallocated

 

How do I know if my Buffers are Overallocated?

How do I know if my Buffers are Overallocated?

www.ibm.com

 

728x90
728x90

Troubleshooting


Problem

This article describes a problem which occurs when CURRENT does not recalculate the actual date and time when called more than once in a stored procedure.

Resolving The Problem


PROBLEM

Repeated calls to the CURRENT function within a stored procedure always returns the same value.

This problem is typically seen when the stored procedure uses a cursor that loops through large quantities of data, such that by the time the final row is reached, the actual time has moved on and CURRENT no longer hold the true system datetime.


CAUSE

If using CURRENT in a stored procedure, the datetime value is set to the current system datetime at the time the procedure is called. CURRENT is not updated while the stored procedure is running.


SOLUTION

The system table sysmaster:sysshmvals.sh_curtime stores the exact system datetime. Run the following query within your stored procedure to return the actual time and store it into the local variable current_time.

select DBINFO('utc_to_datetime', sh_curtime) into current_time
from sysmaster:sysshmvals;

The following example demonstrates the different results returned when comparing the use of the CURRENT function with that of the sysshmvals query shown above.


Example:

create database currdemo;

create table dates (text char(20),
                    time datetime year to second);

create procedure test()
define i int;
define current_time datetime year to second;


insert into dates values ("current 1st run", CURRENT);

select DBINFO('utc_to_datetime', sh_curtime) into current_time
from sysmaster:sysshmvals;
insert into dates values ("realtime 1st run", current_time);

system "sleep 5" ;

insert into dates values ("current 2nd run", CURRENT);

select DBINFO('utc_to_datetime', sh_curtime) into current_time
from sysmaster:sysshmvals;
insert into dates values ("realtime 2nd run", current_time);

end procedure;

execute procedure test();

select * from dates order by text;


The output looks as follows:

text                 time

current 1st run      2006-10-31 17:02:11
current 2nd run      2006-10-31 17:02:11
realtime 1st run     2006-10-31 17:02:11
realtime 2nd run     2006-10-31 17:02:16

 

출처 : https://www.ibm.com/support/pages/how-obtain-actual-date-and-time-stored-procedure

728x90
728x90

안녕하세요. 이번 글에서는 인포믹스 테이블 복구 기능에 대해서 소개드리고자 합니다.

데이터베이스의 물리적인 오류나 사용자의 실수로 데이터가 손실되었을 때 테이블 복구 기능을 사용합니다.

 

오라클의 FLASHBACK과 유사한 기능으로 볼수도 있습니다. FLASHBACK은 언두영역의 과거 데이터를 복구하는 반면, 인포믹스의 테이블 복구기능은 백업본을 스캔하여 특정 테이블의 데이터를 추출하여 복구합니다.

 

Informix의 테이블 복구기능의 장점은 아래와 같습니다.

1. ontape 또는 onbar로 백업받은 이미지가 존재할 경우, 백업된 시점의 데이터를 복구할 수 있다.

2. 백업 전체를 리스토어 할 필요없이 특정 테이블만 추출하여 복구가 가능하다. 따라서 복구시 테이블 크기만큼의 여유공간만 필요하므로 효율적이다.

3. 기존 테이블과 다른 이름의 테이블, 또는 CSV파일 형식으로 지정하여 복구할 수 있다.

4. onbar의 경우 log restore가 가능하다. 즉, 특정 시점을 지정하여 복구 가능하다.

 

인포믹스 테이블 복구에는 archecker라는 유틸리티를 사용합니다.

복구를 수행하려면 대상 테이블의 CREATE TABLE 문장이 포함된 스키마 커맨드 파일이 필요합니다.

테이블 복구 명령 개요

 

아래는 스키마 커맨드 파일의 예시입니다. 텍스트 형식의 파일입니다.

특정 테이블의 데이터를 다른 테이블 명으로 복구하는 시나리오
특정 테이블 데이터를 파일로 복구하는 시나리오

스키마 커맨드 파일이 준비되면 archecker를 실행하여 복구를 수행할 수 있습니다.

archecker 유틸리티 실행 예시

기존 데이터에 영향없이 과거 데이터를 별도 공간에 복구하는 방법이기 때문에 매우 유용한 기능입니다.

대신 스키마 정보가 꼭 필요하기 때문에 수시로 백업이 되어야겠죠.

이 기능은 별도 라이센스가 필요하지 않으므로 인포믹스 무료 버전인 Innovator-C 에디션에서도 사용가능합니다.

728x90
728x90

안녕하세요. DBeaver와 같은 JDBC 연결 프로그램에서 인포믹스의 한글 데이터가 깨져보이는 경우가 있습니다.

이런 현상을 해결하기 위한 방법을 정리해보려고 합니다.

 

우선 대부분의 경우는 인포믹스 데이터베이스 로케일이 영문일 가능성이 높습니다.

아래와 같이 데이터베이스의 로케일을 확인하실 수 있습니다. 영문 로케일은 일반적으로 en_US.819 로 표시됩니다.

데이터베이스에 한글 데이터 INSERT 문장을 실행하면 오류메시지(Code-set conversion function failed due to an illegal sequence or invalid value.)가 발생합니다.

이번에는 터미널에서 한글 데이터를 입력해놓고 조회를 해봅니다. 조회는 오류가 발생하지 않지만 아래처럼 알아볼 수 없는 깨진 글자가 나옵니다.

근본적인 해결방법은 데이터베이스 생성시 한글 코드를 지원하는 데이터베이스에 마이그레이션 하는 것이 좋지만, 실제 사용중인 데이터베이스를 변경하기는 어려우므로, JDBC Driver에서 지원되는 기능을 사용하여 이 현상을 우회할 수 있습니다.

 

DBeaver Connection 설정에서 JDBC Driver 사용자 속성 NEWCODESET을 추가합니다.

해당 속성을 추가해서 JDK 로케일(ksc5601)과 인포믹스 JDBC 드라이버의 내부 테이블(8859-1,819)과 매핑시킬 수 있습니다.

속성을 추가한 후 새로 데이터베이스에 연결하면 한글 데이터 입력과 조회 모두 잘 실행됩니다.

 

NEWCODESET 속성은 인포믹스 서버 10버전 부터 소개되고 있는데, 이에 대응하는 JDBC Driver는 3버전 이상입니다.

DBeaver에서 JDBC Driver 3.00.JC1 버전으로 연결 테스트를 해보니 오류가 나네요. 최소 3.5버전 이상으로 테스트 해보시길 바랍니다.

728x90
728x90

안녕하세요. Informix에는 연도별 주차를 계산하는 기능이 없어서 찾아보다가 다른 코드를 보고 따라서 만들어 보았습니다. 주차에 대한 개념이 없다보니 커뮤니티와 블로그, 위키 자료들을 참고했는데요. 일반적으로 쓰이는 형태가 ISO 8601 표준이라고 합니다.

ISO 8601에 따르면 1주차에 대한 정의는 아래와 같습니다.

  • 시작 연도의 첫 번째 목요일이 포함된 주
  • 1월 4일이 있는 주
  • 시작 연도에 일의 대부분(4일 이상)이 있는 첫 번째 주
  • 12월 29일 - 1월 4일 기간의 월요일로 시작하는 주

아래와 같이 다양한 DBMS에서 함수를 사용하여 특정일자에 대한 주차를 구할 수 있습니다만.. Informix는 자체 기능이 제공되지 않아서 아쉽군요.

DBMS 제공함수 사용예시
Oracle TO_CHAR TO_CHAR(DATE '2021-08-25' , 'IW')
Db2 TO_CHAR TO_CHAR(DATE '2021-08-25' , 'IW')
MySQL WEEK WEEK('2021-08-25', 3);
MariaDB WEEK WEEK('2021-08-25', 3);
SQL Server DATEPART DATEPART(ISOWK,'2021-08-25')
PostgreSQL DATE_PART / EXTRACT DATE_PART('WEEK', '2021-08-25'::DATE)
EXTRACT('WEEK' FROM '2021-08-25'::DATE)
Sybase DATEPART DATEPART(CWK, '8/25/2021')

Informix에는 기본 내장된 함수가 없기 때문에 계산 방식에 대해서 조사를 했습니다.

위에서 상술된 조건을 만족해야하고, Informix에서 datetime 을 일자로 변환하여 계산하는 등의 몇가지 시행착오가 있었습니다.

create function weeknum ( dt varchar(10) )
returning int;
define wknum int;
select trunc(((to_date(dt, '%Y-%m-%d') - yearstart)::interval day(3) to day::varchar(12)::integer) / 7) + 1
into wknum
from
(
select case when nextyr <= to_date(dt, '%Y-%m-%d') then nextyr
            when curryr <= to_date(dt, '%Y-%m-%d') then curryr
            else prioryr 
       end yearstart
from 
(
select  
        mdy(1,1,1753) + trunc(((jan4 - 1 units year - mdy(1,1,1900) + 53690 units day)::varchar(12)::integer) / 7) * 7  units day prioryr,
        mdy(1,1,1753) + trunc(((jan4                - mdy(1,1,1900) + 53690 units day)::varchar(12)::integer) / 7) * 7  units day curryr,
        mdy(1,1,1753) + trunc(((jan4 + 1 units year - mdy(1,1,1900) + 53690 units day)::varchar(12)::integer) / 7) * 7  units day nextyr
from (
select mdy(1,1,1900) + (extend (to_date(dt, '%Y-%m-%d'), year to year) - mdy (1,1,1900))::varchar(12)::integer units year + 3 units day jan4 from dual
)
)
);
return wknum;
end function;

 

아래와 같은 방식으로 Informix에서 특정 일자에 대한 주차를 구할 수 있습니다. 위 함수를 수정하면 쿼리형태로도 사용 가능할 것 같습니다.

> select weeknum ('2021-08-25') from sysmaster:sysdual;
(expression)
          34

 

https://en.wikipedia.org/wiki/ISO_8601

https://en.wikipedia.org/wiki/ISO_week_date

https://www.toolbox.com/tech/oracle/question/how-the-week-number-is-being-derived-121815/

https://www.sqlteam.com/forums/topic.asp?TOPIC_ID=60515 

http://www.whitemiceconsulting.com/informixintervaltips201609

https://www.sqlservercentral.com/articles/a-simple-formula-to-calculate-the-iso-week-number

https://4js.com/online_documentation/fjs-fgl-3.00.05-manual-html/c_fgl_odiagmsv_005.html

 

728x90
728x90

14.10.xC6 버전부터 Round robin 방식으로 분할된 테이블의 개선된 기능을 소개드립니다.

Round robin 분할 방식로 구성된 테이블은 데이터가 지정된 DBspace를 순환하며 입력됩니다.

-- Round robin 분할 테이블에 데이터 입력
> create table test (a int) fragment by round robin in dbs1,dbs2;
Table created.
> insert into test select level from sysmaster:sysdual connect by level <= 5000;
5000 row(s) inserted.
-- 분할된 파티션 별 데이터 건수 확인
$ oncheck -pt demo:test | egrep 'partition|rows'
                  Table fragment partition dbs1 in DBspace dbs1
    Number of rows                 2500
                  Table fragment partition dbs1 in DBspace dbs2  
    Number of rows                 2500

위와 같은 상태에서 새로운 DBspace를 round robin 스키마에 추가하면, 기존 데이터는 이동하지 않고 새로 입력되는 데이터는 기존처럼 DBspace를 순환하며 입력됩니다.

-- Round robin 분할 테이블에 새로운 DBspace 추가
> alter fragment on table test add  dbs3;
Alter fragment completed.
> insert into test select level from sysmaster:sysdual connect by level <= 3;
3 row(s) inserted.
-- 분할된 파티션 별 데이터 건수 확인
$ oncheck -pt demo:test | egrep 'partition|rows'
                  Table fragment partition dbs1 in DBspace dbs1
    Number of rows                 2501
                  Table fragment partition dbs2 in DBspace dbs2
    Number of rows                 2501
                  Table fragment partition dbs3 in DBspace dbs3
    Number of rows                 1

14.10.xC6 버전부터는 Round robin 방식으로 구성된 테이블에 데이터가 입력될 경우, 가장 데이터 건수가 적은 파티션에 우선적으로 입력됩니다.

-- Round robin 분할 테이블에 데이터 입력
> create table test (a int) fragment by round robin in datadbs1,datadbs2;
Table created.
> insert into test select level from sysmaster:sysdual connect by level <= 5000;
5000 row(s) inserted.
-- Round robin 분할 테이블에 새로운 DBspace 추가
> alter fragment on table test add datadbs3;
Alter fragment completed.
> insert into test select level from sysmaster:sysdual connect by level <= 3;
3 row(s) inserted.
-- 분할된 파티션 별 데이터 건수 확인
$ oncheck -pt demo:test | egrep 'partition|rows'
                  Table fragment partition datadbs1 in DBspace datadbs1
    Number of rows                 2500
                  Table fragment partition datadbs2 in DBspace datadbs2
    Number of rows                 2500
                  Table fragment partition datadbs3 in DBspace datadbs3
    Number of rows                 3

 

파티션 별로 데이터량을 균등하게 유지하는 측면에서 좋은 기능인 것 같습니다. UPDATE 문장으로는 데이터가 이동하지 않더군요. 참고하시길 바랍니다.

 

728x90

+ Recent posts