Độ chi tiết của chuỗi thời gian MongoDB

Gần đây, tại MongoDB. live 2021, một trong những thông báo về tính năng lớn hơn là MongoDB phiên bản 5. 0 giới thiệu cái gọi là bộ sưu tập chuỗi thời gian. Thông tin về nó chủ yếu là cấp cao và tài liệu hiện tại cũng không cung cấp một số chi tiết. Đây là lý do tại sao tôi quyết định tìm hiểu sâu hơn một chút để cải thiện hiểu biết cá nhân của mình về những gì đang diễn ra đằng sau hậu trường, khi lưu trữ dữ liệu chuỗi thời gian với loại bộ sưu tập mới này trong MongoDB

Quá khứ

Trong vài năm rồi, mọi người đã sử dụng MongoDB để lưu trữ dữ liệu chuỗi thời gian của họ. Một số người trong số họ gặp khó khăn ban đầu và phải học một cách khó khăn, rằng người ta không chỉ lưu trữ dữ liệu chuỗi thời gian như hiện tại. Sai lầm lớn nhất mà tôi đã thấy nhiều lần trong tự nhiên là dữ liệu chưa được lưu trữ theo cách tối ưu hóa. Điều tôi muốn nói là mọi người đã không đầu tư thêm bất kỳ suy nghĩ nào vào thiết kế lược đồ phù hợp cho tài liệu của họ, mà thay vào đó họ chỉ chèn e. g. phép đo cảm biến thô trực tiếp vào bộ sưu tập. Trong hầu hết các trường hợp, làm như vậy cuối cùng dẫn đến rất nhiều chi phí lưu trữ và xử lý, cấu trúc chỉ mục lớn không cần thiết và đôi khi hiệu suất tổng thể kém. Trước đây, cách giải quyết đúng cách việc lưu trữ dữ liệu chuỗi thời gian với MongoDB là áp dụng thủ thuật thiết kế lược đồ có tên là mẫu nhóm. Ý tưởng chính đằng sau mẫu này là lưu trữ một số phép đo phù hợp với nhau về mặt logic - e. g. dữ liệu từ một cảm biến cụ thể trong một khoảng thời gian nhất định - vào một tài liệu duy nhất chứa một nhóm chứa nhiều phép đo này. Vì việc phát triển vô thời hạn một tài liệu và nhóm của nó là không thực tế, nên lớp ứng dụng sẽ đảm bảo rằng nó bắt đầu một tài liệu mới dựa trên các ngưỡng và quy tắc nhất định, tùy thuộc vào mức độ chi tiết của thời gian và tần suất nhập/khoảng thời gian của dữ liệu cảm biến. Để đưa ra một ví dụ cụ thể, có thể có một tài liệu duy nhất và nhóm của nó, lưu trữ tất cả các phép đo xảy ra mỗi giây trong một giờ cụ thể trong ngày. Sau đó, tài liệu duy nhất này sẽ chứa tối đa 3600 phép đo được nhập trong khoảng thời gian 1 giây trong một giờ cụ thể trong ngày, trước khi một tài liệu mới sẽ được tạo để lưu trữ tất cả các phép đo của cùng một cảm biến cho giờ tiếp theo trong ngày

Mặc dù cách tiếp cận này có thể hoạt động khá tốt, nhưng người ta cần đầu tư những suy nghĩ trước về thiết kế lược đồ và ngoài ra, điều đó có nghĩa là gánh nặng lớn hơn cho các nhà phát triển. Họ phải triển khai cũng như điều chỉnh và điều chỉnh logic nhóm cho các tình huống nhập chuỗi thời gian như vậy trong lớp ứng dụng. Ngoài ra, khi nói đến một số loại truy vấn nhất định, sẽ có nhiều nỗ lực hơn khi nhắm mục tiêu các bộ sưu tập chứa tài liệu được cấu trúc theo mẫu nhóm. Điều này là do đối với các truy vấn đối với các tập hợp như vậy, chiến lược nhóm cụ thể phải được biết và xem xét tương ứng

Hiện tại

Chuyển nhanh đến bản phát hành MongoDB 5. 0 hiện mang đến sự hỗ trợ “gốc” cho các bộ sưu tập chuỗi thời gian. Lời hứa là các nhà phát triển không cần phải vật lộn với các thủ thuật thiết kế lược đồ như mẫu xô nữa. Thay vào đó, họ có thể chỉ cần chèn và truy vấn trực tiếp dữ liệu chuỗi thời gian của mình mà không cần xem xét thêm trên lớp ứng dụng. Nhưng chính xác thì điều này hoạt động như thế nào và nó trông như thế nào ở hậu trường từ góc độ lưu trữ tài liệu?

Các dữ liệu khám phá sau đây dựa trên các phép đo thô. Dữ liệu chứa 3 trường và có dạng như sau

{ ts: 2021-07-10T00:00:03.000Z,
  metadata: { sensorId: 31096, type: 'windspeed' },
  value: 32.53987084180961 }

Vào chế độ toàn màn hình Thoát chế độ toàn màn hình

  • ts đại diện cho dấu thời gian của dữ liệu cảm biến
  • siêu dữ liệu lưu trữ cảm biến và loại dữ liệu nào chúng tôi đang xử lý
  • giá trị giữ số đọc cảm biến thực tế, giá trị tốc độ gió trong trường hợp này

Lưu ý rằng nói chung, bạn có thể có nhiều tài liệu đo lường phức tạp hơn chứa nhiều trường trọng tải hơn với các loại dữ liệu khác nhau và các phần tử lồng nhau. Nó được giữ đơn giản ở đây trên mục đích

Bước 1. Tạo bộ sưu tập chuỗi thời gian

Lệnh để tạo loại bộ sưu tập chuỗi thời gian mới này như sau

db.createCollection("windsensors", { timeseries: { timeField: "ts", metaField: "metadata", granularity: "seconds" } } )

Vào chế độ toàn màn hình Thoát chế độ toàn màn hình

Bên cạnh tên, chúng tôi chỉ định cài đặt liên quan đến chuỗi thời gian. Quan trọng nhất và cấu hình bắt buộc duy nhất là cung cấp tên của trường chứa dấu thời gian của phép đo, "ts" trong trường hợp này. "MetaField" là nhãn mô tả cho dữ liệu cảm biến và "độ chi tiết" (giờ, phút hoặc giây = mặc định) xác định khoảng thời gian nhập dự kiến ​​cho các lần đọc cảm biến được đề cập

Bước 2. Chèn tài liệu mẫu

Với bộ sưu tập chuỗi thời gian trống của chúng tôi, hãy nhập 10 tài liệu mẫu sau đây, bắt nguồn từ 4 cảm biến khác nhau

db.windsensors.insertMany([
   {"metadata":{"sensorId":52396,"type":"windspeed"},"ts":ISODate("2021-07-10T00:00:02Z"),"value":18.263742590570686},
   {"metadata":{"sensorId":31096,"type":"windspeed"},"ts":ISODate("2021-07-10T00:00:03Z"),"value":32.53987084180961},
   {"metadata":{"sensorId":52396,"type":"windspeed"},"ts":ISODate("2021-07-10T00:00:03Z"),"value":18.106480571706808},
   {"metadata":{"sensorId":62088,"type":"windspeed"},"ts":ISODate("2021-07-10T00:00:04Z"),"value":20.306831899199864},
   {"metadata":{"sensorId":31096,"type":"windspeed"},"ts":ISODate("2021-07-10T00:00:04Z"),"value":0.6909954039798452},
   {"metadata":{"sensorId":62088,"type":"windspeed"},"ts":ISODate("2021-07-10T00:00:06Z"),"value":0.031065898581725086},
   {"metadata":{"sensorId":27470,"type":"windspeed"},"ts":ISODate("2021-07-10T00:00:07Z"),"value":6.878726412679837},
   {"metadata":{"sensorId":31096,"type":"windspeed"},"ts":ISODate("2021-07-10T00:00:07Z"),"value":3.9089926192773534},
   {"metadata":{"sensorId":52396,"type":"windspeed"},"ts":ISODate("2021-07-10T00:00:07Z"),"value":28.03679268099916},
   {"metadata":{"sensorId":52396,"type":"windspeed"},"ts":ISODate("2021-07-10T00:00:07Z"),"value":1.0575968433736358}
])

Vào chế độ toàn màn hình Thoát chế độ toàn màn hình

Bước 3. Chạy truy vấn tìm kiếm đơn giản đối với bộ sưu tập chuỗi thời gian

db.windsensors.find()

Vào chế độ toàn màn hình Thoát chế độ toàn màn hình

Tập hợp kết quả cho thấy rằng tất cả 10 tài liệu được trả về riêng biệt, điều này có thể gây ngạc nhiên ngay từ cái nhìn đầu tiên, bởi vì điều này khá giống với những gì chúng ta mong đợi từ một bộ sưu tập "bình thường", tôi. e. không có bất kỳ loại lưu trữ được tối ưu hóa theo chuỗi thời gian nào

{ ts: 2021-07-10T00:00:02.000Z,
  metadata: { sensorId: 52396, type: 'windspeed' },
  _id: ObjectId("60f3350afbb696c9ace09a19"),
  value: 18.263742590570686 }
{ ts: 2021-07-10T00:00:03.000Z,
  metadata: { sensorId: 52396, type: 'windspeed' },
  _id: ObjectId("60f3350afbb696c9ace09a1b"),
  value: 18.106480571706808 }
{ ts: 2021-07-10T00:00:07.000Z,
  metadata: { sensorId: 52396, type: 'windspeed' },
  _id: ObjectId("60f3350afbb696c9ace09a21"),
  value: 28.03679268099916 }
{ ts: 2021-07-10T00:00:07.000Z,
  metadata: { sensorId: 52396, type: 'windspeed' },
  _id: ObjectId("60f3350afbb696c9ace09a22"),
  value: 1.0575968433736358 }
{ ts: 2021-07-10T00:00:03.000Z,
  metadata: { sensorId: 31096, type: 'windspeed' },
  _id: ObjectId("60f3350afbb696c9ace09a1a"),
  value: 32.53987084180961 }
{ ts: 2021-07-10T00:00:04.000Z,
  metadata: { sensorId: 31096, type: 'windspeed' },
  _id: ObjectId("60f3350afbb696c9ace09a1d"),
  value: 0.6909954039798452 }
{ ts: 2021-07-10T00:00:07.000Z,
  metadata: { sensorId: 31096, type: 'windspeed' },
  _id: ObjectId("60f3350afbb696c9ace09a20"),
  value: 3.9089926192773534 }
{ ts: 2021-07-10T00:00:04.000Z,
  metadata: { sensorId: 62088, type: 'windspeed' },
  _id: ObjectId("60f3350afbb696c9ace09a1c"),
  value: 20.306831899199864 }
{ ts: 2021-07-10T00:00:06.000Z,
  metadata: { sensorId: 62088, type: 'windspeed' },
  _id: ObjectId("60f3350afbb696c9ace09a1e"),
  value: 0.031065898581725086 }
{ ts: 2021-07-10T00:00:07.000Z,
  metadata: { sensorId: 27470, type: 'windspeed' },
  _id: ObjectId("60f3350afbb696c9ace09a1f"),
  value: 6.878726412679837 }

Vào chế độ toàn màn hình Thoát chế độ toàn màn hình

Trên thực tế, khi chúng tôi đề cập đến cảm biến gió trong truy vấn của mình, chúng tôi đang làm việc với một khái niệm trừu tượng hợp lý được chính thức coi là "chế độ xem có thể ghi, không cụ thể hóa". Chúng tôi có thể xác minh điều này bằng cách kiểm tra các chế độ xem hiện tại như sau. Đang chạy

db.getCollection('system.views').find()

Vào chế độ toàn màn hình Thoát chế độ toàn màn hình

trình diễn

{ _id: 'mytsdemo.windsensors',
  viewOn: 'system.buckets.windsensors',
  pipeline: 
   [ { '$_internalUnpackBucket': 
        { timeField: 'ts',
          metaField: 'metadata',
          bucketMaxSpanSeconds: 3600,
          exclude: [] } } ] }

Vào chế độ toàn màn hình Thoát chế độ toàn màn hình

Định nghĩa dạng xem cho chúng ta biết rằng nó dựa trên một bộ sưu tập có tên là hệ thống. xô. cảm biến gió. Ngoài chế độ xem "bình thường", do người dùng tạo, trường đường dẫn cho chế độ xem đặc biệt này hiển thị một trình giữ chỗ có tên $_internalUnpackBucket cùng với cài đặt cấu hình liên quan đến chuỗi thời gian được sử dụng trong quá trình tạo bộ sưu tập tương ứng. Đáng chú ý là trường bucketMaxSpanSeconds ở đây là 3600. Đó là một giá trị tính bằng giây và phụ thuộc vào mức độ chi tiết đã chọn được đặt trong thời gian tạo. Đối với ví dụ này, điều đó có nghĩa là một nhóm sẽ kéo dài tối đa 3600 giây, tôi. e. 1 giờ. Điểm khác biệt của điều này là dữ liệu chuỗi thời gian được tối ưu hóa lưu trữ thực tế có thể được tìm thấy trong bộ sưu tập "nội bộ" riêng biệt được chỉ định trong trường viewOn của trừu tượng hóa chế độ xem logic

Bước 4. Chạy truy vấn tìm kiếm đơn giản đối với bộ sưu tập cơ bản gốc

Ngay cả khi thường không có nhu cầu truy cập trực tiếp vào phiên bản lưu trữ được tối ưu hóa của dữ liệu chuỗi thời gian, chúng ta hãy làm điều đó để tìm hiểu điều gì xảy ra đằng sau hậu trường. Truy vấn sau đây chỉ truy xuất một tài liệu từ bộ sưu tập cơ bản này

db.getCollection('system.buckets.windsensors').findOne()

Vào chế độ toàn màn hình Thoát chế độ toàn màn hình

Chúng tôi nhận lại một tập kết quả như thế này

________số 8

Vào chế độ toàn màn hình Thoát chế độ toàn màn hình

Hãy kiểm tra cấu trúc tài liệu bằng cách xem xét kỹ hơn một tập hợp con của các trường được chứa

  • kiểm soát. min giữ giá trị dấu thời gian giới hạn dưới của nhóm, giá trị này phụ thuộc vào mức độ chi tiết đã chọn, ngoài ra, giá trị thấp nhất được đo trong nhóm này và ObjectId đề cập đến mục nhập đầu tiên được lưu trữ trong nhóm của tài liệu này

  • kiểm soát. max giữ giá trị dấu thời gian gần đây nhất được lưu trữ trong nhóm này, ngoài ra, giá trị cao nhất được đo trong nhóm này và ObjectId đề cập đến mục nhập cuối cùng được lưu trữ trong nhóm của tài liệu này cho đến nay

Rõ ràng là dữ liệu chứa cho cả hai, điều khiển. tối thiểu và kiểm soát. max được cập nhật nhanh chóng khi các chỉ số cảm biến mới được nhập vào tài liệu này và nhóm của nó. Nói chung, hai tài liệu phụ đó sẽ lưu trữ các giá trị tối thiểu và tối đa cho từng trường có trong tải trọng của phép đo ban đầu. Trong trường hợp của chúng tôi, đó chỉ là trường giá trị với một phép đo tốc độ gió duy nhất

  • dữ liệu là một đối tượng phức tạp chứa tất cả thông tin của mọi tải trọng dữ liệu cảm biến đã được nhập cho đến nay. Chỉ có 3 trường trong ví dụ cụ thể này. mã định danh tài liệu (_id), dấu thời gian (ts) và dữ liệu cảm biến (giá trị). Nếu có nhiều trường hơn trong tài liệu đo lường ban đầu ngoài giá trị, thì tất cả chúng sẽ được lưu trữ ở đây theo cách tương tự. Bản thân các phép đo được tham chiếu bằng cách sử dụng tên trường được cung cấp bởi chỉ mục bộ chứa i. e. 0. N cho mỗi lần đo. Nói chung, trường dữ liệu sẽ chứa các tài liệu phụ cho tất cả các trường trọng tải của tài liệu đo lường ban đầu

Dựa trên tài liệu duy nhất này, có thể xây dựng lại mọi tài liệu đo lường ban đầu đã từng được nhập vào bộ chứa này, chỉ bằng cách kết hợp trường meta với mọi bộ 3, e. g. { Tôi. 0, ts. 0, giá trị. 0 } … { _id. N, ts. N, giá trị. N } được lấy từ trường _data. Nói chung, đây sẽ là một N-tuple, vì trường dữ liệu sẽ chứa các tài liệu con cho tất cả các trường trọng tải của tài liệu đo lường ban đầu. Một ví dụ cụ thể cho dữ liệu mẫu của chúng tôi dẫn đến tài liệu đo lường ban đầu đầu tiên được lưu trữ trong nhóm này là

{
_id: ObjectId("60f3350afbb696c9ace09a19"),
 ts: 2021-07-10T00:00:02.000Z,
 value: 18.263742590570686,
 meta: { sensorId: 52396, type: 'windspeed' }
}

Vào chế độ toàn màn hình Thoát chế độ toàn màn hình

Nếu chúng tôi kiểm tra 3 tài liệu khác trong bộ sưu tập được tối ưu hóa lưu trữ cơ bản thì chúng trông rất giống nhau. Sự khác biệt về cấu trúc duy nhất của các nhóm là hiện tại, mỗi nhóm có số lượng mục nhập khác nhau, điều này chính xác như vậy vì 10 tài liệu gốc bắt nguồn từ 4 cảm biến khác nhau, mỗi cảm biến có số lần đọc khác nhau được nhập vào cho đến thời điểm đó

db.createCollection("windsensors", { timeseries: { timeField: "ts", metaField: "metadata", granularity: "seconds" } } )
0

Vào chế độ toàn màn hình Thoát chế độ toàn màn hình

Bước 5. Chơi với sự tăng trưởng xô và giới hạn xô

Tài liệu nhóm cho meta. { cảm biếnId. 52396, loại. 'windspeed' } hiện có 4 lần đọc cảm biến. Câu hỏi đặt ra là chúng ta có thể nhập bao nhiêu phép đo nữa vào thùng này?

Trước đó, chúng tôi đã kiểm tra định nghĩa chế độ xem của trừu tượng logic và đề cập ngắn gọn về cài đặt maxBucketSpanSize. Khi chọn mức độ chi tiết của giây trong quá trình tạo bộ sưu tập chuỗi thời gian, giá trị cho maxBucketSpanSize là 3600. Nói cách khác, điều này có nghĩa là các nhóm như thế này có thể kéo dài dữ liệu trong 1 giờ. Nếu chúng tôi nhập một phép đo mỗi giây, chúng tôi có thể cho rằng một nhóm như vậy có thể lưu trữ tới 3600 lần đọc cảm biến, mỗi giây một lần. Tuy nhiên, khi thử điều này, chúng tôi thấy một hành vi khác. Dường như có một số loại giới hạn trên 1000 mục nhập cố định trên mỗi nhóm trong bộ sưu tập chuỗi thời gian. Tài liệu ví dụ bên dưới hiển thị "nhóm đầy đủ" cho sensorId 52396 với các mục nhập nhóm đầu tiên và cuối cùng tương ứng trong khi bỏ qua phần còn lại của dữ liệu vì lý do ngắn gọn

db.createCollection("windsensors", { timeseries: { timeField: "ts", metaField: "metadata", granularity: "seconds" } } )
1

Vào chế độ toàn màn hình Thoát chế độ toàn màn hình

Tôi không tìm thấy bất kỳ dấu hiệu nào trong tài liệu chính thức hiện tại về "hằng số kỳ diệu" này trong việc giới hạn các nhóm ở 1000 mục nhập. Trường hợp này không liên quan được e. g. đến giới hạn kích thước tài liệu vì việc lưu trữ 1000 mục nhập với dữ liệu mẫu này không đạt đến giới hạn kích thước tài liệu cứng. Có thể mã nguồn sẽ tiết lộ thêm về điều này, nhưng cho đến nay tôi đã không dành thời gian để nghiên cứu cách triển khai

Chúng ta cũng có thể thấy từ điều khiển. tối thiểu và kiểm soát. dấu thời gian tối đa mà kích thước khoảng thời gian nhóm cụ thể này là "chỉ" 2363 giây, nhỏ hơn giá trị tối đa có thể là 3600. Điều này là do nhóm đạt đến giới hạn 1000 mục trước khi có thể đạt đến kích thước nhịp. Nói chung, một bộ chứa được đóng lại và một tài liệu mới được tạo, nếu đạt đến maxBucketSpanSize của nó hoặc các mục nhập tối đa của nó bị vượt quá (hiện tại là 1000), tùy theo điều kiện nào xảy ra trước

Một nghiên cứu khác dựa trên những quan sát này giải thích khuyến nghị được tìm thấy trong các tài liệu chính thức, cụ thể là cài đặt mức độ chi tiết đã chọn phải khớp với tốc độ nhập dữ liệu thực tế nhất có thể. Trong ví dụ của chúng tôi về bộ sưu tập chuỗi thời gian có độ chi tiết "giây", kích thước nhóm là 1 giờ (3600 giây). Tuy nhiên, nếu chúng tôi chỉ nhập 2 - 3 giá trị mỗi giờ, điều này có nghĩa là chúng tôi sẽ nhận được nhiều tài liệu mới trong bộ sưu tập chuỗi thời gian cơ bản với các nhóm rất nhỏ chỉ có 2 - 3 mục nhập mỗi. Rõ ràng, điều này sẽ tác động mạnh mẽ đến hiệu suất theo hướng tiêu cực và làm giảm toàn bộ cơ chế tối ưu hóa lưu trữ của các bộ sưu tập chuỗi thời gian đến mức vô lý. Vì vậy, hãy chọn mức độ chi tiết của các bộ sưu tập chuỗi thời gian của bạn một cách khôn ngoan

Kết luận và triển vọng

MongoDB 5. 0 đã giới thiệu một loại bộ sưu tập mới, được tối ưu hóa nguyên bản để lưu trữ dữ liệu chuỗi thời gian. Nó làm cho cuộc sống của các nhà phát triển trở nên dễ dàng hơn vì làm việc với các bộ sưu tập chuỗi thời gian dễ dàng và thuận tiện hơn rất nhiều khi so sánh điều này với quá khứ, nơi cần triển khai rõ ràng mẫu nhóm. Tôi hy vọng bài viết này đã đóng góp một chút vào hiểu biết của bạn về chính xác điều gì xảy ra đằng sau hậu trường của các bộ sưu tập chuỗi thời gian từ góc độ lưu trữ tài liệu và lược đồ tương ứng phản ánh ngầm ý tưởng đằng sau mẫu bộ chứa. Điều quan trọng nhất cần ghi nhớ là hãy coi thường những quan sát của tôi vì đây là lần khám phá nhanh đầu tiên của tôi về MongoDB 5 mới này. 0 tính năng

Tôi dự định viết thêm các phần trong bộ này. Bài viết thứ 2, sẽ thảo luận về các loại truy vấn tổng hợp khác nhau trong các bộ sưu tập chuỗi thời gian tập trung vào các chức năng cửa sổ mới được giới thiệu

MongoDB có tốt cho dữ liệu chuỗi thời gian không?

MongoDB là cơ sở dữ liệu có mục đích chung dựa trên tài liệu với thiết kế lược đồ linh hoạt và ngôn ngữ truy vấn phong phú. Kể từ MongoDB 5. 0, MongoDB vốn hỗ trợ dữ liệu chuỗi thời gian .

Làm thế nào có thể thiết lập độ chi tiết?

Việc đặt tham số mức độ chi tiết một cách chính xác sẽ cải thiện hiệu suất bằng cách tối ưu hóa cách dữ liệu trong bộ sưu tập chuỗi thời gian được lưu trữ nội bộ. Để đặt tham số một cách chính xác, hãy chọn giá trị mức độ chi tiết gần nhất với tốc độ nhập cho một nguồn dữ liệu duy nhất như được chỉ định bởi giá trị cho trường metaField .

Bộ sưu tập chuỗi thời gian trong MongoDB là gì?

MongoDB coi các bộ sưu tập chuỗi thời gian là các chế độ xem không cụ thể hóa có thể ghi được hỗ trợ bởi một bộ sưu tập nội bộ . Khi bạn chèn dữ liệu, bộ sưu tập nội bộ sẽ tự động sắp xếp dữ liệu chuỗi thời gian thành định dạng lưu trữ được tối ưu hóa. Khi bạn truy vấn các bộ sưu tập chuỗi thời gian, bạn thao tác trên một tài liệu cho mỗi phép đo.

Làm cách nào để lưu trữ thời gian trong MongoDB?

MongoDB lưu trữ thời gian ở dạng UTC theo mặc định và sẽ chuyển đổi mọi biểu diễn giờ địa phương thành dạng này. Các ứng dụng phải vận hành hoặc báo cáo về một số giá trị thời gian địa phương chưa sửa đổi có thể lưu trữ múi giờ cùng với dấu thời gian UTC và tính toán thời gian địa phương ban đầu trong logic ứng dụng của chúng.